From 5e385f6bee752649adbfbcc100467e85c17c34fe Mon Sep 17 00:00:00 2001 From: Alfonso Garcia-Caro Date: Fri, 8 Jun 2018 13:58:37 +0200 Subject: [PATCH] Bump version to 2.0.0-alpha-016 --- .vscode/launch.json | 2 +- src/dotnet/Fable.Compiler/CLI/CLI.Util.fs | 2 +- .../Fable.Compiler/Fable.Compiler.fsproj | 2 +- src/dotnet/Fable.Compiler/RELEASE_NOTES.md | 2 +- .../Transforms/FableTransforms.fs | 14 +- src/js/fable-core/Util.ts | 53 +- src/tools/QuickTest.fsproj | 1 + tests/Main/ApplicativeTests.fs | 16 + tests/Main/Fable.Tests.fsproj | 1 + tests/Main/Util/Thoth.Json.Decode.fs | 658 ++++++++++++++++++ 10 files changed, 720 insertions(+), 31 deletions(-) create mode 100644 tests/Main/Util/Thoth.Json.Decode.fs diff --git a/.vscode/launch.json b/.vscode/launch.json index 45a3ef9179..b6404a0883 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,7 @@ "type": "coreclr", "request": "launch", "preLaunchTask": "build", - "program": "${workspaceRoot}/src/dotnet/Fable.Compiler/bin/Debug/netcoreapp2.0/Fable.Compiler.dll", + "program": "${workspaceRoot}/src/dotnet/Fable.Compiler/bin/Debug/netcoreapp2.0/dotnet-fable.dll", "args": ["yarn-splitter", "--fable-core", "../../../build/fable-core"], "cwd": "${workspaceRoot}/src/tools/", "stopAtEntry": true, diff --git a/src/dotnet/Fable.Compiler/CLI/CLI.Util.fs b/src/dotnet/Fable.Compiler/CLI/CLI.Util.fs index c6dc2a0a84..d08bdb92d8 100644 --- a/src/dotnet/Fable.Compiler/CLI/CLI.Util.fs +++ b/src/dotnet/Fable.Compiler/CLI/CLI.Util.fs @@ -2,7 +2,7 @@ namespace Fable.CLI module Literals = - let [] VERSION = "2.0.0-alpha-015" + let [] VERSION = "2.0.0-alpha-016" let [] DEFAULT_PORT = 61225 let [] FORCE = "force:" diff --git a/src/dotnet/Fable.Compiler/Fable.Compiler.fsproj b/src/dotnet/Fable.Compiler/Fable.Compiler.fsproj index 889bce63d3..180b04fe1b 100644 --- a/src/dotnet/Fable.Compiler/Fable.Compiler.fsproj +++ b/src/dotnet/Fable.Compiler/Fable.Compiler.fsproj @@ -3,7 +3,7 @@ Fable: F# to JS Compiler 2.0.0 - 2.0.0-alpha-015 + 2.0.0-alpha-016 Exe DotnetCliTool dotnet-fable diff --git a/src/dotnet/Fable.Compiler/RELEASE_NOTES.md b/src/dotnet/Fable.Compiler/RELEASE_NOTES.md index 395da7216d..c5dfdb1933 100644 --- a/src/dotnet/Fable.Compiler/RELEASE_NOTES.md +++ b/src/dotnet/Fable.Compiler/RELEASE_NOTES.md @@ -1,4 +1,4 @@ -### 2.0.0-alpha-015 +### 2.0.0-alpha-016 * Fable 2 alpha diff --git a/src/dotnet/Fable.Compiler/Transforms/FableTransforms.fs b/src/dotnet/Fable.Compiler/Transforms/FableTransforms.fs index b55f43008d..e19eff2e29 100644 --- a/src/dotnet/Fable.Compiler/Transforms/FableTransforms.fs +++ b/src/dotnet/Fable.Compiler/Transforms/FableTransforms.fs @@ -8,7 +8,7 @@ open Microsoft.FSharp.Compiler.SourceCodeServices let visit f e = match e with | IdentExpr _ | Debugger _ -> e - | Import(e1, e2, kind, t, r) -> Import(f e1, f e2, kind, t, r) + | Import(e1, e2, kind, t, r) -> Import(f e1, f e2, kind, t, r) | Value kind -> match kind with | TypeInfo _ | This _ | Super _ | Null _ | UnitConstant @@ -319,6 +319,8 @@ module private Transforms = | FunctionType(LambdaType argType, returnType) | Option(FunctionType(LambdaType argType, returnType)) -> uncurryLambdaTypeInner [argType] returnType + | FunctionType(DelegateType argTypes, returnType) -> + argTypes, returnType | returnType -> [], returnType let replaceIdentType replacements (id: Ident) = @@ -475,11 +477,11 @@ module private Transforms = Operation(Emit(macro, Some info), t, r) | e -> e - let rec uncurryApplications e = + let rec uncurryApplications (com: ICompiler) e = match e with | NestedApply(applied, args, t, r) -> - let applied = visitFromOutsideIn uncurryApplications applied - let args = args |> List.map (visitFromOutsideIn uncurryApplications) + let applied = visitFromOutsideIn (uncurryApplications com) applied + let args = args |> List.map (visitFromOutsideIn (uncurryApplications com)) match applied.Type with | FunctionType(DelegateType argTypes, _) -> if List.sameLength argTypes args then @@ -519,9 +521,9 @@ let optimizations = // Then apply uncurry optimizations fun com e -> visitFromInsideOut (uncurryReceivedArgs com) e fun com e -> visitFromInsideOut (uncurryRecordFields com) e - fun com e -> visitFromInsideOut (uncurrySendingArgs com) e fun com e -> visitFromInsideOut (uncurryInnerFunctions com) e - fun _ e -> visitFromOutsideIn uncurryApplications e + fun com e -> visitFromOutsideIn (uncurryApplications com) e + fun com e -> visitFromInsideOut (uncurrySendingArgs com) e // Don't traverse the expression for the unwrap function optimization unwrapFunctions ] diff --git a/src/js/fable-core/Util.ts b/src/js/fable-core/Util.ts index a825b2c273..e9098c7b1d 100644 --- a/src/js/fable-core/Util.ts +++ b/src/js/fable-core/Util.ts @@ -442,28 +442,39 @@ export interface ICurried { /* tslint:disable */ export function uncurry(arity: number, f: Function) { /* tslint:enable */ - // f may be a function option with None value - if (f == null) { - return null; + if (f == null) { return null; } + + // return (...args) => { + // // In some cases there may be more arguments applied than necessary + // // (e.g. index when mapping an array), discard them + // args = args.slice(0, arity); + // let res = f; + // while (args.length > 0) { + // const curArgs = args.splice(0,1); + // res = res.apply(null, curArgs); + // } + // return res; + // }; + switch (arity) { + case 2: + return (a1: any, a2: any) => f(a1)(a2); + case 3: + return (a1: any, a2: any, a3: any) => f(a1)(a2)(a3); + case 4: + return (a1: any, a2: any, a3: any, a4: any) => f(a1)(a2)(a3)(a4); + case 5: + return (a1: any, a2: any, a3: any, a4: any, a5: any) => f(a1)(a2)(a3)(a4)(a5); + case 6: + return (a1: any, a2: any, a3: any, a4: any, a5: any, a6: any) => f(a1)(a2)(a3)(a4)(a5)(a6); + case 7: + return (a1: any, a2: any, a3: any, a4: any, a5: any, a6: any, a7: any) => f(a1)(a2)(a3)(a4)(a5)(a6)(a7); + case 8: + return (a1: any, a2: any, a3: any, a4: any, a5: any, a6: any, a7: any, a8: any) => + f(a1)(a2)(a3)(a4)(a5)(a6)(a7)(a8); + default: + throw new Error("Uncurrying to more than 8-arity is not supported: " + arity); } - - const wrap: ICurried = (...args: any[]) => { - // In some cases there may be more arguments applied than necessary - // (e.g. index when mapping an array), discard them - let res: any = f; - for (let i = 0; i < arity; i++) { - const accArgs = [args[i]]; - const partialArity = Math.max(f.length, 1); - while (accArgs.length < partialArity) { - accArgs.push(args[++i]); - } - res = res.apply(null, accArgs); - } - return res; - }; - wrap.curried = true; - return wrap; } /* tslint:disable */ @@ -522,7 +533,7 @@ export function partialApply(arity: number, f: ICurried, args: any[]): any { return (a1: any) => (a2: any) => (a3: any) => (a4: any) => (a5: any) => (a6: any) => (a7: any) => (a8: any) => f.apply(null, args.concat([a1, a2, a3, a4, a5, a6, a7, a8])); default: - throw new Error("Partially applying to get a function with more than 8-arity is not supported: " + arity); + throw new Error("Partially applying to more than 8-arity is not supported: " + arity); } } } diff --git a/src/tools/QuickTest.fsproj b/src/tools/QuickTest.fsproj index 4aa7cdf372..cb675a3963 100644 --- a/src/tools/QuickTest.fsproj +++ b/src/tools/QuickTest.fsproj @@ -7,6 +7,7 @@ + diff --git a/tests/Main/ApplicativeTests.fs b/tests/Main/ApplicativeTests.fs index 0a4d67b327..cd6e46479c 100644 --- a/tests/Main/ApplicativeTests.fs +++ b/tests/Main/ApplicativeTests.fs @@ -704,6 +704,8 @@ module Results = let sum = add3 Ok 1 <*> Ok 2 <*> Ok 3 equal (Ok 6) sum +open Thoth.Json.Decode + let tests7 = [ testCase "SRTP with ActivePattern works" <| fun () -> (lengthWrapper []) |> equal 0 @@ -810,6 +812,20 @@ let tests7 = [ [1,2; 3,4; 5,6] |> List.map (sum 10) List.sum li |> equal 51 + + testCase "Composing methods returning 2-arity lambdas works" <| fun _ -> + let infoHelp version = + match version with + | 4 -> succeed 1 + | 3 -> succeed 1 + | _ -> fail <| "Trying to decode info, but version " + (version.ToString()) + "is not supported" + + let info : Decoder = + field "version" int + |> andThen infoHelp + + decodeString info """{ "version": 3, "data": 2 }""" + |> equal (FSharp.Core.Ok 1) ] let tests = diff --git a/tests/Main/Fable.Tests.fsproj b/tests/Main/Fable.Tests.fsproj index c66f5e2685..cad4e7ed47 100644 --- a/tests/Main/Fable.Tests.fsproj +++ b/tests/Main/Fable.Tests.fsproj @@ -14,6 +14,7 @@ + diff --git a/tests/Main/Util/Thoth.Json.Decode.fs b/tests/Main/Util/Thoth.Json.Decode.fs new file mode 100644 index 0000000000..d21d5668d0 --- /dev/null +++ b/tests/Main/Util/Thoth.Json.Decode.fs @@ -0,0 +1,658 @@ +module Thoth.Json.Decode + +open Fable.Core +open Fable.Core.JsInterop +open Fable.Import + +module Helpers = + [] + let jsTypeof (_ : obj) : string = jsNative + + let inline isString (o: obj) : bool = o :? string + + let inline isBoolean (o: obj) : bool = o :? bool + + let inline isNumber (o: obj) : bool = jsTypeof o = "number" + + let inline isArray (o: obj) : bool = JS.Array.isArray(o) + + let inline isObject (o: obj) : bool = not (isNull o) && jsTypeof o = "object" + + let inline isNaN (o: obj) : bool = JS.Number.isNaN(!!o) + + [] + let isValidIntRange (_: obj) : bool = jsNative + + [] + let isIntFinite (_: obj) : bool = jsNative + + [] + let isDefined (_: obj) : bool = jsNative + + [] + let anyToString (_: obj) : string= jsNative + + let inline isFunction (o: obj) : bool = jsTypeof o = "function" + + let inline objectKeys (o: obj) : string seq = upcast JS.Object.keys(o) + +type ErrorReason = + | BadPrimitive of string * obj + | BadPrimitiveExtra of string * obj * string + | BadField of string * obj + | BadPath of string * obj * string + | TooSmallArray of string * obj + | FailMessage of string + | BadOneOf of string list + +type DecoderError = string * ErrorReason + +type Decoder<'T> = string -> obj -> Result<'T, DecoderError> + +let private genericMsg msg value newLine = + try + "Expecting " + + msg + + " but instead got:" + + (if newLine then "\n" else " ") + + (Helpers.anyToString value) + with + | _ -> + "Expecting " + + msg + + " but decoder failed. Couldn't report given value due to circular structure." + + (if newLine then "\n" else " ") + +let private errorToString (path : string, error) = + let reason = + match error with + | BadPrimitive (msg, value) -> + genericMsg msg value false + | BadPrimitiveExtra (msg, value, reason) -> + genericMsg msg value false + "\nReason: " + reason + | BadField (msg, value) -> + genericMsg msg value true + | BadPath (msg, value, fieldName) -> + genericMsg msg value true + ("\nNode `" + fieldName + "` is unkown.") + | TooSmallArray (msg, value) -> + "Expecting " + msg + ".\n" + (Helpers.anyToString value) + | BadOneOf messages -> + "I run into the following problems:\n\n" + String.concat "\n" messages + | FailMessage msg -> + "I run into a `fail` decoder: " + msg + + match error with + | BadOneOf _ -> + // Don't need to show the path here because each error case will show it's own path + reason + | _ -> + "Error at: `" + path + "`\n" + reason + +let unwrap (path : string) (decoder : Decoder<'T>) (value : obj) : 'T = + match decoder path value with + | Ok success -> + success + | Error error -> + failwith (errorToString error) + +/////////////// +// Runners /// +///////////// + +let decodeValue (path : string) (decoder : Decoder<'T>) = + fun value -> + try + match decoder path value with + | Ok success -> + Ok success + | Error error -> + Error (errorToString error) + with + | ex -> + Error ex.Message + +let decodeString (decoder : Decoder<'T>) = + fun value -> + try + let json = JS.JSON.parse value + decodeValue "$" decoder json + with + | ex -> + Error("Given an invalid JSON: " + ex.Message) + +////////////////// +// Primitives /// +//////////////// + +let string : Decoder = + fun path value -> + if Helpers.isString value then + Ok(unbox value) + else + (path, BadPrimitive("a string", value)) |> Error + +let int : Decoder = + fun path value -> + if not (Helpers.isNumber value) then + (path, BadPrimitive("an int", value)) |> Error + else + if not (Helpers.isValidIntRange value) then + (path, BadPrimitiveExtra("an int", value, "Value was either too large or too small for an int")) |> Error + else + Ok(unbox value) + +let int64 : Decoder = + fun path value -> + if Helpers.isNumber value + then unbox value |> int64 |> Ok + elif Helpers.isString value + then unbox value |> int64 |> Ok + else (path, BadPrimitive("an int64", value)) |> Error + +let uint64 : Decoder = + fun path value -> + if Helpers.isNumber value + then unbox value |> uint64 |> Ok + elif Helpers.isString value + then unbox value |> uint64 |> Ok + else (path, BadPrimitive("an uint64", value)) |> Error + +let bool : Decoder = + fun path value -> + if Helpers.isBoolean value then + Ok(unbox value) + else + (path, BadPrimitive("a boolean", value)) |> Error + +let float : Decoder = + fun path value -> + if Helpers.isNumber value then + Ok(unbox value) + else + (path, BadPrimitive("a float", value)) |> Error + +let datetime : Decoder = + fun path value -> + if Helpers.isString value + then System.DateTime.Parse(unbox value) |> Ok + else (path, BadPrimitive("a date", value)) |> Error + +let datetimeOffset : Decoder = + fun path value -> + if Helpers.isString value + then System.DateTimeOffset.Parse(unbox value) |> Ok + else (path, BadPrimitive("a date with offset", value)) |> Error + +///////////////////////// +// Object primitives /// +/////////////////////// + +let field (fieldName: string) (decoder : Decoder<'value>) : Decoder<'value> = + fun path value -> + let fieldValue = value?(fieldName) + let currentPath = path + "." + fieldName + if Helpers.isDefined fieldValue then + decoder currentPath fieldValue + else + (currentPath, BadField ("an object with a field named `" + fieldName + "`", value)) + |> Error + +let at (fieldNames: string list) (decoder : Decoder<'value>) : Decoder<'value> = + fun path value -> + let mutable cValue = value + let mutable currentPath = path + try + for fieldName in fieldNames do + let currentNode = cValue?(fieldName) + currentPath <- currentPath + "." + fieldName + if Helpers.isDefined currentNode then + cValue <- currentNode + else + failwith fieldName + unwrap currentPath decoder cValue |> Ok + with + | ex -> + let msg = "an object with path `" + (String.concat "." fieldNames) + "`" + (currentPath, BadPath (msg, value, ex.Message)) + |> Error + +let index (requestedIndex: int) (decoder : Decoder<'value>) : Decoder<'value> = + fun path value -> + let currentPath = path + ".[" + (Operators.string requestedIndex) + "]" + if Helpers.isArray value then + let vArray = unbox value + if requestedIndex < vArray.Length then + unwrap currentPath decoder (vArray.[requestedIndex]) |> Ok + else + let msg = + "a longer array. Need index `" + + (requestedIndex.ToString()) + + "` but there are only `" + + (vArray.Length.ToString()) + + "` entries" + + (currentPath, TooSmallArray(msg, value)) + |> Error + else + (currentPath, BadPrimitive("an array", value)) + |> Error + +// let nullable (d1: Decoder<'value>) : Resul<'value option, DecoderError> = + +////////////////////// +// Data structure /// +//////////////////// + +let list (decoder : Decoder<'value>) : Decoder<'value list> = + fun path value -> + if Helpers.isArray value then + unbox value + |> Array.map (unwrap path decoder) + |> Array.toList + |> Ok + else + (path, BadPrimitive ("a list", value)) + |> Error + +let array (decoder : Decoder<'value>) : Decoder<'value array> = + fun path value -> + if Helpers.isArray value then + unbox value + |> Array.map (unwrap path decoder) + |> Ok + else + (path, BadPrimitive ("an array", value)) + |> Error + +let keyValuePairs (decoder : Decoder<'value>) : Decoder<(string * 'value) list> = + fun path value -> + if not (Helpers.isObject value) || Helpers.isArray value then + (path, BadPrimitive ("an object", value)) + |> Error + else + value + |> Helpers.objectKeys + |> Seq.map (fun key -> (key, value?(key) |> unwrap path decoder)) + |> Seq.toList + |> Ok + +let tuple2 (decoder1: Decoder<'T1>) (decoder2: Decoder<'T2>) : Decoder<'T1 * 'T2> = + fun path value -> + if Helpers.isArray value then + let value = unbox value + let a = unwrap path decoder1 value.[0] + let b = unwrap path decoder2 value.[1] + Ok(a, b) + else + (path, BadPrimitive ("a tuple", value)) + |> Error + +////////////////////////////// +// Inconsistent Structure /// +//////////////////////////// + +let option (d1 : Decoder<'value>) : Decoder<'value option> = + fun path value -> + match decodeValue path d1 value with + | Ok v -> Ok (Some v) + | Error _ -> Ok None + +let oneOf (decoders : Decoder<'value> list) : Decoder<'value> = + fun path value -> + let rec runner (decoders : Decoder<'value> list) (errors : string list) = + match decoders with + | head::tail -> + match decodeValue path head value with + | Ok v -> + Ok v + | Error error -> runner tail (errors @ [error]) + | [] -> (path, BadOneOf errors) |> Error + + runner decoders [] + +////////////////////// +// Fancy decoding /// +//////////////////// + +let nil (output : 'a) : Decoder<'a> = + fun path value -> + if isNull value then + Ok output + else + (path, BadPrimitive("null", value)) |> Error + +let value v = Ok v + +let succeed (output : 'a) : Decoder<'a> = + fun _ _ -> + Ok output + +let fail (msg: string) : Decoder<'a> = + fun path _ -> + (path, FailMessage msg) |> Error + +let andThen (cb: 'a -> Decoder<'b>) (decoder : Decoder<'a>) : Decoder<'b> = + fun path value -> + match decodeValue path decoder value with + | Error error -> + failwith error + | Ok result -> + cb result path value + +///////////////////// +// Map functions /// +/////////////////// + +let map + (ctor : 'a -> 'value) + (d1 : Decoder<'a>) : Decoder<'value> = + (fun path value -> + let t = unwrap path d1 value + Ok (ctor t) + ) + +let map2 + (ctor : 'a -> 'b -> 'value) + (d1 : Decoder<'a>) + (d2 : Decoder<'b>) : Decoder<'value> = + (fun path value -> + let t = unwrap path d1 value + let t2 = unwrap path d2 value + + Ok (ctor t t2) + ) + +let map3 + (ctor : 'a -> 'b -> 'c -> 'value) + (d1 : Decoder<'a>) + (d2 : Decoder<'b>) + (d3 : Decoder<'c>) : Decoder<'value> = + (fun path value -> + let v1 = unwrap path d1 value + let v2 = unwrap path d2 value + let v3 = unwrap path d3 value + + Ok (ctor v1 v2 v3) + ) + +let map4 + (ctor : 'a -> 'b -> 'c -> 'd -> 'value) + (d1 : Decoder<'a>) + (d2 : Decoder<'b>) + (d3 : Decoder<'c>) + (d4 : Decoder<'d>) : Decoder<'value> = + (fun path value -> + let v1 = unwrap path d1 value + let v2 = unwrap path d2 value + let v3 = unwrap path d3 value + let v4 = unwrap path d4 value + + Ok (ctor v1 v2 v3 v4) + ) + +let map5 + (ctor : 'a -> 'b -> 'c -> 'd -> 'e -> 'value) + (d1 : Decoder<'a>) + (d2 : Decoder<'b>) + (d3 : Decoder<'c>) + (d4 : Decoder<'d>) + (d5 : Decoder<'e>) : Decoder<'value> = + (fun path value -> + let v1 = unwrap path d1 value + let v2 = unwrap path d2 value + let v3 = unwrap path d3 value + let v4 = unwrap path d4 value + let v5 = unwrap path d5 value + + Ok (ctor v1 v2 v3 v4 v5) + ) + +let map6 + (ctor : 'a -> 'b -> 'c -> 'd -> 'e -> 'f -> 'value) + (d1 : Decoder<'a>) + (d2 : Decoder<'b>) + (d3 : Decoder<'c>) + (d4 : Decoder<'d>) + (d5 : Decoder<'e>) + (d6 : Decoder<'f>) : Decoder<'value> = + (fun path value -> + let v1 = unwrap path d1 value + let v2 = unwrap path d2 value + let v3 = unwrap path d3 value + let v4 = unwrap path d4 value + let v5 = unwrap path d5 value + let v6 = unwrap path d6 value + + Ok (ctor v1 v2 v3 v4 v5 v6) + ) + +let map7 + (ctor : 'a -> 'b -> 'c -> 'd -> 'e -> 'f -> 'g -> 'value) + (d1 : Decoder<'a>) + (d2 : Decoder<'b>) + (d3 : Decoder<'c>) + (d4 : Decoder<'d>) + (d5 : Decoder<'e>) + (d6 : Decoder<'f>) + (d7 : Decoder<'g>) : Decoder<'value> = + (fun path value -> + let v1 = unwrap path d1 value + let v2 = unwrap path d2 value + let v3 = unwrap path d3 value + let v4 = unwrap path d4 value + let v5 = unwrap path d5 value + let v6 = unwrap path d6 value + let v7 = unwrap path d7 value + + Ok (ctor v1 v2 v3 v4 v5 v6 v7) + ) + +let map8 + (ctor : 'a -> 'b -> 'c -> 'd -> 'e -> 'f -> 'g -> 'h -> 'value) + (d1 : Decoder<'a>) + (d2 : Decoder<'b>) + (d3 : Decoder<'c>) + (d4 : Decoder<'d>) + (d5 : Decoder<'e>) + (d6 : Decoder<'f>) + (d7 : Decoder<'g>) + (d8 : Decoder<'h>) : Decoder<'value> = + (fun path value -> + let v1 = unwrap path d1 value + let v2 = unwrap path d2 value + let v3 = unwrap path d3 value + let v4 = unwrap path d4 value + let v5 = unwrap path d5 value + let v6 = unwrap path d6 value + let v7 = unwrap path d7 value + let v8 = unwrap path d8 value + + Ok (ctor v1 v2 v3 v4 v5 v6 v7 v8) + ) + +let dict (decoder : Decoder<'value>) : Decoder> = + map Map.ofList (keyValuePairs decoder) + +//////////////// +// Pipeline /// +////////////// + +// let custom d1 d2 = map2 (|>) d1 d2 + +// // let hardcoded<'a, 'b, 'c> : 'a -> Decoder<('a -> 'b)> -> 'c -> Result<'b,DecoderError> = succeed >> custom + +// let required path (key : string) (valDecoder : Decoder<'a>) (decoder : Decoder<'a -> 'b>) : Decoder<'b> = +// custom (field key valDecoder) decoder path + +// let requiredAt (path : string list) (valDecoder : Decoder<'a>) (decoder : Decoder<'a -> 'b>) : Decoder<'b> = +// custom (at path valDecoder) decoder + +// let decode output value = succeed output value + +// /// Convert a `Decoder>` into a `Decoder<'a>` +// let resolve d1 : Decoder<'a> = +// fun path value -> +// andThen id d1 path value + +// let optionalDecoder path pathDecoder valDecoder fallback = +// let nullOr decoder = +// oneOf [ decoder; nil fallback ] + +// let handleResult input = +// match decodeValue path pathDecoder input with +// | Ok rawValue -> +// // Field was present, so we try to decode the value +// match decodeValue path (nullOr valDecoder) rawValue with +// | Ok finalResult -> +// succeed finalResult + +// | Error finalErr -> +// fail finalErr + +// | Error _ -> +// // Field was not present +// succeed fallback + +// value +// |> andThen handleResult + +// let optional key valDecoder fallback decoder = +// custom (optionalDecoder (field key value) valDecoder fallback) decoder + +// let optionalAt path valDecoder fallback decoder = +// custom (optionalDecoder (at path value) valDecoder fallback) decoder + +////////////////// +// Reflection /// +//////////////// + +// open Microsoft.FSharp.Reflection + +// // As generics are erased by Fable, let's just do an unsafe cast for performance +// let inline private boxDecoder (d: Decoder<'T>): Decoder = +// !!d // d >> Result.map box + +// let inline private unboxDecoder (d: Decoder): Decoder<'T> = +// !!d // d >> Result.map unbox + +// let private object (decoders: (string * Decoder)[]) (value: obj) = +// if not (Helpers.isObject value) || Helpers.isArray value then +// BadPrimitive ("an object", value) |> Error +// else +// (decoders, Ok []) ||> Array.foldBack (fun (name, decoder) acc -> +// match acc with +// | Error _ -> acc +// | Ok result -> +// // TODO!!! Optional types shouldn't be required +// field name decoder value +// |> Result.map (fun v -> v::result)) + +// let private mixedArray msg (decoders: Decoder[]) (values: obj[]): Result = +// if decoders.Length <> values.Length then +// sprintf "Expected %i %s but got %i" decoders.Length msg values.Length +// |> FailMessage |> Error +// else +// (values, decoders, Ok []) +// |||> Array.foldBack2 (fun value decoder acc -> +// match acc with +// | Error _ -> acc +// | Ok result -> decoder value |> Result.map (fun v -> v::result)) + +// let rec private autoDecodeRecordsAndUnions (t: System.Type) (isCamelCase : bool) : Decoder = +// if FSharpType.IsRecord(t) then +// fun value -> +// let decoders = +// FSharpType.GetRecordFields(t) +// |> Array.map (fun fi -> +// let name = +// if isCamelCase then +// fi.Name.[..0].ToLowerInvariant() + fi.Name.[1..] +// else +// fi.Name +// name, autoDecoder isCamelCase fi.PropertyType) +// object decoders value +// |> Result.map (fun xs -> FSharpValue.MakeRecord(t, List.toArray xs)) +// elif FSharpType.IsUnion(t) then +// fun (value: obj) -> +// if Helpers.isString(value) then +// let name = unbox value +// match FSharpType.GetUnionCases(t) |> Array.tryFind (fun x -> x.Name = name) with +// | None -> FailMessage("Cannot find case " + name + " in " + t.FullName) |> Error +// | Some uci -> FSharpValue.MakeUnion(uci, [||]) |> Ok +// else +// let uci, values = FSharpValue.GetUnionFields(value, t) +// match FSharpType.GetUnionCases(t) |> Array.tryFind (fun x -> x.Name = uci.Name) with +// | None -> FailMessage("Cannot find case " + uci.Name + " in " + t.FullName) |> Error +// | Some uci -> +// let decoders = uci.GetFields() |> Array.map (fun fi -> autoDecoder isCamelCase fi.PropertyType) +// mixedArray "union fields" decoders values +// |> Result.map (fun values -> FSharpValue.MakeUnion(uci, List.toArray values)) +// else +// failwithf "Class types cannot be automatically deserialized: %s" t.FullName + +// and private autoDecoder isCamelCase (t: System.Type) : Decoder = +// if t.IsArray then +// let decoder = t.GetElementType() |> autoDecoder isCamelCase +// array decoder |> boxDecoder +// elif t.IsGenericType then +// if FSharpType.IsTuple(t) then +// let decoders = FSharpType.GetTupleElements(t) |> Array.map (autoDecoder isCamelCase) +// fun value -> +// if Helpers.isArray value then +// mixedArray "tuple elements" decoders (unbox value) +// |> Result.map (fun xs -> FSharpValue.MakeTuple(List.toArray xs, t)) +// else BadPrimitive ("an array", value) |> Error +// else +// let fullname = t.GetGenericTypeDefinition().FullName +// if fullname = typedefof.FullName +// then t.GenericTypeArguments.[0] |> (autoDecoder isCamelCase) |> list |> boxDecoder +// elif fullname = typedefof< Map >.FullName +// then +// let decoder = t.GenericTypeArguments.[1] |> autoDecoder isCamelCase +// (array (tuple2 string decoder) >> Result.map Map) |> boxDecoder +// else autoDecodeRecordsAndUnions t isCamelCase +// else +// let fullname = t.FullName +// if fullname = typeof.FullName +// then boxDecoder int +// elif fullname = typeof.FullName +// then boxDecoder float +// elif fullname = typeof.FullName +// then boxDecoder string +// elif fullname = typeof.FullName +// then boxDecoder bool +// elif fullname = typeof.FullName +// then boxDecoder int64 +// elif fullname = typeof.FullName +// then boxDecoder uint64 +// elif fullname = typeof.FullName +// then boxDecoder datetime +// elif fullname = typeof.FullName +// then boxDecoder datetimeOffset +// // Fable compiles decimals as floats +// elif fullname = typeof.FullName +// then boxDecoder float +// // Fable compiles Guids as strings +// elif fullname = typeof.FullName +// then boxDecoder string +// elif fullname = typeof.FullName +// then Ok +// else autoDecodeRecordsAndUnions t isCamelCase + +// type Auto = +// static member GenerateDecoder<'T>(?isCamelCase : bool, [] ?resolver: ITypeResolver<'T>): Decoder<'T> = +// let isCamelCase = defaultArg isCamelCase false +// resolver.Value.ResolveType() |> (autoDecoder isCamelCase) |> unboxDecoder + +// static member DecodeString<'T>(json: string, ?isCamelCase : bool, [] ?resolver: ITypeResolver<'T>): 'T = +// let decoder = Auto.GenerateDecoder(?isCamelCase=isCamelCase, ?resolver=resolver) +// match decodeString decoder json with +// | Ok x -> x +// | Error msg -> failwith msg + +// static member DecodeString(json: string, t: System.Type, ?isCamelCase : bool): obj = +// let isCamelCase = defaultArg isCamelCase false +// let decoder = autoDecoder isCamelCase t +// match decodeString decoder json with +// | Ok x -> x +// | Error msg -> failwith msg