Skip to content
Closed
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
1 change: 1 addition & 0 deletions paket.dependencies
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
source https://nuget.org/api/v2
framework: >= net45
redirects: on

nuget Newtonsoft.Json >= 9.0.1
nuget FSharp.Core ~> 4.0.0
Expand Down
27 changes: 14 additions & 13 deletions paket.lock
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
REDIRECTS: ON
RESTRICTION: >= net45
NUGET
remote: https://www.nuget.org/api/v2
Expand All @@ -6,20 +7,20 @@ NUGET
YamlDotNet (4.2.1)
GITHUB
remote: fsharp/FSharp.Data
src/CommonRuntime/IO.fs (503ed03c0ab56dcd4938946d898df886a7c5a79c)
src/CommonRuntime/NameUtils.fs (503ed03c0ab56dcd4938946d898df886a7c5a79c)
src/CommonRuntime/Pluralizer.fs (503ed03c0ab56dcd4938946d898df886a7c5a79c)
src/CommonRuntime/StructuralTypes.fs (503ed03c0ab56dcd4938946d898df886a7c5a79c)
src/CommonRuntime/TextConversions.fs (503ed03c0ab56dcd4938946d898df886a7c5a79c)
src/Json/JsonConversions.fs (503ed03c0ab56dcd4938946d898df886a7c5a79c)
src/Json/JsonExtensions.fs (503ed03c0ab56dcd4938946d898df886a7c5a79c)
src/Json/JsonValue.fs (503ed03c0ab56dcd4938946d898df886a7c5a79c)
src/Net/Http.fs (503ed03c0ab56dcd4938946d898df886a7c5a79c)
src/Net/UriUtils.fs (503ed03c0ab56dcd4938946d898df886a7c5a79c)
src/CommonRuntime/IO.fs (4c99395411e923afc82d010fd89190dd43c60a79)
src/CommonRuntime/NameUtils.fs (4c99395411e923afc82d010fd89190dd43c60a79)
src/CommonRuntime/Pluralizer.fs (4c99395411e923afc82d010fd89190dd43c60a79)
src/CommonRuntime/StructuralTypes.fs (4c99395411e923afc82d010fd89190dd43c60a79)
src/CommonRuntime/TextConversions.fs (4c99395411e923afc82d010fd89190dd43c60a79)
src/Json/JsonConversions.fs (4c99395411e923afc82d010fd89190dd43c60a79)
src/Json/JsonExtensions.fs (4c99395411e923afc82d010fd89190dd43c60a79)
src/Json/JsonValue.fs (4c99395411e923afc82d010fd89190dd43c60a79)
src/Net/Http.fs (4c99395411e923afc82d010fd89190dd43c60a79)
src/Net/UriUtils.fs (4c99395411e923afc82d010fd89190dd43c60a79)
remote: fsprojects/FSharp.TypeProviders.StarterPack
src/ProvidedTypes.fs (1dab4f94411500794342f61f877feab772e5a754)
src/ProvidedTypes.fsi (1dab4f94411500794342f61f877feab772e5a754)
src/ProvidedTypesTesting.fs (1dab4f94411500794342f61f877feab772e5a754)
src/ProvidedTypes.fs (c372debaa7ef4febfe16d98dec765d74640bbe82)
src/ProvidedTypes.fsi (c372debaa7ef4febfe16d98dec765d74640bbe82)
src/ProvidedTypesTesting.fs (c372debaa7ef4febfe16d98dec765d74640bbe82)
GROUP Build
RESTRICTION: == net45
NUGET
Expand Down
9 changes: 6 additions & 3 deletions src/SwaggerProvider.DesignTime/DefinitionCompiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ open Microsoft.FSharp.Quotations
open System
open System.Reflection

type FileWrapper(fileName: string, data: IO.Stream) =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to make this a tuple of these two pieces of data but that failed for some reason.

member __.fileName = fileName
member __.data = data

