Created
June 15, 2021 15:05
-
-
Save bessgeor/a06052d94cc7d324e60fd0cff9cfe404 to your computer and use it in GitHub Desktop.
Example of generated server code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[<RequireQualifiedAccess>] | |
module SpecwithparametersandrequestbodyAPI | |
open System.ComponentModel.DataAnnotations | |
open FSharp.Control.Tasks.V2.ContextInsensitive | |
open Giraffe | |
open System.Threading.Tasks | |
open Microsoft.AspNetCore.Http | |
open Microsoft.Extensions.DependencyInjection | |
///<summary>replaces any generated validation rules for type</summary> | |
[<Interface>] | |
type 'model IGiraffeValidator = | |
abstract Validate: ('model * ValidationContext) -> ValidationResult array | |
///<summary>replaces any generated validation rules for type</summary> | |
and [<Interface>] 'model IGiraffeAdditionalValidator = | |
abstract Validate: ('model * ValidationContext) -> ValidationResult array | |
[<CLIMutable>] | |
type forDefaultsTesting = | |
{ optionalArrayWithDefaultItems: int array option | |
[<Required>] | |
requiredArrayWithDefaultItems: int array | |
[<Required>] | |
apiKey: string | |
[<Required>] | |
apiVersionNumber: string | |
[<Required>] | |
apiUrl: System.Uri | |
[<Required>] | |
apiCount: int64 | |
[<Required>] | |
apiAvg: int | |
[<Required>] | |
isInternal: bool | |
[<Required>] | |
start: System.DateTime | |
[<Required>] | |
someDateTime: System.DateTimeOffset | |
[<Required>] | |
pi: double | |
[<Required>] | |
someUid: System.Guid } | |
and [<CLIMutable>] dataSetListInput = | |
{ total: int option | |
[<Required>] | |
defaultsTest: forDefaultsTesting } | |
and [<CLIMutable>] dataSetListOutput = | |
{ [<Required>] | |
pathParam: string | |
queryParam: int option | |
total: int option | |
[<Required>] | |
defaultsTest: forDefaultsTesting } | |
and [<CLIMutable>] forDefaultsTestingForBinding = | |
{ optionalArrayWithDefaultItems: int option array option | |
[<Required>] | |
requiredArrayWithDefaultItems: int option array | |
apiKey: string option | |
apiVersionNumber: string option | |
apiUrl: System.Uri option | |
apiCount: int64 option | |
apiAvg: int option | |
isInternal: bool option | |
start: System.DateTime option | |
someDateTime: System.DateTimeOffset option | |
pi: double option | |
someUid: System.Guid option } | |
and [<CLIMutable>] dataSetListInputForBinding = | |
{ total: int option | |
[<Required>] | |
defaultsTest: forDefaultsTestingForBinding } | |
and [<CLIMutable>] PostPostIdPath = | |
{ [<Required>] | |
param: string } | |
and [<CLIMutable>] PostPostIdQuery = { param: int option } | |
and [<CLIMutable>] PostIdPostInput = | |
{ [<Required>] | |
paramFromPath: string | |
paramFromQuery: int option } | |
///<summary> | |
///Input binding error | |
///</summary> | |
and ArgumentError = | |
///<summary> | |
///Bound argument is not valid | |
///</summary> | |
| ArgumentValidationError of ValidationResult array | |
///<summary> | |
///Giraffe returned a Result.Error from tryBindXXX | |
///</summary> | |
| GiraffeBindingError of string | |
///<summary> | |
///Exception occurred during IFormatter bind | |
///</summary> | |
| FormatterBindingException of exn | |
///<summary> | |
///Multiple errors occurred in one location | |
///</summary> | |
| CombinedArgumentErrors of ArgumentError array | |
///<summary> | |
///Location argument error | |
///</summary> | |
and ArgumentLocationedError = | |
///<summary> | |
///Body error | |
///</summary> | |
| BodyBindingError of ArgumentError | |
///<summary> | |
///Query error | |
///</summary> | |
| QueryBindingError of ArgumentError | |
///<summary> | |
///Path error | |
///</summary> | |
| PathBindingError of ArgumentError | |
///<summary> | |
///Multiple locations errors | |
///</summary> | |
| CombinedArgumentLocationError of ArgumentLocationedError array | |
let rec argErrorToString level value = | |
let sep = System.String(' ', level * 2) | |
match value with | |
| GiraffeBindingError err -> sprintf "%sGiraffe binding error: %s" sep err | |
| FormatterBindingException err -> err.Message | |
| CombinedArgumentErrors err -> | |
sprintf "%sMultiple errors:\n%s" sep (String.concat "\n" (Seq.map (argErrorToString (level + 1)) err)) | |
| ArgumentValidationError err -> | |
let errStrings = | |
Option.ofObj err | |
|> Option.defaultValue Array.empty | |
|> Array.map (fun v -> | |
let path = | |
Option.ofObj v.MemberNames |> Option.map (String.concat ".") | |
let error = Option.ofObj v.ErrorMessage | |
Option.map2 (sprintf "%s (at %s)") error path | |
|> Option.orElse error | |
|> Option.orElse path | |
|> Option.defaultValue "unknown validation error") | |
if errStrings |> Array.length = 0 then | |
sprintf "%sUnknown validation error" sep | |
else if errStrings |> Array.length = 1 then | |
errStrings | |
|> Array.head | |
|> sprintf "%sValidation error: %s" sep | |
else | |
let sepInner = sprintf "\n%s " sep | |
errStrings | |
|> String.concat sepInner | |
|> sprintf "%sValidation errors:%s%s" sep sepInner | |
let rec argLocationErrorToString level value = | |
let sep = System.String(' ', level * 2) | |
match value with | |
| BodyBindingError body -> sprintf "%sBody binding error:\n%s" sep (argErrorToString (level + 1) body) | |
| PathBindingError path -> sprintf "%sPath binding error:\n%s" sep (argErrorToString (level + 1) path) | |
| QueryBindingError query -> sprintf "%sQuery binding error:\n%s" sep (argErrorToString (level + 1) query) | |
| CombinedArgumentLocationError err -> | |
sprintf | |
"%sMultiple binding errors:\n%s" | |
sep | |
(String.concat "\n\n" (Seq.map (argLocationErrorToString (level + 1)) err)) | |
let tryExtractError value = | |
match value with | |
| Ok _ -> None | |
| Error err -> Some err | |
let isObjectValid boxed errors validationContext = | |
Validator.TryValidateObject(boxed, validationContext, errors, true) | |
let isValueValid validationAttributes boxed errors validationContext = | |
Validator.TryValidateValue(boxed, validationContext, errors, validationAttributes) | |
let validateInner isValid (ctx: HttpContext) validationContext (value: 'model) = | |
let customValidator = | |
ctx.RequestServices.GetService<IGiraffeValidator<'model>>() | |
let errs = | |
if System.Object.ReferenceEquals(customValidator, null) then | |
let errs = System.Collections.Generic.List() | |
if isValid value errs validationContext then Array.empty else errs |> Seq.toArray | |
else | |
customValidator.Validate(value, validationContext) | |
let errs = | |
let customAugmentingValidator = | |
ctx.RequestServices.GetService<IGiraffeAdditionalValidator<'model>>() | |
if System.Object.ReferenceEquals(customAugmentingValidator, null) then | |
errs | |
else | |
customAugmentingValidator.Validate(value, validationContext) | |
|> Array.append errs | |
errs | |
let withValue (validationContext: ValidationContext) value = | |
let ctx = | |
ValidationContext(value, validationContext.Items) | |
ctx.InitializeServiceProvider(fun t -> validationContext.GetService t) | |
ctx.MemberName <- null | |
ctx | |
let withMemberAndValue (validationContext: ValidationContext) name value = | |
let ctx = withValue validationContext value | |
ctx.MemberName <- name | |
ctx | |
let rec validate ctx (validationContext: ValidationContext) = | |
let instance = validationContext.ObjectInstance | |
[| match instance with | |
| :? dataSetListInput as value -> | |
yield! validateInner isObjectValid ctx validationContext value | |
let validationContext = | |
withMemberAndValue validationContext "defaultsTest" value.defaultsTest | |
yield! validate ctx validationContext | |
| :? forDefaultsTesting as value -> yield! validateInner isObjectValid ctx validationContext value | |
| :? PostPostIdPath as value -> yield! validateInner isObjectValid ctx validationContext value | |
| :? PostPostIdQuery as value -> yield! validateInner isObjectValid ctx validationContext value | |
| v -> failwithf "Unknown type came to validation: %A" (v.GetType()) |] | |
let bindValidation (ctx: HttpContext) location (value: 'model) = | |
let validationContext = | |
ValidationContext(value, ctx.RequestServices, ctx.Items) | |
let errs = validate ctx validationContext | |
if (errs |> Array.length) = 0 then | |
Ok value | |
else | |
errs | |
|> ArgumentValidationError | |
|> location | |
|> Error | |
[<AbstractClass>] | |
type Service() = | |
abstract PostId: PostPostIdPath -> HttpHandler | |
override this.PostId pathArgs = | |
fun next ctx -> | |
task { | |
let queryArgs = | |
ctx.TryBindQueryString<PostPostIdQuery> System.Globalization.CultureInfo.InvariantCulture | |
|> Result.mapError (GiraffeBindingError >> QueryBindingError) | |
|> Result.bind (bindValidation ctx QueryBindingError) | |
let pathArgs = | |
Result<PostPostIdPath, ArgumentLocationedError>.Ok pathArgs | |
|> Result.bind (bindValidation ctx PathBindingError) | |
let combinedArgs = | |
pathArgs | |
|> Result.bind (fun pathArgs -> | |
queryArgs | |
|> Result.map (fun queryArgs -> | |
let v: PostIdPostInput = | |
{ paramFromPath = pathArgs.param | |
paramFromQuery = queryArgs.param } | |
v)) | |
let! bodyArgs = | |
task { | |
try | |
let! bodyArgs = ctx.BindJsonAsync<dataSetListInputForBinding>() | |
return Ok bodyArgs | |
|> Result.map (fun (src: dataSetListInputForBinding) -> | |
let v: dataSetListInput = | |
{ total = src.total | |
defaultsTest = | |
{ optionalArrayWithDefaultItems = | |
src.defaultsTest.optionalArrayWithDefaultItems | |
|> Option.map (fun (src: int option array) -> | |
src | |
|> Array.map (fun (src: int option) -> | |
src |> Option.defaultValue (42))) | |
requiredArrayWithDefaultItems = | |
src.defaultsTest.requiredArrayWithDefaultItems | |
|> Array.map (fun (src: int option) -> src |> Option.defaultValue (48)) | |
apiKey = | |
src.defaultsTest.apiKey | |
|> Option.defaultValue ("pa$$word") | |
apiVersionNumber = | |
src.defaultsTest.apiVersionNumber | |
|> Option.defaultValue ("1") | |
apiUrl = | |
src.defaultsTest.apiUrl | |
|> Option.defaultValue (System.Uri "http://localhost:8080/api") | |
apiCount = | |
src.defaultsTest.apiCount | |
|> Option.defaultValue (123456789123456L) | |
apiAvg = | |
src.defaultsTest.apiAvg | |
|> Option.defaultValue (1234567890) | |
isInternal = | |
src.defaultsTest.isInternal | |
|> Option.defaultValue (true) | |
start = | |
src.defaultsTest.start | |
|> Option.defaultValue (System.DateTime(2020, 10, 8)) | |
someDateTime = | |
src.defaultsTest.someDateTime | |
|> Option.defaultValue | |
(System.DateTimeOffset | |
((637377153000000000L), | |
System.TimeSpan.FromTicks((108000000000L)))) | |
pi = src.defaultsTest.pi |> Option.defaultValue (3.14) | |
someUid = | |
src.defaultsTest.someUid | |
|> Option.defaultValue | |
(System.Guid.ParseExact("8282cbfd-f323-4b7d-bcc0-28f127c2b365", "D")) } } | |
v) | |
|> Result.bind (bindValidation ctx BodyBindingError) | |
with e -> | |
return FormatterBindingException e | |
|> BodyBindingError | |
|> Error | |
} | |
let args = | |
combinedArgs | |
|> Result.bind (fun combinedArgs -> | |
bodyArgs | |
|> Result.map (fun bodyArgs -> (combinedArgs, bodyArgs))) | |
let args = | |
match args with | |
| Ok v -> Ok v | |
| Error e -> | |
let errs = | |
[ tryExtractError pathArgs | |
tryExtractError queryArgs | |
tryExtractError bodyArgs ] | |
|> Seq.choose id | |
|> Seq.toArray | |
(if errs.Length > 1 then CombinedArgumentLocationError errs else Array.head errs) | |
|> Error | |
let! logicOutput = | |
match args with | |
| Ok (combinedArgs, bodyArgs) -> this.PostIdInput(combinedArgs, bodyArgs, ctx) | |
| Error e -> this.PostIdInputError(e, ctx) | |
return! this.PostIdOutput logicOutput next ctx | |
} | |
abstract PostIdInput: (PostIdPostInput * dataSetListInput * HttpContext) -> Task<dataSetListOutput> | |
abstract PostIdInputError: (ArgumentLocationedError * HttpContext) -> Task<dataSetListOutput> | |
override this.PostIdInputError t = | |
let (err, http) = t | |
let err = argLocationErrorToString 0 err | |
Task.FromException<dataSetListOutput>(exn err) | |
abstract PostIdOutput: dataSetListOutput -> HttpHandler | |
override this.PostIdOutput input = json input | |
let webApp: HttpHandler = | |
fun next ctx -> | |
task { | |
let service = ctx.GetService<Service>() | |
return! (POST >=> routeBind "/id/{param}" service.PostId) next ctx | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment