Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
; EditorConfig helps developers define and maintain consistent
; coding styles between different editors and IDEs.

; For more visit http://editorconfig.org.
root = true

; Choose between lf or rf on "end_of_line" property
[*]
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.fs]
indent_size = 2
indent_style = space
Binary file modified .paket/paket.exe
Binary file not shown.
3 changes: 1 addition & 2 deletions paket.dependencies
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,11 @@ framework: >= net45

nuget Newtonsoft.Json >= 9.0.1
nuget FSharp.Core ~> 4.0.0
nuget Microsoft.Net.Http
nuget YamlDotNet
// New schema parser (experimental at the moment)
nuget Microsoft.OpenApi.Readers prerelease

github fsharp/FSharp.Data src/Net/UriUtils.fs
github fsharp/FSharp.Data src/Net/Http.fs
github fsharp/FSharp.Data src/CommonRuntime/Pluralizer.fs
github fsharp/FSharp.Data src/CommonRuntime/NameUtils.fs
github fsprojects/FSharp.TypeProviders.SDK src/ProvidedTypes.fsi
Expand Down
16 changes: 10 additions & 6 deletions paket.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ RESTRICTION: >= net45
NUGET
remote: https://www.nuget.org/api/v2
FSharp.Core (4.0.0.1)
Microsoft.Bcl (1.1.10)
Microsoft.Bcl.Build (>= 1.0.14)
Microsoft.Bcl.Build (1.0.21) - import_targets: false
Microsoft.Net.Http (2.2.29)
Microsoft.Bcl (>= 1.1.10)
Microsoft.Bcl.Build (>= 1.0.14)
Microsoft.OpenApi (1.0.0-beta014) - restriction: || (&& (>= net45) (>= netstandard2.0)) (>= net46)
Microsoft.OpenApi.Readers (1.0.0-beta014)
Microsoft.OpenApi (>= 1.0.0-beta014) - restriction: || (&& (>= net45) (>= netstandard2.0)) (>= net46)
Expand All @@ -11,13 +17,11 @@ NUGET
YamlDotNet (4.3)
GITHUB
remote: fsharp/FSharp.Data
src/CommonRuntime/NameUtils.fs (d7b754ddec038209bca7709994289454db1e395e)
src/CommonRuntime/Pluralizer.fs (d7b754ddec038209bca7709994289454db1e395e)
src/Net/Http.fs (d7b754ddec038209bca7709994289454db1e395e)
src/Net/UriUtils.fs (d7b754ddec038209bca7709994289454db1e395e)
src/CommonRuntime/NameUtils.fs (0fa6a6799172565f8f49bea5e572c1bb191406ff)
src/CommonRuntime/Pluralizer.fs (0fa6a6799172565f8f49bea5e572c1bb191406ff)
remote: fsprojects/FSharp.TypeProviders.SDK
src/ProvidedTypes.fs (059ed4fcf4f4d7587331574092777e65337a95db)
src/ProvidedTypes.fsi (059ed4fcf4f4d7587331574092777e65337a95db)
src/ProvidedTypes.fs (259244bd893f6f266f2f1c043cb6a52eba3cd3ce)
src/ProvidedTypes.fsi (259244bd893f6f266f2f1c043cb6a52eba3cd3ce)
GROUP Build
RESTRICTION: == net461
NUGET
Expand Down
82 changes: 43 additions & 39 deletions src/SwaggerProvider.DesignTime/Configuration.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ open System.Configuration
open System.Collections.Generic

type Logging() =
static member logf (s:string) =
()//System.IO.File.AppendAllLines(@"c:\temp\swaggerlog.txt", [|s|])
static member logf (s: string) =
() // File.AppendAllLines("swaggerlog", [|s|])

/// Returns the Assembly object of SwaggerProvider.Runtime.dll (this needs to
/// work when called from SwaggerProvider.DesignTime.dll)
let getSwaggerProviderRuntimeAssembly() =
let swaggerRuntimeAssy =
AppDomain.CurrentDomain.GetAssemblies()
|> Seq.find (fun a -> a.FullName.StartsWith("SwaggerProvider,"))