/// Object for compiling definitions.
type DefinitionCompiler (schema:SwaggerObject) =
let definitions = Map.ofSeq schema.Definitions
Expand Down Expand Up @@ -66,7 +70,7 @@ type DefinitionCompiler (schema:SwaggerObject) =
| String, _ -> typeof<string>
| Date, true | DateTime, true -> typeof<DateTime>
| Date, false | DateTime, false -> typeof<Option<DateTime>>
| File, _ -> typeof<byte>.MakeArrayType(1)
| File, _ -> typeof<FileWrapper> // contentType * fileStream
| Enum _, _ -> typeof<string> //TODO: find better type
| Array eTy, _ -> (compileSchemaObject (uniqueName tyName "Item") eTy true).MakeArrayType()
| Dictionary eTy,_-> typedefof<Map<string, obj>>.MakeGenericType(
Expand Down Expand Up @@ -155,8 +159,7 @@ type DefinitionCompiler (schema:SwaggerObject) =

// Compiles the `definitions` part of the schema
do schema.Definitions
|> Seq.iter (fun (name,_) ->
compileDefinition name |> ignore)
|> Seq.iter (fst >> compileDefinition >> ignore)

/// Compiles the definition.
member __.GetProvidedTypes() =
Expand Down
141 changes: 90 additions & 51 deletions src/SwaggerProvider.DesignTime/OperationCompiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ open FSharp.Data
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.ExprShape
open System.Text.RegularExpressions
open System.IO

type BodyType =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modeling the body content like this made it easier to switch between formdata and multipart form data when we get parameters lists that have normal form data and files

| BodyForm of Expr<(string*string) seq>
| BodyObject of Expr<obj>
| BodyMultipart of Expr<MultipartItem seq>

/// Object for compiling operations.
type OperationCompiler (schema:SwaggerObject, defCompiler:DefinitionCompiler) =
Expand All @@ -27,20 +33,23 @@ type OperationCompiler (schema:SwaggerObject, defCompiler:DefinitionCompiler) =
let paramName = niceCamelName x.Name
let paramType = defCompiler.CompileTy methodName paramName x.Type x.Required
if x.Required then ProvidedParameter(paramName, paramType)
else
else
let paramDefaultValue = defCompiler.GetDefaultValue x.Type
ProvidedParameter(paramName, paramType, false, paramDefaultValue)
]
let retTy =
let retTy, isReturnFile =
let okResponse = // BUG : wrong selector
op.Responses |> Array.tryFind (fun (code, resp) ->
(code.IsSome && (code.Value = 200 || code.Value = 201)) || code.IsNone)
match okResponse with
| Some (_,resp) ->
match resp.Schema with
| None -> typeof<unit>
| Some ty -> defCompiler.CompileTy methodName "Response" ty true
| None -> typeof<unit>
| None -> typeof<unit>, false
| Some ty when ty = SchemaObject.File ->
typeof<IO.Stream>, true
| Some ty ->
defCompiler.CompileTy methodName "Response" ty true, false
| None -> typeof<unit>, false

let m = ProvidedMethod(methodName, parameters, retTy)
if not <| String.IsNullOrEmpty(op.Summary)
Expand All @@ -62,13 +71,13 @@ type OperationCompiler (schema:SwaggerObject, defCompiler:DefinitionCompiler) =
// Fit headers into quotation
let headers =
let jsonConsumable = op.Consumes |> Seq.exists (fun mt -> mt="application/json")
<@@
<@
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

generally got away from the untyped expression API as much as possible in this PR.

let headersArr = (%%headers:(string*string)[])
let ctHeaderExist = headersArr |> Array.exists (fun (h,_)->h="Content-Type")
if not(ctHeaderExist) && jsonConsumable
then Array.append [|"Content-Type","application/json"|] headersArr
else headersArr
@@>
@>
//let headerPairs =
// seq {
// yield! headers
Expand Down Expand Up @@ -99,36 +108,52 @@ type OperationCompiler (schema:SwaggerObject, defCompiler:DefinitionCompiler) =
let obj = Expr.Coerce(exp, typeof<obj>)
<@ (%%obj : obj).ToString() @>

let rec corceQueryString name expr =
let rec coerceQueryString name expr =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tiny typo here

let obj = Expr.Coerce(expr, typeof<obj>)
<@ let o = (%%obj : obj)
SwaggerProvider.Internal.RuntimeHelpers.toQueryParams name o @>

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

let createStream (stringData: string) =
stringData |> Text.Encoding.UTF8.GetBytes |> MemoryStream :> Stream

