Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upload file support for OpenApiProvider #203

Merged
merged 11 commits into from
Sep 11, 2022
1 change: 1 addition & 0 deletions docs/RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- BREAKING: `task` CE wrap all exceptions in `AggregateException` (with `OpenApiException` inside)
- Model enums as `string`, `int32` or `boolean` (Fixed [#186](https://github.com/fsprojects/SwaggerProvider/issues/186) )
- Add `Accept` header to all request (Fixed [#196](https://github.com/fsprojects/SwaggerProvider/issues/196))
- Supported requests with `octet-stream` body content [#203](https://github.com/fsprojects/SwaggerProvider/pull/203)
- Microsoft.OpenApi v1.4.1

#### 1.0.2 - Jul 10, 2022
Expand Down
13 changes: 6 additions & 7 deletions src/SwaggerProvider.DesignTime/Caching.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ module SwaggerProvider.Caching

open System
open System.Collections.Concurrent
open System.IO

// https://github.com/fsharp/FSharp.Data/blob/master/src/CommonRuntime/IO.fs

Expand Down Expand Up @@ -74,7 +73,7 @@ let internal logTime category (instance: string) =

let internal dummyDisposable =
{ new IDisposable with
member __.Dispose() = ()
member _.Dispose() = ()
}

let inline internal log(_: string) = ()
Expand Down Expand Up @@ -112,25 +111,25 @@ let createInMemoryCache(expiration: TimeSpan) =
}

{ new ICache<_, _> with
member __.Set(key, value) =
dict.[key] <- (value, DateTime.UtcNow)
member _.Set(key, value) =
dict[key] <- (value, DateTime.UtcNow)
invalidationFunction key |> Async.Start

member x.TryRetrieve(key, ?extendCacheExpiration) =
match dict.TryGetValue(key) with
| true, (value, timestamp) when DateTime.UtcNow - timestamp < expiration ->
if extendCacheExpiration = Some true then
dict.[key] <- (value, DateTime.UtcNow)
dict[key] <- (value, DateTime.UtcNow)

Some value
| _ -> None

member __.Remove(key) =
member _.Remove(key) =
match dict.TryRemove(key) with
| true, _ -> log $"Explicitly removed from cache: {key}"
| _ -> ()

member __.GetOrAdd(key, valueFactory) =
member _.GetOrAdd(key, valueFactory) =
let res, _ = dict.GetOrAdd(key, (fun k -> valueFactory(), DateTime.UtcNow))
invalidationFunction key |> Async.Start
res
Expand Down
17 changes: 1 addition & 16 deletions src/SwaggerProvider.DesignTime/Provider.SwaggerClient.fs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,8 @@ open SwaggerProvider.Internal
open SwaggerProvider.Internal.v2.Parser
open SwaggerProvider.Internal.v2.Compilers

//module Handlers =

// let logError event (ex: exn) =
// let ex =
// match ex with
// | :? TypeInitializationException as typInit -> typInit.InnerException
// | _ -> ex
// Logging.logf "[%s]\t%s: %s\n%s" event (ex.GetType().Name) ex.Message ex.StackTrace
// let logResolve kind (args: ResolveEventArgs) = Logging.logf "[%s]\t%s on behalf of %s" kind args.Name args.RequestingAssembly.FullName


/// The Swagger Type Provider.
[<TypeProvider>]
[<TypeProvider; Obsolete("Use OpenApiClientTypeProvider when possible, it supports v2 & v3 schema formats.")>]
type public SwaggerTypeProvider(cfg: TypeProviderConfig) as this =
inherit TypeProviderForNamespaces
(
Expand All @@ -30,10 +19,6 @@ type public SwaggerTypeProvider(cfg: TypeProviderConfig) as this =
addDefaultProbingLocation = true
)

// static do
// AppDomain.CurrentDomain.FirstChanceException.Add(fun args -> Handlers.logError "FirstChanceException" args.Exception)
// AppDomain.CurrentDomain.UnhandledException.Add(fun args -> Handlers.logError "UnhandledException" (args.ExceptionObject :?> exn))

let ns = "SwaggerProvider"
let asm = Assembly.GetExecutingAssembly()

Expand Down
8 changes: 4 additions & 4 deletions src/SwaggerProvider.DesignTime/Utils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ module SchemaReader =
|> Seq.choose(fun x ->
let pair = x.Split('=')

if (pair.Length = 2) then Some(pair.[0], pair.[1]) else None)
if (pair.Length = 2) then Some(pair[0], pair[1]) else None)

let request = new HttpRequestMessage(HttpMethod.Get, schemaPathRaw)

for (name, value) in headers do
for name, value in headers do
request.Headers.TryAddWithoutValidation(name, value) |> ignore
// using a custom handler means that we can set the default credentials.
use handler = new HttpClientHandler(UseDefaultCredentials = true)
Expand All @@ -44,7 +44,7 @@ module SchemaReader =

match res with
| Choice1Of2 x -> return x
| Choice2Of2(:? System.Net.WebException as wex) when not <| isNull wex.Response ->
| Choice2Of2(:? WebException as wex) when not <| isNull wex.Response ->
use stream = wex.Response.GetResponseStream()
use reader = new StreamReader(stream)
let err = reader.ReadToEnd()
Expand Down Expand Up @@ -75,5 +75,5 @@ type UniqueNameGenerator() =
newName
| true -> findUniq prefix (i + 1)

member __.MakeUnique methodName =
member _.MakeUnique methodName =
findUniq methodName 0
54 changes: 27 additions & 27 deletions src/SwaggerProvider.DesignTime/v2/DefinitionCompiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ type DefinitionPath =
if ind = definitionPath.Length then
ind - 1
elif
Char.IsLetterOrDigit definitionPath.[ind]
|| definitionPath.[ind] = nsSeparator
Char.IsLetterOrDigit definitionPath[ind]
|| definitionPath[ind] = nsSeparator
then
getCharInTypeName(ind + 1)
else
Expand Down Expand Up @@ -76,10 +76,10 @@ and NamespaceAbstraction(name: string) =
| _, value -> failwithf $"Cannot %s{opName} '%s{tyName}' because the slot is used by %A{value}"

/// Namespace name
member __.Name = name
member _.Name = name

/// Generate unique name and reserve it for the type
member __.ReserveUniqueName namePref nameSuffix = // TODO: Strange signature - think more
member _.ReserveUniqueName namePref nameSuffix = // TODO: Strange signature - think more
let rec findUniq prefix i =
let newName = sprintf "%s%s" prefix (if i = 0 then "" else i.ToString())

Expand All @@ -93,34 +93,34 @@ and NamespaceAbstraction(name: string) =
newName

/// Release previously reserved name
member __.ReleaseNameReservation tyName =
member _.ReleaseNameReservation tyName =
updateReservation "release the name" tyName (fun () -> providedTys.Remove(tyName) |> ignore)

/// Mark type name as named alias for basic type
member __.MarkTypeAsNameAlias tyName =
updateReservation "mark as Alias type" tyName (fun () -> providedTys.[tyName] <- NameAlias)
member _.MarkTypeAsNameAlias tyName =
updateReservation "mark as Alias type" tyName (fun () -> providedTys[tyName] <- NameAlias)

/// Associate ProvidedType with reserved type name
member __.RegisterType(tyName, ty) =
member _.RegisterType(tyName, ty) =
match providedTys.TryGetValue tyName with
| true, Reservation -> providedTys.[tyName] <- ProvidedType ty
| true, Namespace ns -> providedTys.[tyName] <- NestedType(ty, ns)
| true, Reservation -> providedTys[tyName] <- ProvidedType ty
| true, Namespace ns -> providedTys[tyName] <- NestedType(ty, ns)
| false, _ -> failwithf $"Cannot register the type '%s{tyName}' because name was not reserved"
| _, value -> failwithf $"Cannot register the type '%s{tyName}' because the slot is used by %A{value}"

/// Get or create sub-namespace
member __.GetOrCreateNamespace name =
member _.GetOrCreateNamespace name =
match providedTys.TryGetValue name with
| true, Namespace ns -> ns
| true, NestedType(_, ns) -> ns
| true, ProvidedType ty ->
let ns = NamespaceAbstraction(name)
providedTys.[name] <- NestedType(ty, ns)
providedTys[name] <- NestedType(ty, ns)
ns
| false, _
| true, Reservation ->
let ns = NamespaceAbstraction(name)
providedTys.[name] <- Namespace ns
providedTys[name] <- Namespace ns
ns
| true, value -> failwithf $"Name collision, cannot create namespace '%s{name}' because it used by '%A{value}'"

Expand All @@ -133,7 +133,7 @@ and NamespaceAbstraction(name: string) =
ns.Resolve { dPath with Namespace = tail }

/// Create Provided representation of Namespace
member __.GetProvidedTypes() =
member _.GetProvidedTypes() =
List.ofSeq providedTys
|> List.choose(fun kv ->
match kv.Value with
Expand Down Expand Up @@ -168,7 +168,7 @@ type DefinitionCompiler(schema: SwaggerObject, provideNullable) as this =
let propertyName = scope.MakeUnique <| nicePascalName propName

let providedField =
let fieldName = $"_%c{Char.ToLower propertyName.[0]}%s{propertyName.Substring(1)}"
let fieldName = $"_%c{Char.ToLower propertyName[0]}%s{propertyName.Substring(1)}"

ProvidedField(fieldName, ty)

Expand Down Expand Up @@ -239,7 +239,7 @@ type DefinitionCompiler(schema: SwaggerObject, provideNullable) as this =
let pTy =
compileSchemaObject ns (ns.ReserveUniqueName tyName (nicePascalName p.Name)) p.Type p.IsRequired ns.RegisterType

let (pField, pProp) = generateProperty p.Name pTy
let pField, pProp = generateProperty p.Name pTy

if not <| String.IsNullOrWhiteSpace p.Description then
pProp.AddXmlDoc p.Description
Expand Down Expand Up @@ -279,15 +279,15 @@ type DefinitionCompiler(schema: SwaggerObject, provideNullable) as this =
ctorParams,
invokeCode =
fun args ->
let (this, args) =
let this, args =
match args with
| x :: xs -> (x, xs)
| _ -> failwith "Wrong constructor arguments"

List.zip args fields
|> List.map(fun (arg, f) -> Expr.FieldSetUnchecked(this, f, arg))
|> List.rev
|> List.fold (fun a b -> Expr.Sequential(a, b)) (<@@ () @@>)
|> List.fold (fun a b -> Expr.Sequential(a, b)) <@@ () @@>
)

// Override `.ToString()`
Expand All @@ -299,9 +299,9 @@ type DefinitionCompiler(schema: SwaggerObject, provideNullable) as this =
isStatic = false,
invokeCode =
fun args ->
let this = args.[0]
let this = args[0]

let (pNames, pValues) =
let pNames, pValues =
Array.ofList members
|> Array.map(fun (pField, pProp) ->
let pValObj = Expr.FieldGet(this, pField)
Expand Down Expand Up @@ -329,15 +329,15 @@ type DefinitionCompiler(schema: SwaggerObject, provideNullable) as this =

let strs =
values
|> Array.mapi(fun i v -> String.Format("{0}={1}", pNames.[i], formatValue v))
|> Array.mapi(fun i v -> String.Format("{0}={1}", pNames[i], formatValue v))

String.Format("{{{0}}}", String.Join("; ", strs))
@@>
)

toStr.SetMethodAttrs(MethodAttributes.Public ||| MethodAttributes.Virtual)

let objToStr = (typeof<obj>).GetMethod("ToString", [||])
let objToStr = typeof<obj>.GetMethod ("ToString", [||])
ty.DefineMethodOverride(toStr, objToStr)
ty.AddMember <| toStr

Expand All @@ -362,10 +362,10 @@ type DefinitionCompiler(schema: SwaggerObject, provideNullable) as this =
| String -> typeof<string>
| Date
| DateTime -> typeof<DateTime>
| File -> typeof<byte>.MakeArrayType (1)
| File -> typeof<byte>.MakeArrayType 1
| Enum(_, "string") -> typeof<string>
| Enum(_, "boolean") -> typeof<bool>
| Enum(_, _) -> typeof<int32>
| Enum _ -> typeof<int32>
| Array eTy ->
(compileSchemaObject ns (ns.ReserveUniqueName tyName "Item") eTy true ns.RegisterType)
.MakeArrayType(1)
Expand Down Expand Up @@ -399,14 +399,14 @@ type DefinitionCompiler(schema: SwaggerObject, provideNullable) as this =
|> Seq.iter(fun (name, _) -> compileDefinition name |> ignore)

/// Namespace that represent provided type space
member __.Namespace = nsRoot
member _.Namespace = nsRoot

/// Method that allow OperationCompiler to resolve object reference, compile basic and anonymous types.
member __.CompileTy opName tyUseSuffix ty required =
member _.CompileTy opName tyUseSuffix ty required =
compileSchemaObject nsOps (nsOps.ReserveUniqueName opName tyUseSuffix) ty required nsOps.RegisterType

/// Default value for optional parameters
member __.GetDefaultValue _ =
member _.GetDefaultValue _ =
// This method is only used for not required types
// Reference types, Option<T> and Nullable<T>
null
22 changes: 10 additions & 12 deletions src/SwaggerProvider.DesignTime/v2/OperationCompiler.fs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ open SwaggerProvider.Internal.v2.Parser.Schema
open Swagger.Internal

open System
open System.Collections.Generic
open System.Net.Http
open System.Text.Json
open System.Text.RegularExpressions
Expand All @@ -15,7 +14,6 @@ open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.ExprShape
open SwaggerProvider.Internal
open Swagger
open Swagger.Internal

/// Object for compiling operations.
type OperationCompiler(schema: SwaggerObject, defCompiler: DefinitionCompiler, ignoreControllerPrefix, ignoreOperationId, asAsync: bool) =
Expand All @@ -39,7 +37,7 @@ type OperationCompiler(schema: SwaggerObject, defCompiler: DefinitionCompiler, i
Array.append required optional
|> Array.fold
(fun (names, parameters) current ->
let (names, paramName) = uniqueParamName names current
let names, paramName = uniqueParamName names current

let paramType =
defCompiler.CompileTy methodName paramName current.Type current.Required
Expand Down Expand Up @@ -77,7 +75,7 @@ type OperationCompiler(schema: SwaggerObject, defCompiler: DefinitionCompiler, i
typedefof<Async<unit>>
else
typedefof<System.Threading.Tasks.Task<unit>>),
[ defaultArg retTy (typeof<unit>) ]
[ defaultArg retTy typeof<unit> ]
)

let m =
Expand All @@ -88,7 +86,7 @@ type OperationCompiler(schema: SwaggerObject, defCompiler: DefinitionCompiler, i
invokeCode =
fun args ->
let this =
Expr.Coerce(args.[0], typeof<ProvidedApiClientBase>)
Expr.Coerce(args[0], typeof<ProvidedApiClientBase>)
|> Expr.Cast<ProvidedApiClientBase>

let httpMethod = op.Type.ToString()
Expand Down Expand Up @@ -158,7 +156,7 @@ type OperationCompiler(schema: SwaggerObject, defCompiler: DefinitionCompiler, i
<@ Array.append %heads [| name, %value |] @>

// Partitions arguments based on their locations
let (path, payload, queries, heads) =
let path, payload, queries, heads =
let mPath = op.Path

parameters
Expand Down Expand Up @@ -260,25 +258,25 @@ type OperationCompiler(schema: SwaggerObject, defCompiler: DefinitionCompiler, i

static member GetMethodNameCandidate (op: OperationObject) skipLength ignoreOperationId =
if ignoreOperationId || String.IsNullOrWhiteSpace(op.OperationId) then
let (_, pathParts) =
let _, pathParts =
(op.Path.Split([| '/' |], StringSplitOptions.RemoveEmptyEntries), (false, []))
||> Array.foldBack(fun x (nextIsArg, pathParts) ->
if x.StartsWith("{") then
(true, pathParts)
else
(false, (if nextIsArg then singularize x else x) :: pathParts))

String.Join("_", (op.Type.ToString()) :: pathParts)
String.Join("_", op.Type.ToString() :: pathParts)
else
op.OperationId.Substring(skipLength)
|> nicePascalName

member __.CompileProvidedClients(ns: NamespaceAbstraction) =
member _.CompileProvidedClients(ns: NamespaceAbstraction) =
let defaultHost =
let protocol =
match schema.Schemes with
| [||] -> "http" // Should use the scheme used to access the Swagger definition itself.
| array -> array.[0]
| array -> array[0]

$"%s{protocol}://%s{schema.Host}"

Expand Down Expand Up @@ -334,7 +332,7 @@ type OperationCompiler(schema: SwaggerObject, defCompiler: DefinitionCompiler, i
)
ProvidedConstructor(
[ ProvidedParameter("options", typeof<JsonSerializerOptions>) ],
invokeCode = (fun args -> <@@ () @@>),
invokeCode = (fun _ -> <@@ () @@>),
BaseConstructorCall =
fun args ->
let httpClient = <@ RuntimeHelpers.getDefaultHttpClient defaultHost @> :> Expr
Expand All @@ -348,7 +346,7 @@ type OperationCompiler(schema: SwaggerObject, defCompiler: DefinitionCompiler, i
)
ProvidedConstructor(
[],
invokeCode = (fun args -> <@@ () @@>),
invokeCode = (fun _ -> <@@ () @@>),
BaseConstructorCall =
fun args ->
let httpClient = <@ RuntimeHelpers.getDefaultHttpClient defaultHost @> :> Expr
Expand Down
Loading