Expand All @@ -24,46 +24,50 @@ let rec searchDirectories (patterns:string list) dirs =
match patterns with
| [] -> dirs
| name::patterns when name.EndsWith("*") ->
let prefix = name.TrimEnd([|'*'|])
dirs
|> List.collect (fun dir ->
Directory.GetDirectories dir
|> Array.filter (fun x -> x.IndexOf(prefix, dir.Length) >= 0)
|> List.ofArray
)
|> searchDirectories patterns
let prefix = name.TrimEnd([|'*'|])
dirs
|> List.collect (fun dir ->
Directory.GetDirectories dir
|> Array.filter (fun x -> x.IndexOf(prefix, dir.Length) >= 0)
|> List.ofArray
)
|> searchDirectories patterns
| name::patterns ->
dirs
|> List.map (fun d -> Path.Combine(d, name))
|> searchDirectories patterns
dirs
|> List.map (fun d -> Path.Combine(d, name))
|> searchDirectories patterns

/// Returns the real assembly location - when shadow copying is enabled, this
/// returns the original assembly location (which may contain other files we need)
let getAssemblyLocation (assem:Assembly) =
if System.AppDomain.CurrentDomain.ShadowCopyFiles then
(new System.Uri(assem.EscapedCodeBase)).LocalPath
if System.AppDomain.CurrentDomain.ShadowCopyFiles
then (System.Uri(assem.EscapedCodeBase)).LocalPath
else assem.Location

/// Reads the 'SwaggerProvider.dll.config' file and gets the 'ProbingLocations'
/// parameter from the configuration file. Resolves the directories and returns
/// them as a list.
let getProbingLocations() =
let probingLocations =
try
let root = getSwaggerProviderRuntimeAssembly() |> getAssemblyLocation
Logging.logf <| sprintf "Root %s" root
let config = System.Configuration.ConfigurationManager.OpenExeConfiguration(root)
let rootExe = getAssemblyLocation swaggerRuntimeAssy
let rootDir = Path.GetDirectoryName rootExe
Logging.logf <| sprintf "Root %s" rootDir
let config = System.Configuration.ConfigurationManager.OpenExeConfiguration(rootExe)
let pattern = config.AppSettings.Settings.["ProbingLocations"]
if pattern <> null then
Logging.logf <| sprintf "Pattern %s" pattern.Value
let rootDir = Path.GetDirectoryName(root)
[ yield rootDir
let pattern = pattern.Value.Split(';', ',') |> List.ofSeq
for pat in pattern do
let roots = [ rootDir ]
for dir in roots |> searchDirectories (List.ofSeq (pat.Split('/','\\'))) do
if Directory.Exists(dir)
then yield Path.GetFullPath(dir) ]
else []
if isNull pattern
then []
else
Logging.logf <| sprintf "Probing patterns %A" (pattern.Value.Split(';'))
let dirs =
[ yield rootDir
let pattern = pattern.Value.Split(';', ',') |> List.ofSeq
for pat in pattern do
let roots = [ rootDir ]
for dir in roots |> searchDirectories (List.ofSeq (pat.Split('/','\\'))) do
if Directory.Exists(dir)
then yield Path.GetFullPath(dir) ]
Logging.logf (sprintf "Found probing directories: %A" dirs)
dirs
with :? ConfigurationErrorsException | :? KeyNotFoundException -> []

/// Given an assembly name, try to find it in either assemblies
Expand All @@ -80,18 +84,16 @@ let resolveReferencedAssembly (asmName:string) =
System.AppDomain.CurrentDomain.GetAssemblies()
|> Seq.tryFind (fun a -> AssemblyName.ReferenceMatchesDefinition(fullName, a.GetName()))
match loadedAsm with
| Some asm -> asm
| Some asm ->
Logging.logf (sprintf "found assembly %s" asm.FullName)
asm
| None ->

// Otherwise, search the probing locations for a DLL file
let libraryName =
let idx = asmName.IndexOf(',')
if idx > 0 then asmName.Substring(0, idx) else asmName

let locations = getProbingLocations()
Logging.logf <| sprintf "Probing locations: %A" locations