let addPayload load (param : ParameterObject) (exp : Expr) =
let name = param.Name
let var = coerceString param.Type param.CollectionFormat exp
// delay the string coercion so that if it's a stream we don't tostring it.
let var () = coerceString param.Type param.CollectionFormat exp
match load with
| Some (FormData, b) -> Some (FormData, <@@ Seq.append %%b [name, (%var : string)] @@>)
| None -> match param.In with
| Body -> Some (Body, Expr.Coerce (exp, typeof<obj>))
| _ -> Some (FormData, <@@ (seq [name, (%var : string)]) @@>)
| Some (BodyForm f) ->
Some (BodyForm <@ Seq.append %f [name, %var()] @>)
| Some (BodyForm f) when param.Type = SchemaObject.File ->
// have to convert existing form items to streams when we hit a file so that multipart upload can occur
let prevs = <@ %f |> Seq.map (fun (k,v) -> MultipartItem(k, k, createStream(v))) @>
let wrapper = Expr.Coerce (exp, typeof<FileWrapper>) |> Expr.Cast<FileWrapper>
Some (BodyMultipart <@ Seq.append %prevs [ MultipartItem(name, (%wrapper).fileName, (%wrapper).data) ] @>)
| Some (BodyMultipart f) ->
Some (BodyMultipart <@ Seq.append %f [ MultipartItem(name, name, createStream(%var())) ] @>)
| None when param.Type = SchemaObject.File ->
let wrapper = Expr.Coerce (exp, typeof<FileWrapper>) |> Expr.Cast<FileWrapper>
Some(BodyMultipart <@ Seq.singleton (MultipartItem(name, (%wrapper).fileName, (%wrapper).data)) @>)
| None ->
match param.In with
| Body ->
Some (BodyObject (Expr.Coerce (exp, typeof<obj>) |> Expr.Cast<obj>))
| _ ->
Some (BodyForm <@ (seq [name, (%var() : string)]) @>)
| _ -> failwith ("Can only contain one payload")

let addQuery quer name (exp : Expr) =
let listValues = corceQueryString name exp
<@@ List.append
(%%quer : (string*string) list)
(%listValues : (string*string) list) @@>
let listValues = coerceQueryString name exp
<@ List.append %quer %listValues @>

let addHeader head name (exp : Expr) =
<@@ Array.append (%%head : (string*string) []) ([|name, (%%exp : string)|]) @@>
let addHeader head name (exp : Expr<string>) =
<@ Array.append %head ([| name, %exp |]) @>

// Partitions arguments based on their locations
let (path, payload, queries, heads) =
let (path, payload, queries: Expr<(string*string) list>, heads) =
let mPath = op.Path
parameters
|> List.fold (
Expand All @@ -137,54 +162,68 @@ type OperationCompiler (schema:SwaggerObject, defCompiler:DefinitionCompiler) =
let value = coerceString param.Type param.CollectionFormat exp
match param.In with
| Path -> (replacePathTemplate path name value, load, quer, head)
| FormData
| Body -> (path, addPayload load param exp, quer, head)
| FormData | Body -> (path, addPayload load param exp, quer, head)
| Query -> (path, load, addQuery quer name exp, head)
| Header -> (path, load, quer, addHeader head name value)
)
(<@@ mPath @@>, None, <@@ ([] : (string*string) list) @@>, headers)
(<@ mPath @>, None, <@ ([] : (string*string) list) @>, headers)


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

let customizeHttpRequest =
<@@ let customizeCall = (%%customizeHttpRequest : Net.HttpWebRequest -> Net.HttpWebRequest)
fun (request:Net.HttpWebRequest) ->
<@ let customizeCall = %%customizeHttpRequest
fun (request:Net.HttpWebRequest) ->
if restCall = "Post"
then request.ContentLength <- 0L
customizeCall request @@>
customizeCall request @>