let asm = locations |> Seq.tryPick (fun dir ->
let asm = probingLocations |> Seq.tryPick (fun dir ->
let library = Path.Combine(dir, libraryName+".dll")
if File.Exists(library) then
Logging.logf <| sprintf "Found assembly, checking version! (%s)" library
Expand All @@ -104,7 +106,9 @@ let resolveReferencedAssembly (asmName:string) =
else
Logging.logf "...version mismatch, skipping"
None
else None)
else
Logging.logf <| sprintf "Didn't find library %s" libraryName
None)

if asm = None then Logging.logf <| sprintf "Assembly not found! %s" asmName
defaultArg asm null
defaultArg asm null
112 changes: 71 additions & 41 deletions src/SwaggerProvider.DesignTime/OperationCompiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ open Swagger.Parser.Schema
open Swagger.Internal

open System
open FSharp.Data

open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.ExprShape
open System.Text.RegularExpressions
open SwaggerProvider.Internal
open System.Threading.Tasks
open System.Net.Http
open System.Collections.Generic

/// Object for compiling operations.
type OperationCompiler (schema:SwaggerObject, defCompiler:DefinitionCompiler, ignoreControllerPrefix, ignoreOperationId, asAsync: bool, asyncTy: Type, taskTy: Type) =
type OperationCompiler (schema:SwaggerObject, defCompiler:DefinitionCompiler, ignoreControllerPrefix, ignoreOperationId, asAsync: bool) =
let compileOperation (methodName:string) (op:OperationObject) =
if String.IsNullOrWhiteSpace methodName
then failwithf "Operation name could not be empty. See '%s/%A'" op.Path op.Type
Expand Down Expand Up @@ -60,22 +60,29 @@ type OperationCompiler (schema:SwaggerObject, defCompiler:DefinitionCompiler, ig
| None -> None
| Some ty -> Some <| defCompiler.CompileTy methodName "Response" ty true
| None -> None

let generateReturnType (retTy: Type) asAsync =
if asAsync then ProvidedTypeBuilder.MakeGenericType(asyncTy, [retTy])
else ProvidedTypeBuilder.MakeGenericType(taskTy, [retTy])

let overallReturnType = generateReturnType (defaultArg retTy (typeof<unit>)) asAsync
let overallReturnType =
ProvidedTypeBuilder.MakeGenericType(
(if asAsync
then typedefof<Async<unit>>
else typedefof<System.Threading.Tasks.Task<unit>>),
[defaultArg retTy (typeof<unit>)]
)

let m = ProvidedMethod(methodName, parameters, overallReturnType, invokeCode = fun args ->
let thisTy = typeof<ProvidedSwaggerBaseType>
let this = Expr.Coerce(args.[0], thisTy) |> Expr.Cast<ProvidedSwaggerBaseType>
let host = <@ (%this).Host @>
let headers = <@ (%this).Headers @>
let customizeHttpRequest = <@ (%this).CustomizeHttpRequest @>

let httpMethod = op.Type.ToString()

let basePath =
let basePath = schema.BasePath
<@ RuntimeHelpers.combineUrl %host basePath @>
if String.IsNullOrWhiteSpace basePath
then host
else <@ RuntimeHelpers.combineUrl %host basePath @>

// Fit headers into quotation
let headers =
Expand Down Expand Up @@ -112,7 +119,8 @@ type OperationCompiler (schema:SwaggerObject, defCompiler:DefinitionCompiler, ig
RuntimeHelpers.toQueryParams name o @>

let replacePathTemplate (path: Expr<string>) (name: string) (value: Expr<string>) =
<@ Regex.Replace(%path, sprintf "{%s}" name, %value) @>
let pattern = sprintf "{%s}" name
<@ Regex.Replace(%path, pattern, %value) @>

let addPayload load (param : ParameterObject) (exp : Expr) =
let name = param.Name
Expand Down Expand Up @@ -152,61 +160,83 @@ type OperationCompiler (schema:SwaggerObject, defCompiler:DefinitionCompiler, ig
( <@ mPath @>, None, <@ [] @>, headers)

let address = <@ RuntimeHelpers.combineUrl %basePath %path @>
let restCall = op.Type.ToString()

let customizeHttpRequest =
<@ fun (request:Net.HttpWebRequest) ->
if restCall = "Post"
then request.ContentLength <- 0L
(%customizeHttpRequest) request @>
// let customizeHttpRequest =
// <@ fun (request:HttpRequestMessage) ->
// if httpMethod = Post.ToString() && request.Content <> null
// then request.Content.Headers.ContentLength <- Nullable<_> 0L
// (%customizeHttpRequest) request @>

let innerReturnType = defaultArg retTy null
let action =

let httpRequestMessage =
<@
let requestUrl =
let uriB = UriBuilder %address
let newQueries =
%queries
|> Seq.map (fun (name, value) ->
String.Format("{0}={1}", Uri.EscapeDataString name, Uri.EscapeDataString value))
|> String.concat "&"
if String.IsNullOrEmpty uriB.Query
then uriB.Query <- newQueries
else uriB.Query <- String.Format("{0}&{1}", uriB.Query, newQueries)
uriB.Uri
let method = HttpMethod(httpMethod)
new HttpRequestMessage(method, requestUrl)
@>

let httpRequestMessageWithPayload =
match payload with
| None ->
<@ Http.AsyncRequestString(%address,
httpMethod = restCall,
headers = %heads,
query = %queries,
customizeHttpRequest = %customizeHttpRequest) @>
| None -> httpRequestMessage
| Some (FormData, b) ->
<@ Http.AsyncRequestString(%address,
httpMethod = restCall,
headers = %heads,
body = HttpRequestBody.FormValues (%%b: seq<string*string>),
query = %queries,
customizeHttpRequest = %customizeHttpRequest) @>
<@ let keyValues = (%%b: seq<string*string>) |> Seq.map KeyValuePair
let msg = %httpRequestMessage
msg.Content <- new FormUrlEncodedContent(keyValues)
msg @>
| Some (Body, b) ->
<@ let body = RuntimeHelpers.serialize (%%b: obj)
Http.AsyncRequestString(%address,
httpMethod = restCall,
headers = %heads,
body = HttpRequestBody.TextRequest body,
query = %queries,
customizeHttpRequest = %customizeHttpRequest) @>
<@ let content = new StringContent(RuntimeHelpers.serialize (%%b: obj), Text.Encoding.UTF8, "application/json")
let msg = %httpRequestMessage
msg.Content <- content
msg @>
| Some (x, _) -> failwith ("Payload should not be able to have type: " + string x)


let action =
<@
let msg = %httpRequestMessageWithPayload
%heads
|> Seq.iter (fun (name, value) ->
if not <| msg.Headers.TryAddWithoutValidation(name, value) then
let errMsg = String.Format("Cannot add header '{0}'='{1}' to HttpRequestMessage", name, value)
if (name <> "Content-Type") then
raise <| System.Exception(errMsg)
)
let msg = (%customizeHttpRequest) msg
RuntimeHelpers.sendMessage msg
@>

let responseObj =
<@ async {
let! response = %action
return RuntimeHelpers.deserialize response innerReturnType
} @>
} @>

let responseUnit =
<@ async {
let! _ = %action
return ()
} @>

let sync e = <@ Async.RunSynchronously(%e) @>
let task t = <@ Async.StartAsTask(%t) @>

// if we're an async method, then we can just return the above, coerced to the overallReturnType.
// if we're not async, then run that^ through Async.RunSynchronously before doing the coercion.
match asAsync, retTy with
| true, Some t -> Expr.Coerce(<@ RuntimeHelpers.asyncCast t %responseObj @>, overallReturnType)
| true, None -> responseUnit.Raw
| false, Some t -> Expr.Coerce(<@ RuntimeHelpers.taskCast t %(task responseObj) @>, overallReturnType)
| false, None -> Expr.Coerce(task responseUnit, overallReturnType)
)
| false, None -> (task responseUnit).Raw
)
if not <| String.IsNullOrEmpty(op.Summary)
then m.AddXmlDoc(op.Summary) // TODO: Use description of parameters in docs
if op.Deprecated
Expand Down
Loading