// Make HTTP call
let result =
match payload with
| None ->
<@@ Http.RequestString(%%address,
<@ Http.RequestStream(%address,
httpMethod = restCall,
headers = %heads,
query = %queries,
customizeHttpRequest = %customizeHttpRequest) @>
| Some (BodyMultipart parts) ->
<@ Http.RequestStream(%address,
httpMethod = restCall,
headers = (%%heads : array<string*string>),
query = (%%queries : (string * string) list),
customizeHttpRequest = (%%customizeHttpRequest : Net.HttpWebRequest -> Net.HttpWebRequest)) @@>
| Some (FormData, b) ->
<@@ Http.RequestString(%%address,
body = HttpRequestBody.Multipart(string (Guid.NewGuid()), %parts),
headers = %heads,
query = %queries,
customizeHttpRequest = %customizeHttpRequest) @>
| Some (BodyForm formData) ->
<@ Http.RequestStream(%address,
httpMethod = restCall,
headers = (%%heads : array<string*string>),
body = HttpRequestBody.FormValues (%%b : seq<string * string>),
query = (%%queries : (string * string) list),
customizeHttpRequest = (%%customizeHttpRequest : Net.HttpWebRequest -> Net.HttpWebRequest)) @@>
| Some (Body, b) ->
<@@ let body = SwaggerProvider.Internal.RuntimeHelpers.serialize (%%b : obj)
Http.RequestString(%%address,
headers = %heads,
body = HttpRequestBody.FormValues %formData,
query = %queries,
customizeHttpRequest = %customizeHttpRequest) @>
| Some (BodyObject b) ->
<@ let body = SwaggerProvider.Internal.RuntimeHelpers.serialize %b
Http.RequestStream(%address,
httpMethod = restCall,
headers = (%%heads : array<string*string>),
headers = %heads,
body = HttpRequestBody.TextRequest body,
query = (%%queries : (string * string) list),
customizeHttpRequest = (%%customizeHttpRequest : Net.HttpWebRequest -> Net.HttpWebRequest))
@@>
| Some (x, _) -> failwith ("Payload should not be able to have type: " + string x)

query = %queries,
customizeHttpRequest = %customizeHttpRequest) @>
// Return deserialized object
let value = <@@ SwaggerProvider.Internal.RuntimeHelpers.deserialize
(%%result : string) retTy @@>

let value =
<@@
let stream = (%result).ResponseStream
if isReturnFile
then box stream
else
let reader = new StreamReader(stream)
let body = reader.ReadToEnd()
let ret = box <| SwaggerProvider.Internal.RuntimeHelpers.deserialize body retTy
reader.Close()
stream.Close()
ret @@>
Expr.Coerce(value, retTy)

m
Expand Down
4 changes: 4 additions & 0 deletions tests/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Place your settings in this file to overwrite default and user settings.
{
fsharp
}
2 changes: 1 addition & 1 deletion tests/SwaggerProvider.ProviderTests/App.config
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>

<runtime><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<Paket>True</Paket>
Expand Down
10 changes: 10 additions & 0 deletions tests/SwaggerProvider.ProviderTests/Swagger.PetStore.Tests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
open SwaggerProvider
open FSharp.Data
open Expecto
open SwaggerProvider.Internal.Compilers
open System.IO
open System

type PetStore = SwaggerProvider<"http://petstore.swagger.io/v2/swagger.json", "Content-Type=application/json">
let store = PetStore()
Expand Down Expand Up @@ -51,4 +54,11 @@ let petStoreTests =
Expect.equal pet.Category pet2.Category "same Category"
Expect.equal pet.Status pet2.Status "same Status"
Expect.notEqual pet pet2 "different objects"

testCase "file Upload" <| fun _ ->
try
let result = store.UploadFile(6L, "thing", new FileWrapper("thing.jpg", new MemoryStream(Text.Encoding.UTF8.GetBytes("hello!"))))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FileWrapper is a leaky type, I'd like to not have to expose it. Thoughts?

printfn "%A" result
with
| exn -> ()
]
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ let api = WebAPI()
let shouldEqual expected actual =
Expect.equal actual expected "return value"


[<Tests>]
let returnControllersTests =
testList "All/Swashbuckle.ReturnControllers.Tests" [
Expand Down
10 changes: 4 additions & 6 deletions tests/Swashbuckle.OWIN.Server/App.config
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<runtime>

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">

<runtime><assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.Http" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="4.0.0.0" />
Expand All @@ -20,7 +19,7 @@
<assemblyIdentity name="FSharp.Core" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="4.4.1.0" />
</dependentAssembly>
<dependentAssembly>
<dependentAssembly>
<Paket>True</Paket>
<assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="3.1.0.0" />
Expand All @@ -40,5 +39,4 @@
<assemblyIdentity name="System.Web.Http" publicKeyToken="31bf3856ad364e35" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="5.2.3.0" />
</dependentAssembly>
</assemblyBinding></runtime>
</configuration>
</assemblyBinding></runtime></configuration>
Loading