From 11fee033929d3308cab9cb16d49adc1987d6d57d Mon Sep 17 00:00:00 2001 From: "T. Brandon Ashley" Date: Sun, 10 Feb 2019 15:10:47 -0500 Subject: [PATCH] Upgrade elm dependencies, to latest http & elm-graphql (#82) * chore(*): elm-graphql 2.0.0 upgrade * chore(*): elm-graphql 4.2.0 upgrade * fix(*): target self on external links --- assets/elm.json | 12 +- assets/package-lock.json | 13 +- assets/package.json | 2 +- assets/src/Data/Jwt.elm | 6 +- assets/src/Effect/Program.elm | 10 +- assets/src/Ellie/Api/Helpers.elm | 22 +- assets/src/Elm/Docs.elm | 63 +- assets/src/Elm/Error.elm | 65 +- assets/src/Elm/Package.elm | 8 +- assets/src/Extra/HttpBuilder.elm | 13 - assets/src/Extra/String.elm | 22 - assets/src/Network/Absinthe/Socket.elm | 3 - assets/src/Pages/Editor/Effects.elm | 90 +- assets/src/Pages/Editor/Views/StatusBar.elm | 8 +- assets/src/Pages/Embed/Effects.elm | 46 +- assets/vendor/Graphql/Codec.elm | 20 + assets/vendor/Graphql/Document/Field.elm | 25 +- assets/vendor/Graphql/Field.elm | 178 ---- assets/vendor/Graphql/Http.elm | 282 +++++- .../Graphql/Internal/Builder/Object.elm | 110 +-- assets/vendor/Graphql/Internal/Encode.elm | 12 +- assets/vendor/Graphql/OptionalArgument.elm | 4 +- assets/vendor/Graphql/RawField.elm | 6 +- assets/vendor/Graphql/SelectionSet.elm | 844 ++++++++++++++---- 24 files changed, 1180 insertions(+), 684 deletions(-) delete mode 100644 assets/src/Extra/HttpBuilder.elm delete mode 100644 assets/src/Extra/String.elm create mode 100644 assets/vendor/Graphql/Codec.elm delete mode 100644 assets/vendor/Graphql/Field.elm diff --git a/assets/elm.json b/assets/elm.json index 069324df..5451e70d 100644 --- a/assets/elm.json +++ b/assets/elm.json @@ -10,24 +10,24 @@ "direct": { "Skinney/murmur3": "2.0.8", "elm/browser": "1.0.1", - "elm/core": "1.0.0", + "elm/core": "1.0.2", "elm/html": "1.0.0", - "elm/http": "1.0.0", + "elm/http": "2.0.0", "elm/json": "1.1.2", "elm/parser": "1.1.0", - "elm/regex": "1.0.0", "elm/svg": "1.0.1", "elm/time": "1.0.0", "elm/url": "1.0.0", "elm-community/list-extra": "8.1.0", "folkertdev/elm-deque": "3.0.0", "jinjor/elm-debounce": "3.0.0", - "lukewestby/elm-http-builder": "6.0.0", "lukewestby/elm-string-interpolate": "1.0.3", - "lukewestby/http-extra": "2.0.1", "rtfeldman/elm-css": "16.0.0" }, "indirect": { + "elm/bytes": "1.0.7", + "elm/file": "1.0.3", + "elm/regex": "1.0.0", "elm/virtual-dom": "1.0.2", "rtfeldman/elm-hex": "1.0.0" } @@ -36,4 +36,4 @@ "direct": {}, "indirect": {} } -} +} \ No newline at end of file diff --git a/assets/package-lock.json b/assets/package-lock.json index 39e391f4..430b2df9 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -809,14 +809,13 @@ } }, "@dillonkearns/elm-graphql": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@dillonkearns/elm-graphql/-/elm-graphql-1.0.8.tgz", - "integrity": "sha1-8O806C5c6BYCDhCsBCb8H8n4Z0M=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@dillonkearns/elm-graphql/-/elm-graphql-3.2.0.tgz", + "integrity": "sha512-0AAcDELY1aEY3IJZI0ZbWFuEZNCr32ioF2ht6Bf4J4xR7CnFz91/AeGdVAXLoQjwhrbtNsiQ4B8CrRFxDClN9A==", "requires": { "encoding": "^0.1.12", "glob": "^7.1.2", "graphql-request": "^1.4.0", - "minimist": "^1.2.0", "request": "^2.83.0" } }, @@ -4535,7 +4534,7 @@ "graphql-request": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-1.8.2.tgz", - "integrity": "sha1-OY0QrhXFhWdnQb3j/AHVypSPj74=", + "integrity": "sha512-dDX2M+VMsxXFCmUX0Vo0TopIZIX4ggzOtiCsThgtrKR4niiaagsGTDIHj3fsOMFETpa064vzovI+4YV4QnMbcg==", "requires": { "cross-fetch": "2.2.2" } @@ -5037,7 +5036,7 @@ "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha1-ICK0sl+93CHS9SSXSkdKr+czkIs=", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "requires": { "safer-buffer": ">= 2.1.2 < 3" } @@ -10052,7 +10051,7 @@ "whatwg-fetch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", - "integrity": "sha1-3eal3zFfnTmZGqF2IYU9cguFVm8=" + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" }, "whet.extend": { "version": "0.9.9", diff --git a/assets/package.json b/assets/package.json index 9b19a4b5..b4983851 100644 --- a/assets/package.json +++ b/assets/package.json @@ -14,7 +14,7 @@ "@babel/core": "7.0.0-beta.46", "@babel/preset-env": "7.0.0-beta.46", "@babel/preset-stage-2": "7.0.0-beta.46", - "@dillonkearns/elm-graphql": "^1.0.7", + "@dillonkearns/elm-graphql": "^3.2.0", "@webcomponents/custom-elements": "1.1.0", "babel-loader": "8.0.0-beta.4", "codemirror": "5.27.4", diff --git a/assets/src/Data/Jwt.elm b/assets/src/Data/Jwt.elm index 9e36a202..7671c3c2 100644 --- a/assets/src/Data/Jwt.elm +++ b/assets/src/Data/Jwt.elm @@ -1,7 +1,7 @@ module Data.Jwt exposing (Jwt, decoder, encoder, field, fromString, toString, withTokenHeader) -import Graphql.Field import Graphql.Http +import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) import Json.Decode as Decode exposing (Decoder) import Json.Encode as Encode exposing (Value) @@ -35,6 +35,6 @@ withTokenHeader (Jwt token) = Graphql.Http.withHeader "authorization" ("Bearer " ++ token) -field : Graphql.Field.Field String a -> Graphql.Field.Field Jwt a +field : SelectionSet String a -> SelectionSet Jwt a field stringField = - Graphql.Field.map Jwt stringField + SelectionSet.map Jwt stringField diff --git a/assets/src/Effect/Program.elm b/assets/src/Effect/Program.elm index 41de87aa..94bf49a3 100644 --- a/assets/src/Effect/Program.elm +++ b/assets/src/Effect/Program.elm @@ -133,7 +133,7 @@ runCmd config state cmd = |> maybeWithToken token |> withCaching cache |> Graphql.Http.send identity - |> Cmd.map (Result.mapError Graphql.Http.ignoreParsedErrorData) + -- |> Cmd.map (Result.mapError Graphql.Http.ignoreParsedErrorData) |> Cmd.map (\result -> case result of @@ -141,7 +141,8 @@ runCmd config state cmd = UserMsg msg Err str -> - UserMsg (onError str) + -- UserMsg (onError str) + NoOp ) |> maybeWithDebounce state debounce @@ -149,7 +150,7 @@ runCmd config state cmd = Graphql.Http.mutationRequest url selection |> maybeWithToken token |> Graphql.Http.send identity - |> Cmd.map (Result.mapError Graphql.Http.ignoreParsedErrorData) + -- |> Cmd.map (Result.mapError Graphql.Http.ignoreParsedErrorData) |> Cmd.map (\result -> case result of @@ -157,7 +158,8 @@ runCmd config state cmd = UserMsg msg Err str -> - UserMsg (onError str) + -- UserMsg (onError str) + NoOp ) |> maybeWithDebounce state debounce diff --git a/assets/src/Ellie/Api/Helpers.elm b/assets/src/Ellie/Api/Helpers.elm index 25bcb702..f28f470c 100644 --- a/assets/src/Ellie/Api/Helpers.elm +++ b/assets/src/Ellie/Api/Helpers.elm @@ -3,36 +3,36 @@ module Ellie.Api.Helpers exposing (defaultField, nameField, projectIdField, unit import Ellie.Api.Scalar as ApiScalar import Elm.Name as Name exposing (Name) import Elm.Version as Version exposing (Version) -import Graphql.Field as Field exposing (Field) import Graphql.Http exposing (Request) +import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) -versionField : Field ApiScalar.ElmVersion a -> Field Version a +versionField : SelectionSet ApiScalar.ElmVersion a -> SelectionSet Version a versionField = - Field.mapOrFail <| + SelectionSet.mapOrFail <| \(ApiScalar.ElmVersion string) -> Version.fromString string -unitField : Field ApiScalar.Unit a -> Field () a +unitField : SelectionSet ApiScalar.Unit a -> SelectionSet () a unitField = - Field.map <| \_ -> () + SelectionSet.map <| \_ -> () -projectIdField : Field ApiScalar.PrettyId a -> Field String a +projectIdField : SelectionSet ApiScalar.PrettyId a -> SelectionSet String a projectIdField = - Field.map <| + SelectionSet.map <| \(ApiScalar.PrettyId string) -> string -nameField : Field ApiScalar.ElmName a -> Field Name a +nameField : SelectionSet ApiScalar.ElmName a -> SelectionSet Name a nameField = - Field.mapOrFail <| + SelectionSet.mapOrFail <| \(ApiScalar.ElmName string) -> Name.fromString string -defaultField : a -> Field (Maybe a) b -> Field a b +defaultField : a -> SelectionSet (Maybe a) b -> SelectionSet a b defaultField default = - Field.map (Maybe.withDefault default) + SelectionSet.map (Maybe.withDefault default) withMaybe : (a -> Request b -> Request b) -> Maybe a -> Request b -> Request b diff --git a/assets/src/Elm/Docs.elm b/assets/src/Elm/Docs.elm index 165238f8..897da292 100644 --- a/assets/src/Elm/Docs.elm +++ b/assets/src/Elm/Docs.elm @@ -41,8 +41,7 @@ import Ellie.Api.Object.ElmDocsUnion as ElmDocsUnion import Ellie.Api.Object.ElmDocsValue as ElmDocsValue import Ellie.Api.Scalar as Scalar import Elm.Package as Package exposing (Package) -import Graphql.Field as Field -import Graphql.SelectionSet exposing (SelectionSet, hardcoded, with) +import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) @@ -194,46 +193,46 @@ selection : SelectionSet (Package -> Module) ElmDocsModule selection = let selection_ = - ElmDocsModule.selection Module - |> with ElmDocsModule.name - |> with ElmDocsModule.comment - |> with (ElmDocsModule.unions unionSelection) - |> with (ElmDocsModule.aliases aliasSelection) - |> with (ElmDocsModule.values valueSelection) - |> with (ElmDocsModule.binops binopSelection) + SelectionSet.succeed Module + |> SelectionSet.with ElmDocsModule.name + |> SelectionSet.with ElmDocsModule.comment + |> SelectionSet.with (ElmDocsModule.unions unionSelection) + |> SelectionSet.with (ElmDocsModule.aliases aliasSelection) + |> SelectionSet.with (ElmDocsModule.values valueSelection) + |> SelectionSet.with (ElmDocsModule.binops binopSelection) unionSelection = - ElmDocsUnion.selection Union - |> with ElmDocsUnion.name - |> with ElmDocsUnion.comment - |> with ElmDocsUnion.args - |> with (ElmDocsUnion.tags tagSelection) + SelectionSet.succeed Union + |> SelectionSet.with ElmDocsUnion.name + |> SelectionSet.with ElmDocsUnion.comment + |> SelectionSet.with ElmDocsUnion.args + |> SelectionSet.with (ElmDocsUnion.tags tagSelection) aliasSelection = - ElmDocsAlias.selection Alias - |> with ElmDocsAlias.name - |> with ElmDocsAlias.comment - |> with ElmDocsAlias.args - |> with (Field.map (\(Scalar.ElmDocsType t) -> t) ElmDocsAlias.type_) + SelectionSet.succeed Alias + |> SelectionSet.with ElmDocsAlias.name + |> SelectionSet.with ElmDocsAlias.comment + |> SelectionSet.with ElmDocsAlias.args + |> SelectionSet.with (SelectionSet.map (\(Scalar.ElmDocsType t) -> t) ElmDocsAlias.type_) valueSelection = - ElmDocsValue.selection Value - |> with ElmDocsValue.name - |> with ElmDocsValue.comment - |> with (Field.map (\(Scalar.ElmDocsType t) -> t) ElmDocsValue.type_) + SelectionSet.succeed Value + |> SelectionSet.with ElmDocsValue.name + |> SelectionSet.with ElmDocsValue.comment + |> SelectionSet.with (SelectionSet.map (\(Scalar.ElmDocsType t) -> t) ElmDocsValue.type_) binopSelection = - ElmDocsBinop.selection Binop - |> with ElmDocsBinop.name - |> with ElmDocsBinop.comment - |> with (Field.map (\(Scalar.ElmDocsType t) -> t) ElmDocsBinop.type_) - |> with (Field.map makeAssociativity ElmDocsBinop.associativity) - |> with ElmDocsBinop.precedence + SelectionSet.succeed Binop + |> SelectionSet.with ElmDocsBinop.name + |> SelectionSet.with ElmDocsBinop.comment + |> SelectionSet.with (SelectionSet.map (\(Scalar.ElmDocsType t) -> t) ElmDocsBinop.type_) + |> SelectionSet.with (SelectionSet.map makeAssociativity ElmDocsBinop.associativity) + |> SelectionSet.with ElmDocsBinop.precedence tagSelection = - ElmDocsTag.selection (\a b -> ( a, b )) - |> with ElmDocsTag.name - |> with (Field.map (List.map (\(Scalar.ElmDocsType t) -> t)) ElmDocsTag.args) + SelectionSet.succeed Tuple.pair + |> SelectionSet.with ElmDocsTag.name + |> SelectionSet.with (SelectionSet.map (List.map (\(Scalar.ElmDocsType t) -> t)) ElmDocsTag.args) makeAssociativity a = case a of diff --git a/assets/src/Elm/Error.elm b/assets/src/Elm/Error.elm index 4e77f744..8fb95349 100644 --- a/assets/src/Elm/Error.elm +++ b/assets/src/Elm/Error.elm @@ -36,8 +36,7 @@ import Ellie.Api.Object.ElmErrorRegion as ElmErrorRegion import Ellie.Api.Object.ElmErrorStyle as ElmErrorStyle import Ellie.Api.Union as ApiUnion import Ellie.Api.Union.ElmError as ElmError -import Graphql.Field as Field -import Graphql.SelectionSet exposing (SelectionSet, with) +import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) type Error @@ -106,31 +105,31 @@ selection : SelectionSet Error ApiUnion.ElmError selection = let badModuleSelection = - ElmErrorBadModule.selection BadModule - |> with ElmErrorBadModule.path - |> with ElmErrorBadModule.name - |> with (ElmErrorBadModule.problems problemSelection) + SelectionSet.succeed BadModule + |> SelectionSet.with ElmErrorBadModule.path + |> SelectionSet.with ElmErrorBadModule.name + |> SelectionSet.with (ElmErrorBadModule.problems problemSelection) problemSelection = - ElmErrorProblem.selection Problem - |> with ElmErrorProblem.title - |> with (ElmErrorProblem.region regionSelection) - |> with (ElmErrorProblem.message chunkSelection) + SelectionSet.succeed Problem + |> SelectionSet.with ElmErrorProblem.title + |> SelectionSet.with (ElmErrorProblem.region regionSelection) + |> SelectionSet.with (ElmErrorProblem.message chunkSelection) regionSelection = - ElmErrorRegion.selection Region - |> with (ElmErrorRegion.start positionSelection) - |> with (ElmErrorRegion.end positionSelection) + SelectionSet.succeed Region + |> SelectionSet.with (ElmErrorRegion.start positionSelection) + |> SelectionSet.with (ElmErrorRegion.end positionSelection) positionSelection = - ElmErrorPosition.selection Position - |> with ElmErrorPosition.line - |> with ElmErrorPosition.column + SelectionSet.succeed Position + |> SelectionSet.with ElmErrorPosition.line + |> SelectionSet.with ElmErrorPosition.column chunkSelection = - ElmErrorChunk.selection makeChunk - |> with ElmErrorChunk.string - |> with (ElmErrorChunk.style styleSelection) + SelectionSet.succeed makeChunk + |> SelectionSet.with ElmErrorChunk.string + |> SelectionSet.with (ElmErrorChunk.style styleSelection) makeChunk string maybeStyle = case maybeStyle of @@ -141,10 +140,10 @@ selection = Unstyled string styleSelection = - ElmErrorStyle.selection Style - |> with ElmErrorStyle.bold - |> with ElmErrorStyle.underline - |> with (Field.map (Maybe.map makeColor) ElmErrorStyle.color) + SelectionSet.succeed Style + |> SelectionSet.with ElmErrorStyle.bold + |> SelectionSet.with ElmErrorStyle.underline + |> SelectionSet.with (SelectionSet.map (Maybe.map makeColor) ElmErrorStyle.color) makeColor color = case color of @@ -196,13 +195,13 @@ selection = ElmErrorColor.VividBlack -> BLACK in - ElmError.selection (Maybe.withDefault (ModuleProblems [])) - [ ElmErrorGeneralProblem.selection (\path title message -> GeneralProblem { path = path, title = title, message = message }) - |> with ElmErrorGeneralProblem.path - |> with ElmErrorGeneralProblem.title - |> with (ElmErrorGeneralProblem.message chunkSelection) - |> ElmError.onElmErrorGeneralProblem - , ElmErrorModuleProblems.selection ModuleProblems - |> with (ElmErrorModuleProblems.errors badModuleSelection) - |> ElmError.onElmErrorModuleProblems - ] + ElmError.fragments + { onElmErrorGeneralProblem = + SelectionSet.succeed (\path title message -> GeneralProblem { path = path, title = title, message = message }) + |> SelectionSet.with ElmErrorGeneralProblem.path + |> SelectionSet.with ElmErrorGeneralProblem.title + |> SelectionSet.with (ElmErrorGeneralProblem.message chunkSelection) + , onElmErrorModuleProblems = + SelectionSet.succeed ModuleProblems + |> SelectionSet.with (ElmErrorModuleProblems.errors badModuleSelection) + } diff --git a/assets/src/Elm/Package.elm b/assets/src/Elm/Package.elm index b5f34b6d..8386ffe9 100644 --- a/assets/src/Elm/Package.elm +++ b/assets/src/Elm/Package.elm @@ -18,7 +18,7 @@ import Ellie.Api.Object.ElmPackage as ApiPackage import Ellie.Api.Scalar as ApiScalar import Elm.Name as Name exposing (Name) import Elm.Version as Version exposing (Version) -import Graphql.SelectionSet exposing (SelectionSet, with) +import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) import Json.Decode as Decode exposing (Decoder) import Json.Encode as Encode exposing (Value) @@ -93,9 +93,9 @@ codeLink { name, version } = selection : SelectionSet Package ApiObject.ElmPackage selection = - ApiPackage.selection Package - |> with (ApiHelpers.nameField ApiPackage.name) - |> with (ApiHelpers.versionField ApiPackage.version) + SelectionSet.succeed Package + |> SelectionSet.with (ApiHelpers.nameField ApiPackage.name) + |> SelectionSet.with (ApiHelpers.versionField ApiPackage.version) toInputObject : Package -> ApiInputObject.ElmPackageInput diff --git a/assets/src/Extra/HttpBuilder.elm b/assets/src/Extra/HttpBuilder.elm deleted file mode 100644 index 5d23fa30..00000000 --- a/assets/src/Extra/HttpBuilder.elm +++ /dev/null @@ -1,13 +0,0 @@ -module Extra.HttpBuilder exposing (withMaybe) - -import HttpBuilder exposing (RequestBuilder) - - -withMaybe : (a -> RequestBuilder b -> RequestBuilder b) -> Maybe a -> RequestBuilder b -> RequestBuilder b -withMaybe extension maybeValue builder = - case maybeValue of - Just a -> - extension a builder - - Nothing -> - builder diff --git a/assets/src/Extra/String.elm b/assets/src/Extra/String.elm deleted file mode 100644 index 9b2ca4f7..00000000 --- a/assets/src/Extra/String.elm +++ /dev/null @@ -1,22 +0,0 @@ -module Extra.String exposing (fromFloat, fromInt, replace) - -import Regex exposing (Regex) - - -replace : String -> String -> String -> String -replace pattern replacement input = - Regex.replace - Regex.All - (Regex.regex pattern) - (\_ -> replacement) - input - - -fromInt : Int -> String -fromInt int = - toString int - - -fromFloat : Float -> String -fromFloat float = - toString float diff --git a/assets/src/Network/Absinthe/Socket.elm b/assets/src/Network/Absinthe/Socket.elm index f6648fb5..a5d89c63 100644 --- a/assets/src/Network/Absinthe/Socket.elm +++ b/assets/src/Network/Absinthe/Socket.elm @@ -15,9 +15,6 @@ import Graphql.Operation exposing (RootSubscription) import Graphql.SelectionSet exposing (SelectionSet) import Json.Decode as Decode exposing (Decoder) import Json.Encode as Encode exposing (Value) -import Process -import Task -import Time port absintheSocketOutbound : Value -> Cmd msg diff --git a/assets/src/Pages/Editor/Effects.elm b/assets/src/Pages/Editor/Effects.elm index 6b5933fd..d80b26a0 100644 --- a/assets/src/Pages/Editor/Effects.elm +++ b/assets/src/Pages/Editor/Effects.elm @@ -24,7 +24,7 @@ import Elm.Version as Version exposing (Version) import Extra.Json.Encode as Encode import Graphql.Http import Graphql.OptionalArgument as OptionalArgument -import Graphql.SelectionSet as SelectionSet exposing (SelectionSet(..), hardcoded, with) +import Graphql.SelectionSet as SelectionSet exposing (SelectionSet(..)) import Json.Decode as Decode exposing (Decoder) import Json.Encode as Encode exposing (Value) import Pages.Editor.Types.EditorAction as EditorAction exposing (EditorAction) @@ -37,19 +37,19 @@ getRevision : Revision.Id -> Command (Result (Graphql.Http.Error ()) Revision) getRevision revisionId = let query = - ApiQuery.selection identity - |> with (ApiQuery.revision arguments revisionQuery) + SelectionSet.succeed identity + |> SelectionSet.with (ApiQuery.revision arguments revisionQuery) arguments = { id = ApiScalar.PrettyId revisionId } revisionQuery = - ApiRevision.selection Revision - |> with ApiRevision.htmlCode - |> with ApiRevision.elmCode - |> with (ApiRevision.packages Package.selection) - |> with (ApiHelpers.defaultField "" ApiRevision.title) - |> with (ApiHelpers.versionField ApiRevision.elmVersion) + SelectionSet.succeed Revision + |> SelectionSet.with ApiRevision.htmlCode + |> SelectionSet.with ApiRevision.elmCode + |> SelectionSet.with (ApiRevision.packages Package.selection) + |> SelectionSet.with (ApiHelpers.defaultField "" ApiRevision.title) + |> SelectionSet.with (ApiHelpers.versionField ApiRevision.elmVersion) in Command.GraphqlQuery { url = "/api" @@ -65,16 +65,16 @@ searchPackages : String -> Command (Result (Graphql.Http.Error ()) (List Package searchPackages queryString = let query = - ApiQuery.selection identity - |> with (ApiQuery.packageSearch arguments packageQuery) + SelectionSet.succeed identity + |> SelectionSet.with (ApiQuery.packageSearch arguments packageQuery) arguments = { query = queryString } packageQuery = - ApiPackage.selection Package - |> with (ApiHelpers.nameField ApiPackage.name) - |> with (ApiHelpers.versionField ApiPackage.version) + SelectionSet.succeed Package + |> SelectionSet.with (ApiHelpers.nameField ApiPackage.name) + |> SelectionSet.with (ApiHelpers.versionField ApiPackage.version) in Command.GraphqlQuery { url = "/api" @@ -90,8 +90,8 @@ formatCode : Jwt -> Version -> String -> Command (Result (Graphql.Http.Error ()) formatCode token version code = let mutation = - ApiMutation.selection identity - |> with (ApiMutation.formatCode arguments) + SelectionSet.succeed identity + |> SelectionSet.with (ApiMutation.formatCode arguments) arguments = { code = code @@ -111,8 +111,8 @@ compile : Jwt -> Version -> String -> List Package -> Command (Result (Graphql.H compile token elmVersion elmCode packages = let mutation = - ApiMutation.selection (\_ -> ()) - |> with (ApiMutation.compile arguments) + SelectionSet.succeed (\_ -> ()) + |> SelectionSet.with (ApiMutation.compile arguments) arguments = { elmCode = elmCode @@ -142,8 +142,8 @@ authenticate = , onError = Err , debounce = Nothing , selection = - ApiMutation.selection (Jwt.fromString >> Ok) - |> with ApiMutation.authenticate + SelectionSet.succeed (Jwt.fromString >> Ok) + |> SelectionSet.with ApiMutation.authenticate } @@ -151,21 +151,21 @@ workspaceUpdates : Jwt -> Subscription WorkspaceUpdate workspaceUpdates token = let selection = - ApiSubscription.selection identity - |> with (ApiSubscription.workspace workspaceUpdateSelection) + SelectionSet.succeed identity + |> SelectionSet.with (ApiSubscription.workspace workspaceUpdateSelection) workspaceUpdateSelection = - ApiWorkspaceUpdate.selection (Maybe.withDefault Disconnected) - [ ApiWorkspaceAttached.selection Attached - |> with (ApiWorkspaceAttached.packages Package.selection) - |> ApiWorkspaceUpdate.onWorkspaceAttached - , ApiCompileCompleted.selection CompileCompleted - |> with (ApiCompileCompleted.error Error.selection) - |> ApiWorkspaceUpdate.onCompileCompleted - , ApiWorkspaceError.selection (\_ -> Disconnected) - |> with ApiWorkspaceError.message - |> ApiWorkspaceUpdate.onWorkspaceError - ] + ApiWorkspaceUpdate.fragments + { onWorkspaceAttached = + SelectionSet.succeed Attached + |> SelectionSet.with (ApiWorkspaceAttached.packages Package.selection) + , onCompileCompleted = + SelectionSet.succeed CompileCompleted + |> SelectionSet.with (ApiCompileCompleted.error Error.selection) + , onWorkspaceError = + SelectionSet.succeed (\_ -> Disconnected) + |> SelectionSet.with ApiWorkspaceError.message + } in Subscription.AbsintheSubscription { url = Constants.socketOrigin ++ "/api/sockets", token = Just (Jwt.toString token) } @@ -183,8 +183,8 @@ attachToWorkspace : Jwt -> Version -> Command (Result (Graphql.Http.Error ()) () attachToWorkspace token version = let selection = - ApiMutation.selection (\_ -> ()) - |> with (ApiMutation.attachToWorkspace arguments) + SelectionSet.succeed (\_ -> ()) + |> SelectionSet.with (ApiMutation.attachToWorkspace arguments) arguments = { elmVersion = ApiScalar.ElmVersion <| Version.toString version } @@ -211,8 +211,8 @@ createRevision : Jwt -> Int -> Revision -> Command (Result (Graphql.Http.Error ( createRevision token termsVersion revision = let selection = - ApiMutation.selection identity - |> with (ApiMutation.createRevision arguments revisionSelection) + SelectionSet.succeed identity + |> SelectionSet.with (ApiMutation.createRevision arguments revisionSelection) arguments = { inputs = @@ -231,8 +231,8 @@ createRevision token termsVersion revision = } revisionSelection = - ApiRevision.selection identity - |> with (ApiHelpers.projectIdField ApiRevision.id) + SelectionSet.succeed identity + |> SelectionSet.with (ApiHelpers.projectIdField ApiRevision.id) in Command.GraphqlMutation { url = "/api" @@ -247,8 +247,8 @@ getDocs : List Package -> Command (List Docs.Module) getDocs packages = let selection = - ApiQuery.selection List.concat - |> with (ApiQuery.packages { packages = List.map makeArgs packages } docsSelection) + SelectionSet.succeed List.concat + |> SelectionSet.with (ApiQuery.packages { packages = List.map makeArgs packages } docsSelection) makeArgs package = { name = ApiScalar.ElmName <| Name.toString package.name @@ -256,14 +256,14 @@ getDocs packages = } packageSelection = - ApiPackage.selection Package - |> with (ApiHelpers.nameField ApiPackage.name) - |> with (ApiHelpers.versionField ApiPackage.version) + SelectionSet.succeed Package + |> SelectionSet.with (ApiHelpers.nameField ApiPackage.name) + |> SelectionSet.with (ApiHelpers.versionField ApiPackage.version) docsSelection = packageSelection |> SelectionSet.map (\p d -> List.map ((|>) p) d) - |> with (ApiPackage.docs Docs.selection) + |> SelectionSet.with (ApiPackage.docs Docs.selection) in Command.GraphqlQuery { url = "/api" diff --git a/assets/src/Pages/Editor/Views/StatusBar.elm b/assets/src/Pages/Editor/Views/StatusBar.elm index 0bb4d40c..eeb856b2 100644 --- a/assets/src/Pages/Editor/Views/StatusBar.elm +++ b/assets/src/Pages/Editor/Views/StatusBar.elm @@ -42,14 +42,18 @@ view config = , textDecoration none , fontSize (px 16) ] - [ Attributes.href "/a/terms/2#privacy" ] + [ Attributes.href "/a/terms/3#privacy" + , Attributes.target "_self" + ] [ Html.text "Privacy" ] , Html.styled Html.a [ color Theme.primaryForeground , textDecoration none , fontSize (px 16) ] - [ Attributes.href "/a/terms/2#terms" ] + [ Attributes.href "/a/terms/3#terms" + , Attributes.target "_self" + ] [ Html.text "Terms" ] ] , Html.styled Html.div diff --git a/assets/src/Pages/Embed/Effects.elm b/assets/src/Pages/Embed/Effects.elm index 662cf780..dbc93bf0 100644 --- a/assets/src/Pages/Embed/Effects.elm +++ b/assets/src/Pages/Embed/Effects.elm @@ -20,7 +20,7 @@ import Ellie.Constants as Constants import Elm.Error as Error exposing (Error) import Elm.Package as Package exposing (Package) import Graphql.Http -import Graphql.SelectionSet as SelectionSet exposing (SelectionSet(..), hardcoded, with) +import Graphql.SelectionSet as SelectionSet exposing (SelectionSet(..), with) import Json.Encode as Encode exposing (Value) import Pages.Embed.Types.EmbedUpdate as EmbedUpdate exposing (EmbedUpdate) import Pages.Embed.Types.Revision as Revision exposing (Revision) @@ -30,20 +30,20 @@ getRevision : Revision.Id -> Command (Result (Graphql.Http.Error ()) Revision) getRevision revisionId = let query = - ApiQuery.selection identity - |> with (ApiQuery.revision arguments revisionQuery) + SelectionSet.succeed identity + |> SelectionSet.with (ApiQuery.revision arguments revisionQuery) arguments = { id = ApiScalar.PrettyId revisionId } revisionQuery = - ApiRevision.selection Revision - |> with ApiRevision.htmlCode - |> with ApiRevision.elmCode - |> with (ApiRevision.packages Package.selection) - |> with (ApiHelpers.defaultField "" ApiRevision.title) - |> with (ApiHelpers.versionField ApiRevision.elmVersion) + SelectionSet.succeed Revision + |> SelectionSet.with ApiRevision.htmlCode + |> SelectionSet.with ApiRevision.elmCode + |> SelectionSet.with (ApiRevision.packages Package.selection) + |> SelectionSet.with (ApiHelpers.defaultField "" ApiRevision.title) + |> SelectionSet.with (ApiHelpers.versionField ApiRevision.elmVersion) in Command.GraphqlQuery { url = "/api" @@ -59,12 +59,12 @@ runEmbed : Revision.Id -> Command (Result (Graphql.Http.Error ()) (Maybe (Maybe runEmbed revisionId = let selection = - ApiMutation.selection identity - |> with (ApiMutation.runEmbed arguments embedReadySelection) + SelectionSet.succeed identity + |> SelectionSet.with (ApiMutation.runEmbed arguments embedReadySelection) embedReadySelection = - ApiEmbedReady.selection identity - |> with (ApiEmbedReady.error Error.selection) + SelectionSet.succeed identity + |> SelectionSet.with (ApiEmbedReady.error Error.selection) arguments = { id = ApiScalar.PrettyId revisionId @@ -83,22 +83,22 @@ embedUpdates : Revision.Id -> Subscription EmbedUpdate embedUpdates revisionId = let selection = - ApiSubscription.selection identity - |> with (ApiSubscription.embed arguments embedUpdateSelection) + SelectionSet.succeed identity + |> SelectionSet.with (ApiSubscription.embed arguments embedUpdateSelection) arguments = { id = ApiScalar.PrettyId revisionId } embedUpdateSelection = - ApiEmbedUpdate.selection (Maybe.withDefault (EmbedUpdate.Failed "Missing data")) - [ ApiEmbedReady.selection EmbedUpdate.Compiled - |> with (ApiEmbedReady.error Error.selection) - |> ApiEmbedUpdate.onEmbedReady - , ApiEmbedFailed.selection EmbedUpdate.Failed - |> with ApiEmbedFailed.message - |> ApiEmbedUpdate.onEmbedFailed - ] + ApiEmbedUpdate.fragments + { onEmbedReady = + SelectionSet.succeed EmbedUpdate.Compiled + |> SelectionSet.with (ApiEmbedReady.error Error.selection) + , onEmbedFailed = + SelectionSet.succeed EmbedUpdate.Failed + |> SelectionSet.with ApiEmbedFailed.message + } in Subscription.AbsintheSubscription { url = Constants.socketOrigin ++ "/api/sockets", token = Nothing } diff --git a/assets/vendor/Graphql/Codec.elm b/assets/vendor/Graphql/Codec.elm new file mode 100644 index 00000000..018d77b6 --- /dev/null +++ b/assets/vendor/Graphql/Codec.elm @@ -0,0 +1,20 @@ +module Graphql.Codec exposing (Codec) + +{-| This module is used when you define custom scalars codecs for your schema. +See an example and steps for how to set this up in your codebase here: + + +@docs Codec + +-} + +import Json.Decode +import Json.Encode + + +{-| A simple definition of a decoder/encoder pair. +-} +type alias Codec elmValue = + { encoder : elmValue -> Json.Encode.Value + , decoder : Json.Decode.Decoder elmValue + } diff --git a/assets/vendor/Graphql/Document/Field.elm b/assets/vendor/Graphql/Document/Field.elm index 3d145599..fc92b77e 100644 --- a/assets/vendor/Graphql/Document/Field.elm +++ b/assets/vendor/Graphql/Document/Field.elm @@ -16,7 +16,7 @@ hashedAliasName field = maybeAliasHash : RawField -> Maybe String maybeAliasHash field = - case field of + (case field of Composite name arguments children -> if List.isEmpty arguments then Nothing @@ -24,20 +24,17 @@ maybeAliasHash field = else arguments |> Argument.serialize - |> Murmur3.hashString 0 - |> String.fromInt |> Just - Leaf name arguments -> - if List.isEmpty arguments then - Nothing - - else - arguments - |> Argument.serialize - |> Murmur3.hashString 0 - |> String.fromInt - |> Just + Leaf { typeString, fieldName } arguments -> + arguments + |> Argument.serialize + |> List.singleton + |> List.append [ typeString ] + |> String.concat + |> Just + ) + |> Maybe.map (Murmur3.hashString 0 >> String.fromInt) alias : RawField -> Maybe String @@ -88,7 +85,7 @@ serialize aliasName mIndentationLevel field = ++ "}" |> Just - Leaf fieldName args -> + Leaf { fieldName } args -> Just (fieldName ++ Argument.serialize args) ) |> Maybe.map diff --git a/assets/vendor/Graphql/Field.elm b/assets/vendor/Graphql/Field.elm deleted file mode 100644 index 630ebff7..00000000 --- a/assets/vendor/Graphql/Field.elm +++ /dev/null @@ -1,178 +0,0 @@ -module Graphql.Field exposing - ( map - , mapOrFail, nonNullOrFail, nonNullElementsOrFail - , Field(..) - ) - -{-| `Field`s are automatically generated by the `@dillonkearns/elm-graphql` CLI command. -You can use `Graphql.Field.map` to transform a value. - - -## Safe Transformations - -@docs map - - -## Result (`...OrFail`) Transformations - -**Warning** When you use these functions, you lose the guarantee that the -server response will decode successfully. - -These helpers, though convenient, will cause your entire decoder to fail if -it ever maps to an `Err` instead of an `Ok` `Result`. - -@docs mapOrFail, nonNullOrFail, nonNullElementsOrFail - - -## Types - -@docs Field - --} - -import Graphql.RawField as Field exposing (RawField) -import Json.Decode as Decode exposing (Decoder) - - -{-| -} -type Field decodesTo typeLock - = Field RawField (Decoder decodesTo) - - -{-| Maps the data coming back from the GraphQL endpoint. In this example, -`User.name` is a function that the `@dillonkearns/elm-graphql` CLI tool created which tells us -that the `name` field on a `User` object is a String according to your GraphQL -schema. - - import Api.Object - import Api.Object.User as User - import Graphql.Field as Field - import Graphql.SelectionSet exposing (SelectionSet, with) - - human : SelectionSet String Api.Object.User - human = - User.selection identity - |> with - (User.name - |> Field.map String.toUpper - ) - -You can also map to values of a different type (`String -> Int`, for example), see -[`examples/StarWars.elm`](https://github.com/dillonkearns/elm-graphql/blob/master/examples/src/Starwars.elm) for more advanced example. - --} -map : (decodesTo -> mapsTo) -> Field decodesTo typeLock -> Field mapsTo typeLock -map mapFunction (Field field decoder) = - Field field (Decode.map mapFunction decoder) - - -{-| If the map function provided returns an `Ok` `Result`, it will map to that value. -If it returns an `Err`, the _entire_ response will fail to decode. - - import Date exposing (Date) - import Github.Object - import Github.Object.Repository - import Github.Scalar - import Graphql.Field as Field exposing (Field) - - createdAt : Field Date Github.Object.Repository - createdAt = - Github.Object.Repository.createdAt - |> Field.mapOrFail - (\(Github.Scalar.DateTime dateTime) -> - Date.fromString dateTime - ) - --} -mapOrFail : (decodesTo -> Result String mapsTo) -> Field decodesTo typeLock -> Field mapsTo typeLock -mapOrFail mapFunction (Field field decoder) = - decoder - |> Decode.map mapFunction - |> Decode.andThen - (\result -> - case result of - Ok value -> - Decode.succeed value - - Err errorMessage -> - Decode.fail ("Check your code for calls to mapOrFail, your map function returned an `Err` with the message: " ++ errorMessage) - ) - |> Field field - - -{-| Effectively turns an attribute that is `String` => `String!`, or `User` => -`User!` (if you're not familiar with the GraphQL type language notation, learn more -[here](http://graphql.org/learn/schema/#type-language)). - -This will cause your _entire_ decoder to fail if the field comes back as null. -It's far better to fix your schema then to use this escape hatch! - --} -nonNullOrFail : Field (Maybe decodesTo) typeLock -> Field decodesTo typeLock -nonNullOrFail (Field field decoder) = - decoder - |> Decode.andThen - (\result -> - case result of - Just value -> - Decode.succeed value - - Nothing -> - Decode.fail "Expected non-null but got null, check for calls to nonNullOrFail in your code. Ideally your schema should indicate that this is non-nullable so you don't need to use nonNullOrFail at all." - ) - |> Field field - - -{-| Effectively turns a field that is `[String]` => `[String!]`, or `[User]` => -`[User!]` (if you're not familiar with the GraphQL type language notation, learn more -[here](http://graphql.org/learn/schema/#type-language)). - -This will cause your _entire_ decoder to fail if any elements in the list for this -field comes back as null. -It's far better to fix your schema then to use this escape hatch! - -Often GraphQL schemas will contain things like `[String]` (i.e. a nullable list -of nullable strings) when they really mean `[String!]!` (a non-nullable list of -non-nullable strings). You can chain together these nullable helpers if for some -reason you can't go in and fix this in the schema, for example: - - releases : SelectionSet (List Release) Github.Object.ReleaseConnection - releases = - Github.Object.ReleaseConnection.selection identity - |> with - (Github.Object.ReleaseConnection.nodes release - |> Field.nonNullOrFail - |> Field.nonNullElementsOrFail - ) - -Without the `Field.nonNull...` transformations here, the type would be -`SelectionSet (Maybe (List (Maybe Release))) Github.Object.ReleaseConnection`. - --} -nonNullElementsOrFail : Field (List (Maybe decodesTo)) typeLock -> Field (List decodesTo) typeLock -nonNullElementsOrFail (Field field decoder) = - decoder - |> Decode.andThen - (\result -> - case combineMaybeList result of - Nothing -> - Decode.fail "Expected only non-null list elements but found a null. Check for calls to nonNullElementsOrFail in your code. Ideally your schema should indicate that this is non-nullable so you don't need to use nonNullElementsOrFail at all." - - Just listWithoutNulls -> - Decode.succeed listWithoutNulls - ) - |> Field field - - -combineMaybeList : List (Maybe a) -> Maybe (List a) -combineMaybeList listOfMaybes = - let - step maybeElement accumulator = - case maybeElement of - Nothing -> - Nothing - - Just element -> - Maybe.map ((::) element) accumulator - in - List.foldr step (Just []) listOfMaybes diff --git a/assets/vendor/Graphql/Http.elm b/assets/vendor/Graphql/Http.elm index a9c197e7..faa390ee 100644 --- a/assets/vendor/Graphql/Http.elm +++ b/assets/vendor/Graphql/Http.elm @@ -1,10 +1,10 @@ module Graphql.Http exposing - ( Request, Error(..) + ( Request, HttpError(..), Error, RawError(..) , queryRequest, mutationRequest, queryRequestWithHttpGet , QueryRequestMethod(..) , withHeader, withTimeout, withCredentials, withQueryParams - , send, toTask - , mapError, ignoreParsedErrorData, fromHttpError, discardParsedErrorData + , send, sendWithTracker, toTask + , mapError, discardParsedErrorData, withSimpleHttpError , parseableErrorAsSuccess ) @@ -17,7 +17,7 @@ The builder syntax is inspired by Luke Westby's ## Data Types -@docs Request, Error +@docs Request, HttpError, Error, RawError ## Begin `Request` Pipeline @@ -33,12 +33,12 @@ The builder syntax is inspired by Luke Westby's ## Perform `Request` -@docs send, toTask +@docs send, sendWithTracker, toTask ## Map `Error`s -@docs mapError, ignoreParsedErrorData, fromHttpError, discardParsedErrorData +@docs mapError, discardParsedErrorData, withSimpleHttpError ## Error Handling Strategies @@ -87,6 +87,16 @@ type Request decodesTo } +type alias ReadyRequest decodesTo = + { method : String + , headers : List Http.Header + , url : String + , body : Http.Body + , timeout : Maybe Float + , decoder : Json.Decode.Decoder (DataResult decodesTo) + } + + {-| Union type to pass in to `queryRequestWithHttpGet`. Only applies to queries. Mutations don't accept this configuration option and will always use POST. -} @@ -162,12 +172,55 @@ mutationRequest baseUrl mutationSelectionSet = |> Request +{-| An alias for the default kind of Error. See the `RawError` for the full +type. +-} +type alias Error parsedData = + RawError parsedData HttpError + + {-| Represents the two types of errors you can get, an Http error or a GraphQL error. See the `Graphql.Http.GraphqlError` module docs for more details. -} -type Error parsedData +type RawError parsedData httpError = GraphqlError (GraphqlError.PossiblyParsedData parsedData) (List GraphqlError.GraphqlError) - | HttpError Http.Error + | HttpError httpError + + +toSimpleHttpError : HttpError -> Http.Error +toSimpleHttpError httpError = + case httpError of + BadUrl url -> + Http.BadUrl url + + Timeout -> + Http.Timeout + + NetworkError -> + Http.NetworkError + + BadStatus metadata body -> + Http.BadStatus metadata.statusCode + + BadPayload jsonError -> + Http.BadBody (Json.Decode.errorToString jsonError) + + +{-| An Http Error. A Request can fail in a few ways: + + - `BadUrl` means you did not provide a valid URL. + - `Timeout` means it took too long to get a response. + - `NetworkError` means the user turned off their wifi, went in a cave, etc. + - `BadStatus` means you got a response back, but the status code indicates failure. The second argument in the payload is the response body. + - `BadPayload` means you got a response back with a nice status code, but the body of the response was something unexpected. The String in this case is a debugging message that explains what went wrong with your JSON decoder or whatever. + +-} +type HttpError + = BadUrl String + | Timeout + | NetworkError + | BadStatus Http.Metadata String + | BadPayload Json.Decode.Error {-| Map the error data if it is `ParsedData`. @@ -187,19 +240,44 @@ mapError mapFn error = HttpError httpError -{-| Turn an `Http.Error` into a `Graphql.Http.Error`. --} -fromHttpError : Http.Error -> Error () -fromHttpError httpError = - HttpError httpError +{-| Useful when you want to combine together an Http response with a Graphql +request response. + -- this is just the type that our query decodes to + type alias Response = + { hello : String } + + type Msg + = GotResponse (RemoteData (Graphql.Http.RawError Response Http.Error) Response) + + request = + query + |> Graphql.Http.queryRequest "https://some-graphql-api.com" + |> Graphql.Http.send + (Graphql.Http.withSimpleHttpError + >> RemoteData.fromResult + >> GotResponse + ) + + combinedResponses = + RemoteData.map2 Tuple.pair + model.graphqlResponse + (model.plainHttpResponse |> RemoteData.mapError Graphql.Http.HttpError) -{-| Useful when you don't want to deal with the recovered data if there is `ParsedData`. -Just a shorthand for `mapError` that will turn any `ParsedData` into `()`. -} -ignoreParsedErrorData : Error parsedData -> Error () -ignoreParsedErrorData error = - mapError (\_ -> ()) error +withSimpleHttpError : Result (Error parsedData) decodesTo -> Result (RawError parsedData Http.Error) decodesTo +withSimpleHttpError result = + case result of + Ok decodesTo -> + Ok decodesTo + + Err error -> + case error of + HttpError httpError -> + httpError |> toSimpleHttpError |> HttpError |> Err + + GraphqlError possiblyParsed graphqlErrorList -> + GraphqlError possiblyParsed graphqlErrorList |> Err {-| Useful when you don't want to deal with the recovered data if there is `ParsedData`. @@ -288,7 +366,7 @@ type alias DataResult parsedData = Result ( GraphqlError.PossiblyParsedData parsedData, List GraphqlError.GraphqlError ) parsedData -convertResult : Result Http.Error (DataResult decodesTo) -> Result (Error decodesTo) decodesTo +convertResult : Result HttpError (DataResult decodesTo) -> Result (Error decodesTo) decodesTo convertResult httpResult = case httpResult of Ok successOrError -> @@ -331,15 +409,118 @@ any data that made it through in the response. -} send : (Result (Error decodesTo) decodesTo -> msg) -> Request decodesTo -> Cmd msg -send resultToMessage elmGraphqlRequest = - elmGraphqlRequest - |> toRequest - |> Http.send (convertResult >> resultToMessage) - - -toRequest : Request decodesTo -> Http.Request (DataResult decodesTo) -toRequest (Request request) = - (case request.details of +send resultToMessage ((Request request) as fullRequest) = + fullRequest + |> toHttpRequestRecord resultToMessage + |> (if request.withCredentials then + Http.riskyRequest + + else + Http.request + ) + + +{-| Exactly like `Graphql.Http.request` except it allows you to use the `String` +passed in as the tracker to [`track`](https://package.elm-lang.org/packages/elm/http/2.0.0/Http#track) +and [`cancel`](https://package.elm-lang.org/packages/elm/http/2.0.0/Http#cancel) +requests using the core Elm `Http` package (see +[the `Http.request` docs](https://package.elm-lang.org/packages/elm/http/2.0.0/Http#request)) +-} +sendWithTracker : String -> (Result (Error decodesTo) decodesTo -> msg) -> Request decodesTo -> Cmd msg +sendWithTracker tracker resultToMessage ((Request request) as fullRequest) = + fullRequest + |> toHttpRequestRecord resultToMessage + |> (\requestRecord -> { requestRecord | tracker = Just tracker }) + |> (if request.withCredentials then + Http.riskyRequest + + else + Http.request + ) + + +toHttpRequestRecord : + (Result (Error decodesTo) decodesTo -> msg) + -> Request decodesTo + -> + { method : String + , headers : List Http.Header + , url : String + , body : Http.Body + , expect : Http.Expect msg + , timeout : Maybe Float + , tracker : Maybe String + } +toHttpRequestRecord resultToMessage ((Request request) as fullRequest) = + fullRequest + |> toReadyRequest + |> (\readyRequest -> + { method = readyRequest.method + , headers = readyRequest.headers + , url = readyRequest.url + , body = readyRequest.body + , expect = expectJson (convertResult >> resultToMessage) readyRequest.decoder + , timeout = readyRequest.timeout + , tracker = Nothing + } + ) + + +expectJson : (Result HttpError decodesTo -> msg) -> Json.Decode.Decoder decodesTo -> Http.Expect msg +expectJson toMsg decoder = + Http.expectStringResponse toMsg <| + \response -> + case response of + Http.BadUrl_ url -> + BadUrl url |> Err + + Http.Timeout_ -> + Timeout |> Err + + Http.NetworkError_ -> + NetworkError |> Err + + Http.BadStatus_ metadata body -> + BadStatus metadata body |> Err + + Http.GoodStatus_ metadata body -> + case Json.Decode.decodeString decoder body of + Ok value -> + Ok value + + Err err -> + BadPayload err |> Err + + +jsonResolver : Json.Decode.Decoder decodesTo -> Http.Resolver HttpError decodesTo +jsonResolver decoder = + Http.stringResolver <| + \response -> + case response of + Http.BadUrl_ url -> + BadUrl url |> Err + + Http.Timeout_ -> + Timeout |> Err + + Http.NetworkError_ -> + NetworkError |> Err + + Http.BadStatus_ metadata body -> + BadStatus metadata body |> Err + + Http.GoodStatus_ metadata body -> + case Json.Decode.decodeString decoder body of + Ok value -> + Ok value + + Err err -> + BadPayload err |> Err + + +toReadyRequest : Request decodesTo -> ReadyRequest decodesTo +toReadyRequest (Request request) = + case request.details of Query forcedRequestMethod querySelectionSet -> let queryRequestDetails = @@ -368,9 +549,8 @@ toRequest (Request request) = , headers = request.headers , url = queryRequestDetails.url , body = queryRequestDetails.body - , expect = Http.expectJson (decoderOrError request.expect) + , decoder = decoderOrError request.expect , timeout = request.timeout - , withCredentials = request.withCredentials } Mutation mutationSelectionSet -> @@ -385,26 +565,45 @@ toRequest (Request request) = ) ] ) - , expect = Http.expectJson (decoderOrError request.expect) + , decoder = decoderOrError request.expect , timeout = request.timeout - , withCredentials = request.withCredentials } - ) - |> Http.request {-| Convert a Request to a Task. See `Graphql.Http.send` for an example of how to build up a Request. -} toTask : Request decodesTo -> Task (Error decodesTo) decodesTo -toTask request = - request - |> toRequest - |> Http.toTask +toTask ((Request request) as fullRequest) = + fullRequest + |> toReadyRequest + |> (\readyRequest -> + (if request.withCredentials then + Http.riskyTask + + else + Http.task + ) + { method = readyRequest.method + , headers = readyRequest.headers + , url = readyRequest.url + , body = readyRequest.body + , resolver = resolver fullRequest + , timeout = readyRequest.timeout + } + ) |> Task.mapError HttpError |> Task.andThen failTaskOnHttpSuccessWithErrors +resolver : Request decodesTo -> Http.Resolver HttpError (DataResult decodesTo) +resolver request = + request + |> toReadyRequest + |> .decoder + |> jsonResolver + + failTaskOnHttpSuccessWithErrors : DataResult decodesTo -> Task (Error decodesTo) decodesTo failTaskOnHttpSuccessWithErrors successOrError = case successOrError of @@ -473,7 +672,12 @@ withTimeout timeout (Request request) = Request { request | timeout = Just timeout } -{-| Set with credentials to true. +{-| Set with credentials to true. See [the `XMLHttpRequest/withCredentials` docs](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/withCredentials) +to understand exactly what happens. + +Under the hood, this will use either [`Http.riskyRequest`](https://package.elm-lang.org/packages/elm/http/latest/Http#riskyRequest) +or [`Http.riskyTask`](https://package.elm-lang.org/packages/elm/http/latest/Http#riskyTask). + -} withCredentials : Request decodesTo -> Request decodesTo withCredentials (Request request) = diff --git a/assets/vendor/Graphql/Internal/Builder/Object.elm b/assets/vendor/Graphql/Internal/Builder/Object.elm index b569a7fe..7f74ffba 100644 --- a/assets/vendor/Graphql/Internal/Builder/Object.elm +++ b/assets/vendor/Graphql/Internal/Builder/Object.elm @@ -1,20 +1,21 @@ -module Graphql.Internal.Builder.Object exposing (fieldDecoder, selection, selectionField, interfaceSelection, unionSelection, scalarDecoder) +module Graphql.Internal.Builder.Object exposing (scalarDecoder, exhuastiveFragmentSelection, buildFragment, selectionForField, selectionForCompositeField) {-| **WARNING** `Graphql.Interal` modules are used by the `@dillonkearns/elm-graphql` command line code generator tool. They should not be consumed through hand-written code. Internal functions for use by auto-generated code from the `@dillonkearns/elm-graphql` CLI. -@docs fieldDecoder, selection, selectionField, interfaceSelection, unionSelection, scalarDecoder +@docs scalarDecoder, exhuastiveFragmentSelection, buildFragment, selectionForField, selectionForCompositeField -} import Dict -import Graphql.Field as Field exposing (Field(..)) +import Graphql.Document.Field import Graphql.Internal.Builder.Argument exposing (Argument) import Graphql.RawField exposing (RawField) import Graphql.SelectionSet exposing (FragmentSelectionSet(..), SelectionSet(..)) import Json.Decode as Decode exposing (Decoder) +import String.Interpolate exposing (interpolate) {-| Decoder for scalars for use in auto-generated code. @@ -40,21 +41,33 @@ scalarDecoder = {-| Refer to a field in auto-generated code. -} -fieldDecoder : String -> List Argument -> Decoder decodesTo -> Field decodesTo lockedTo -fieldDecoder fieldName args decoder = - Field (leaf fieldName args) decoder +selectionForField : String -> String -> List Argument -> Decoder decodesTo -> SelectionSet decodesTo lockedTo +selectionForField typeString fieldName args decoder = + let + newLeaf = + leaf { typeString = typeString, fieldName = fieldName } args + in + SelectionSet [ newLeaf ] + (Decode.field + (Graphql.Document.Field.hashedAliasName newLeaf) + decoder + ) {-| Refer to an object in auto-generated code. -} -selectionField : +selectionForCompositeField : String -> List Argument -> SelectionSet a objectTypeLock -> (Decoder a -> Decoder b) - -> Field b lockedTo -selectionField fieldName args (SelectionSet fields decoder) decoderTransform = - Field (composite fieldName args fields) (decoderTransform decoder) + -> SelectionSet b lockedTo +selectionForCompositeField fieldName args (SelectionSet fields decoder) decoderTransform = + SelectionSet [ composite fieldName args fields ] + (Decode.field + (Graphql.Document.Field.hashedAliasName (composite fieldName args fields)) + (decoderTransform decoder) + ) composite : String -> List Argument -> List RawField -> RawField @@ -62,69 +75,42 @@ composite fieldName args fields = Graphql.RawField.Composite fieldName args fields -leaf : String -> List Argument -> RawField -leaf fieldName args = - Graphql.RawField.Leaf fieldName args +leaf : { typeString : String, fieldName : String } -> List Argument -> RawField +leaf details args = + Graphql.RawField.Leaf details args -{-| Used to create the `selection` functions in auto-generated code. +{-| Used to create FragmentSelectionSets for type-specific fragmentsin auto-generated code. -} -selection : (a -> constructor) -> SelectionSet (a -> constructor) typeLock -selection constructor = - SelectionSet [] (Decode.succeed constructor) +buildFragment : String -> SelectionSet decodesTo selectionLock -> FragmentSelectionSet decodesTo fragmentLock +buildFragment fragmentTypeName (SelectionSet fields decoder) = + FragmentSelectionSet fragmentTypeName fields decoder -{-| Used to create the `selection` functions in auto-generated code for interfaces. +{-| Used to create the `selection` functions in auto-generated code for exhuastive fragments. -} -interfaceSelection : List (FragmentSelectionSet typeSpecific typeLock) -> (Maybe typeSpecific -> a -> b) -> SelectionSet (a -> b) typeLock -interfaceSelection typeSpecificSelections constructor = +exhuastiveFragmentSelection : List (FragmentSelectionSet decodesTo typeLock) -> SelectionSet decodesTo typeLock +exhuastiveFragmentSelection typeSpecificSelections = let - typeNameDecoder = - \typeName -> - typeSpecificSelections - |> List.map (\(FragmentSelectionSet thisTypeName fields decoder) -> ( thisTypeName, decoder )) - |> Dict.fromList - |> Dict.get typeName - |> Maybe.map (Decode.map Just) - |> Maybe.withDefault (Decode.succeed Nothing) - selections = typeSpecificSelections |> List.map (\(FragmentSelectionSet typeName fields decoder) -> composite ("...on " ++ typeName) [] fields) in - SelectionSet (leaf "__typename" [] :: selections) - (Decode.map2 (|>) - (Decode.string - |> Decode.field "__typename" - |> Decode.andThen typeNameDecoder - ) - (Decode.succeed constructor) + SelectionSet (Graphql.RawField.typename :: selections) + (Decode.string + |> Decode.field (Graphql.Document.Field.hashedAliasName Graphql.RawField.typename) + |> Decode.andThen + (\typeName -> + typeSpecificSelections + |> List.map (\(FragmentSelectionSet thisTypeName fields decoder) -> ( thisTypeName, decoder )) + |> Dict.fromList + |> Dict.get typeName + |> Maybe.withDefault (exhaustiveFailureMessage typeSpecificSelections typeName |> Decode.fail) + ) ) -{-| Used to create the `selection` functions in auto-generated code for unions. --} -unionSelection : List (FragmentSelectionSet typeSpecific typeLock) -> (Maybe typeSpecific -> a) -> SelectionSet a typeLock -unionSelection typeSpecificSelections constructor = - let - typeNameDecoder = - \typeName -> - typeSpecificSelections - |> List.map (\(FragmentSelectionSet thisTypeName fields decoder) -> ( thisTypeName, decoder )) - |> Dict.fromList - |> Dict.get typeName - |> Maybe.map (Decode.map Just) - |> Maybe.withDefault (Decode.succeed Nothing) - - selections = - typeSpecificSelections - |> List.map (\(FragmentSelectionSet typeName fields decoder) -> composite ("...on " ++ typeName) [] fields) - in - SelectionSet (leaf "__typename" [] :: selections) - (Decode.map2 (|>) - (Decode.string - |> Decode.field "__typename" - |> Decode.andThen typeNameDecoder - ) - (Decode.succeed constructor) - ) +exhaustiveFailureMessage typeSpecificSelections typeName = + interpolate + "Unhandled type `{0}` in exhaustive fragment handling. The following types had handlers registered to handle them: [{1}]. This happens if you are parsing either a Union or Interface. Do you need to rerun the `@dillonkearns/elm-graphql` command line tool?" + [ typeName, typeSpecificSelections |> List.map (\(FragmentSelectionSet fragmentType fields decoder) -> fragmentType) |> String.join ", " ] diff --git a/assets/vendor/Graphql/Internal/Encode.elm b/assets/vendor/Graphql/Internal/Encode.elm index e82a439d..b7be23d9 100644 --- a/assets/vendor/Graphql/Internal/Encode.elm +++ b/assets/vendor/Graphql/Internal/Encode.elm @@ -1,5 +1,5 @@ module Graphql.Internal.Encode exposing - ( null, bool, enum, int, list, string, object, maybe, maybeObject, optional, float + ( null, bool, enum, int, list, string, object, maybe, maybeObject, optional, float, fromJson , serialize , Value ) @@ -10,7 +10,7 @@ code generator tool. They should not be consumed through hand-written code. `Graphql.Internal.Encode.Value`s are low-level details used by generated code. They are only used by the code generated by the `@dillonkearns/elm-graphql` CLI tool. -@docs null, bool, enum, int, list, string, object, maybe, maybeObject, optional, float +@docs null, bool, enum, int, list, string, object, maybe, maybeObject, optional, float, fromJson @docs serialize @docs Value @@ -29,6 +29,14 @@ type Value | Object (List ( String, Value )) +{-| Encode directly from `Json.Encode.Value`. For internal use by +custom scalar codecs. +-} +fromJson : Json.Encode.Value -> Value +fromJson jsonValue = + jsonValue |> Json + + {-| Encode a list of key-value pairs into an object -} object : List ( String, Value ) -> Value diff --git a/assets/vendor/Graphql/OptionalArgument.elm b/assets/vendor/Graphql/OptionalArgument.elm index 94edcd9e..b1963ab3 100644 --- a/assets/vendor/Graphql/OptionalArgument.elm +++ b/assets/vendor/Graphql/OptionalArgument.elm @@ -19,12 +19,12 @@ module Graphql.OptionalArgument exposing import Api.Query as Query import Graphql.Operation exposing (RootQuery) import Graphql.OptionalArgument exposing (OptionalArgument(Null, Present)) - import Graphql.SelectionSet exposing (SelectionSet, with) + import Graphql.SelectionSet as SelectionSet exposing (SelectionSet, with) query : SelectionSet Response RootQuery query = - Query.selection Response + SelectionSet.succeed Response |> with (Query.human { id = "1004" } human) |> with (Query.human { id = "1001" } human) |> with diff --git a/assets/vendor/Graphql/RawField.elm b/assets/vendor/Graphql/RawField.elm index bdb5f080..b6ca8cff 100644 --- a/assets/vendor/Graphql/RawField.elm +++ b/assets/vendor/Graphql/RawField.elm @@ -5,7 +5,7 @@ import Graphql.Internal.Builder.Argument as Argument exposing (Argument) type RawField = Composite String (List Argument) (List RawField) - | Leaf String (List Argument) + | Leaf { typeString : String, fieldName : String } (List Argument) name : RawField -> String @@ -14,10 +14,10 @@ name field = Composite fieldName argumentList fieldList -> fieldName - Leaf fieldName argumentList -> + Leaf { typeString, fieldName } argumentList -> fieldName typename : RawField typename = - Leaf "__typename" [] + Leaf { typeString = "", fieldName = "__typename" } [] diff --git a/assets/vendor/Graphql/SelectionSet.elm b/assets/vendor/Graphql/SelectionSet.elm index ca52de97..3b242aa4 100644 --- a/assets/vendor/Graphql/SelectionSet.elm +++ b/assets/vendor/Graphql/SelectionSet.elm @@ -1,62 +1,252 @@ module Graphql.SelectionSet exposing - ( with, hardcoded, empty, map, succeed, fieldSelection - , map2, withFragment + ( map + , map2, map3, map4, map5, map6, map7, map8 + , withDefault + , with, hardcoded, succeed + , empty , SelectionSet(..), FragmentSelectionSet(..) + , mapOrFail, nonNullOrFail, nonNullElementsOrFail + , list, dict ) -{-| The auto-generated code from the `@dillonkearns/elm-graphql` CLI will provide `selection` -functions for Objects, Interfaces, and Unions in your GraphQL schema. -These functions build up a `Graphql.SelectionSet` which describes a set -of fields to retrieve. The `SelectionSet` is built up in a pipeline similar to how -[`Json.Decode.Pipeline`](http://package.elm-lang.org/packages/NoRedInk/elm-decode-pipeline/latest) -builds up decoders. +{-| The auto-generated code from the `@dillonkearns/elm-graphql` CLI provides +functions that you can use to build up `SelectionSet`s for the GraphQL Objects, +Interfaces, and Unions in your GraphQL schema. -For example, if you had a top-level query `human(id: ID!)` which returns an object -of type `Human`, you could build the following GraphQL query document: +Note that in these examples, all of the modules that start with `StarWars.` or `Github.` +are generated by running the [`@dillonkearns/elm-graphql`](https://npmjs.com/package/@dillonkearns/elm-graphql) +command line tool. + +There are lots more end-to-end examples in the +[`examples` ](https://github.com/dillonkearns/elm-graphql/tree/master/examples/src) +folder. + +With `dillonkearns/elm-graphql`, a `SelectionSet` describes a set of fields to +retrieve. It contains all the information needed to make the request and decode +the response (you don't hand-code the decoders yourself, they are auto-generated +for you!). + + +## Building `SelectionSet`s + +A `SelectionSet` in `dillonkearns/elm-graphql` represents a set of +zero or more things which are either sub-`SelectionSet`s or leaf fields. + +For example, `SelectionSet.empty` is the most basic `SelectionSet` you could build. + + import Graphql.Operation exposing (RootQuery) + import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) + import StarWars.Query as Query + + query : SelectionSet () RootQuery + query = + SelectionSet.empty + +You can execute this query, but the result won't be very interesting! + +In the StarWars API example in the [`examples`](https://github.com/dillonkearns/elm-graphql/tree/master/examples/src) +folder, there is a top-level query field called `hello`. So you could also +build a valid query to get `hello`: + + import Graphql.Operation exposing (RootQuery) + import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) + import StarWars.Query as Query + + query : SelectionSet Response RootQuery + query = + Query.hello + +This is the equivalent of this raw GraphQL query: query { - human(id: 1001) { - name - id - } + hello } -In this example, the `SelectionSet` on `human` is: +If we wanted to query for two top-level fields it's just as easy. Let's see how we would grab both the `hello` and `goodbye` fields like this: + + query { + hello + goodbye + } + +The only difference for combining two `SelectionSet`s is that you need to define which +function we want to use to combine the two fields together into one piece of data. +Let's just define our own function for now, called `welcomeMessage`. + + import Graphql.Operation exposing (RootQuery) + import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) + import StarWars.Query + + welcomeMessage : String -> String -> String + welcomeMessage hello today = + hello ++ "\nToday is " ++ today + + query : SelectionSet String RootQuery + query = + SelectionSet.map2 welcomeMessage + Query.hello + Query.today + + {- + If you run this query you'll get something like: + + Hello from GraphQL! + Today is Wednesday, December 5 + -} + +Great, we retrieved two fields! But often you don't want to combine the values +into a primitive, you just want to store the values in some data structure +like a record. So a very common pattern is to use record constructors as the +constructor function for `map2` (or `mapN`). Any function that takes the right number of arguments +(of the right types, order matters) will work here. + +Let's define a type alias for a record called `Phrases`. When we define this +type alias, Elm creates a function called `Phrases` that will build up a record +of that type. So we can use that function with `map2`! + + import Graphql.Operation exposing (RootQuery) + import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) + import StarWars.Query + + type alias Phrases = + { helloPhrase : String + , goodbyePhrase : String + } + + hero : SelectionSet Phrases RootQuery + hero = + SelectionSet.map2 Phrases + Query.hello + Query.goodbye + +Note that if you changed the order of `Query.hello` and `Query.goodbye`, +you would end up with a record with values under the wrong name. Order matters +with record constructors! + + +## Modularizing `SelectionSet`s + +Since both single fields and collections of fields are `SelectionSet`s in `dillonkearns/elm-graphql`, +you can easily pull in sub-`SelectionSet`s to your queries. Just treat it like you would a regular field. + +This is analagous to using a [fragment in plain GraphQL](https://graphql.org/learn/queries/#fragments). +This is a handy tool for modularizing your GraphQL queries. + +Let's say we want to query Github's GraphQL API like this: { - name - id + repository(owner: "dillonkearns", name: "elm-graphql") { + nameWithOwner + ...timestamps + stargazers(first: 0) { totalCount } + } + } + + fragment timestamps on Repository { + createdAt + updatedAt } -You could build up the above `SelectionSet` with the following `dillonkearns/elm-graphql` code: +(You can try the above query for yourself by pasting the query into the [Github query explorer](https://developer.github.com/v4/explorer/)). + +We could do the equivalent of the `timestamps` fragment with the `timestampsFragment` +we define below. + + import Github.Object + import Github.Object.Repository as Repository + import Graphql.Operation exposing (RootQuery) + import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) + import Iso8601 + import Time exposing (Posix) + + type alias Repo = + { nameWithOwner : String + , timestamps : Timestamps + } + + type alias Timestamps = + { createdAt : Posix + , updatedAt : Posix + } + + repositorySelection : SelectionSet Repo Github.Object.Repository + repositorySelection = + SelectionSet.map2 Repo + Repository.nameWithOwner + timestampsFragment - import Api.Object - import Api.Object.Human as Human - import Graphql.SelectionSet exposing (SelectionSet, with) + timestampsFragment : SelectionSet Timestamps Github.Object.Repository + timestampsFragment = + SelectionSet.map2 Timestamps + (Repository.createdAt |> mapToDateTime) + (Repository.updatedAt |> mapToDateTime) + + mapToDateTime : SelectionSet Github.Scalar.DateTime typeLock -> SelectionSet Posix typeLock + mapToDateTime = + SelectionSet.mapOrFail + (\(Github.Scalar.DateTime value) -> + Iso8601.toTime value + |> Result.mapError + (\_ -> + "Failed to parse " + ++ value + ++ " as Iso8601 DateTime." + ) + ) + +Note that both individual GraphQL fields (like `Repository.nameWithOwner`), and +collections of fields (like our `timestampsFragment`) are just `SelectionSet`s. +So whether it's a single field or a pair of fields, we can pull it into our +query using the exact same syntax! + +Modularizing your queries like this is a great idea. Dealing with these +sub-`SelectionSet`s also allows the Elm compiler to give you more precise +error messages. Just be sure to add type annotations to all your `SelectionSet`s! + + +## Mapping & Combining + +Note: If you run out of `mapN` functions for building up `SelectionSet`s, +you can use the pipeline +which makes it easier to handle large objects, but produces +lower quality type errors. + +@docs map + +@docs map2, map3, map4, map5, map6, map7, map8 + +@docs withDefault + + +## Pipelines + +As an alternative to the `mapN` functions, you can build up +`SelectionSet`s using the pipeline syntax. If you've used +the [`elm-json-decode-pipeline`](https://package.elm-lang.org/packages/NoRedInk/elm-json-decode-pipeline/latest/) +package then this style will feel very familiar. The `map2` example in this page +would translate to this using the pipeline notation: + + import Graphql.SelectionSet as SelectionSet exposing (SelectionSet, with) + import StarWars.Object + import StarWars.Object.Human as Human type alias Human = { name : String , id : String } - hero : SelectionSet Hero Api.Interface.Human + hero : SelectionSet Hero StarWars.Object.Human hero = - Human.selection Human + SelectionSet.succeed Human |> with Human.name |> with Human.id -Note that all of the modules under `Api.` in this case are generated by running -the `@dillonkearns/elm-graphql` command line tool. - -The query itself is also a `SelectionSet` so it is built up similarly. -See [this live code demo](https://rebrand.ly/graphqelm) for an example. +You can see an end-to-end example using the pipeline syntax in the [`examples`](https://github.com/dillonkearns/elm-graphql/tree/master/examples/src) +folder. -@docs with, hardcoded, empty, map, succeed, fieldSelection +@docs with, hardcoded, succeed - -## Combining - -@docs map2, withFragment +@docs empty ## Types @@ -65,10 +255,30 @@ These types are built for you by the code generated by the `@dillonkearns/elm-gr @docs SelectionSet, FragmentSelectionSet + +## Result (`...OrFail`) Transformations + +**Warning** When you use these functions, you lose the guarantee that the +server response will decode successfully. + +These helpers, though convenient, will cause your entire decoder to fail if +it ever maps to an `Err` instead of an `Ok` `Result`. + +If you're wondering why there are so many `Maybe`s in your generated code, +take a look at the +[FAQ question "Why are there so many Maybes in my responses? How do I reduce them?"](https://github.com/dillonkearns/graphqelm/blob/master/FAQ.md#why-are-there-so-many-maybes-in-my-responses-how-do-i-reduce-them). + +@docs mapOrFail, nonNullOrFail, nonNullElementsOrFail + + +## Collections of SelectionSets + +@docs list, dict + -} +import Dict exposing (Dict) import Graphql.Document.Field -import Graphql.Field as Field exposing (Field(..)) import Graphql.RawField as RawField exposing (RawField) import Json.Decode as Decode exposing (Decoder) import List.Extra @@ -80,32 +290,99 @@ type SelectionSet decodesTo typeLock = SelectionSet (List RawField) (Decoder decodesTo) -{-| Create a `SelectionSet` from a single `Field`. +{-| Maps the data coming back from the GraphQL endpoint. In this example, +`User.name` is a function that the `@dillonkearns/elm-graphql` CLI tool created which tells us +that the `name` field on a `User` object is a String according to your GraphQL +schema. - import Api.Object - import Api.Object.Human as Human + import Graphql.Operation exposing (RootQuery) import Graphql.SelectionSet exposing (SelectionSet) + import StarWars.Query as Query - humanSelection : SelectionSet String Api.Object.Human - humanSelection = - SelectionSet.fieldSelection Human.name + query : SelectionSet String RootQuery + query = + Query.hello |> SelectionSet.map String.toUpper --} -fieldSelection : Field response typeLock -> SelectionSet response typeLock -fieldSelection field = - SelectionSet [] (Decode.succeed identity) - |> with field +You can also map to values of a different type. For example, if we +use a (`String -> Int`) map function, it will change the type of our `SelectionSet` +accordingly: + + import Graphql.Operation exposing (RootQuery) + import Graphql.SelectionSet exposing (SelectionSet) + import StarWars.Query as Query + + query : SelectionSet Int RootQuery + query = + Query.hello |> SelectionSet.map String.length + +`SelectionSet.map` is also helpful when using a record to wrap a type: + import Graphql.Operation exposing (RootQuery) + import Graphql.SelectionSet exposing (SelectionSet) + import StarWars.Query as Query + + type alias Response = + { hello : String } + + query : SelectionSet Response RootQuery + query = + SelectionSet.map Response Query.hello + +Mapping is also handy when you are dealing with polymorphic GraphQL types +(Interfaces and Unions). + + import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) + import StarWars.Object.Droid as Droid + import StarWars.Object.Human as Human + import StarWars.Union + import StarWars.Union.CharacterUnion + + type HumanOrDroidDetails + = HumanDetails (Maybe String) + | DroidDetails (Maybe String) + + heroUnionSelection : SelectionSet HumanOrDroidDetails StarWars.Union.CharacterUnion + heroUnionSelection = + StarWars.Union.CharacterUnion.fragments + { onHuman = SelectionSet.map HumanDetails Human.homePlanet + , onDroid = SelectionSet.map DroidDetails Droid.primaryFunction + } -{-| Apply a function to change the result of decoding the `SelectionSet`. -} map : (a -> b) -> SelectionSet a typeLock -> SelectionSet b typeLock map mapFunction (SelectionSet selectionFields selectionDecoder) = SelectionSet selectionFields (Decode.map mapFunction selectionDecoder) +{-| A helper for mapping a SelectionSet to provide a default value. +-} +withDefault : a -> SelectionSet (Maybe a) typeLock -> SelectionSet a typeLock +withDefault default = + map (Maybe.withDefault default) + + {-| Combine two `SelectionSet`s into one, using the given combine function to merge the two data sets together. + + import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) + import StarWars.Object + import StarWars.Object.Human as Human + import StarWars.Scalar + + type alias Human = + { name : String + , id : StarWars.Scalar.Id + } + + hero : SelectionSet Hero StarWars.Object.Human + hero = + SelectionSet.map2 Human + Human.name + Human.id + +Check out the [`examples`](https://github.com/dillonkearns/elm-graphql/tree/master/examples/src) +folder, there are lots of end-to-end examples there! + -} map2 : (decodesTo1 -> decodesTo2 -> decodesToCombined) @@ -118,145 +395,151 @@ map2 combine (SelectionSet selectionFields1 selectionDecoder1) (SelectionSet sel (Decode.map2 combine selectionDecoder1 selectionDecoder2) -{-| Useful for Mutations when you don't want any data back. +{-| Combine three `SelectionSet`s into one, using the given combine function to +merge the two data sets together. This gives more clear error messages than the +pipeline syntax (using `SelectionSet.succeed` to start the pipeline +and `SelectionSet.with` to continue it). - import Api.Mutation as Mutation - import Graphql.Operation exposing (RootMutation) - import Graphql.SelectionSet as SelectionSet exposing (SelectionSet, with) - - sendChatMessage : String -> SelectionSet () RootMutation - sendChatMessage message = - Mutation.selection identity - |> with (Mutation.sendMessage { message = message } SelectionSet.empty) + import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) + import StarWars.Interface + import StarWars.Interface.Character as Character + import StarWars.Scalar --} -empty : SelectionSet () typeLock -empty = - SelectionSet [ RawField.Leaf "__typename" [] ] (Decode.succeed ()) + type alias Character = + { name : String + , id : StarWars.Scalar.Id + , friends : List String + } + characterSelection : SelectionSet Character StarWars.Interface.Character + characterSelection = + SelectionSet.map3 Character + Character.name + Character.id + (Character.friends Character.name) -{-| FragmentSelectionSet type -} -type FragmentSelectionSet decodesTo typeLock - = FragmentSelectionSet String (List RawField) (Decoder decodesTo) - - -{-| Used to pick out fields on an object. - - import Api.Enum.Episode as Episode exposing (Episode) - import Api.Object - import Api.Scalar - import Graphql.SelectionSet exposing (SelectionSet, with) +map3 : + (decodesTo1 -> decodesTo2 -> decodesTo3 -> decodesToCombined) + -> SelectionSet decodesTo1 typeLock + -> SelectionSet decodesTo2 typeLock + -> SelectionSet decodesTo3 typeLock + -> SelectionSet decodesToCombined typeLock +map3 combine (SelectionSet selectionFields1 selectionDecoder1) (SelectionSet selectionFields2 selectionDecoder2) (SelectionSet selectionFields3 selectionDecoder3) = + SelectionSet + (List.concat [ selectionFields1, selectionFields2, selectionFields3 ]) + (Decode.map3 combine selectionDecoder1 selectionDecoder2 selectionDecoder3) - type alias Hero = - { name : String - , id : Api.Scalar.Id - , appearsIn : List Episode - } - hero : SelectionSet Hero Api.Interface.Character - hero = - Character.commonSelection Hero - |> with Character.name - |> with Character.id - |> with Character.appearsIn +{-| -} +map4 : + (decodesTo1 -> decodesTo2 -> decodesTo3 -> decodesTo4 -> decodesToCombined) + -> SelectionSet decodesTo1 typeLock + -> SelectionSet decodesTo2 typeLock + -> SelectionSet decodesTo3 typeLock + -> SelectionSet decodesTo4 typeLock + -> SelectionSet decodesToCombined typeLock +map4 combine (SelectionSet selectionFields1 selectionDecoder1) (SelectionSet selectionFields2 selectionDecoder2) (SelectionSet selectionFields3 selectionDecoder3) (SelectionSet selectionFields4 selectionDecoder4) = + SelectionSet + (List.concat [ selectionFields1, selectionFields2, selectionFields3, selectionFields4 ]) + (Decode.map4 combine selectionDecoder1 selectionDecoder2 selectionDecoder3 selectionDecoder4) --} -with : Field a typeLock -> SelectionSet (a -> b) typeLock -> SelectionSet b typeLock -with (Field field fieldDecoder) (SelectionSet selectionFields selectionDecoder) = - SelectionSet (selectionFields ++ [ field ]) - (Decode.map2 (|>) - (Decode.field - (Graphql.Document.Field.hashedAliasName field) - fieldDecoder - ) - selectionDecoder - ) +{-| -} +map5 : + (decodesTo1 -> decodesTo2 -> decodesTo3 -> decodesTo4 -> decodesTo5 -> decodesToCombined) + -> SelectionSet decodesTo1 typeLock + -> SelectionSet decodesTo2 typeLock + -> SelectionSet decodesTo3 typeLock + -> SelectionSet decodesTo4 typeLock + -> SelectionSet decodesTo5 typeLock + -> SelectionSet decodesToCombined typeLock +map5 combine (SelectionSet selectionFields1 selectionDecoder1) (SelectionSet selectionFields2 selectionDecoder2) (SelectionSet selectionFields3 selectionDecoder3) (SelectionSet selectionFields4 selectionDecoder4) (SelectionSet selectionFields5 selectionDecoder5) = + SelectionSet + (List.concat [ selectionFields1, selectionFields2, selectionFields3, selectionFields4, selectionFields5 ]) + (Decode.map5 combine selectionDecoder1 selectionDecoder2 selectionDecoder3 selectionDecoder4 selectionDecoder5) -{-| Include a `SelectionSet` within a `SelectionSet`. This is the equivalent of -including a [GraphQL fragment](https://graphql.org/learn/queries/#fragments) in -plain GraphQL queries. This is a handy tool for modularizing your GraphQL queries. -([You can try the below query for yourself by pasting the query into the Github query explorer](https://developer.github.com/v4/explorer/)). +{-| -} +map6 : + (decodesTo1 -> decodesTo2 -> decodesTo3 -> decodesTo4 -> decodesTo5 -> decodesTo6 -> decodesToCombined) + -> SelectionSet decodesTo1 typeLock + -> SelectionSet decodesTo2 typeLock + -> SelectionSet decodesTo3 typeLock + -> SelectionSet decodesTo4 typeLock + -> SelectionSet decodesTo5 typeLock + -> SelectionSet decodesTo6 typeLock + -> SelectionSet decodesToCombined typeLock +map6 combine (SelectionSet selectionFields1 selectionDecoder1) (SelectionSet selectionFields2 selectionDecoder2) (SelectionSet selectionFields3 selectionDecoder3) (SelectionSet selectionFields4 selectionDecoder4) (SelectionSet selectionFields5 selectionDecoder5) (SelectionSet selectionFields6 selectionDecoder6) = + SelectionSet + (List.concat [ selectionFields1, selectionFields2, selectionFields3, selectionFields4, selectionFields5, selectionFields6 ]) + (Decode.map6 combine selectionDecoder1 selectionDecoder2 selectionDecoder3 selectionDecoder4 selectionDecoder5 selectionDecoder6) -Let's say we want to query Github's GraphQL API like this: - { - repository(owner: "dillonkearns", name: "elm-graphql") { - nameWithOwner - ...timestamps - stargazers(first: 0) { totalCount } - } - } +{-| -} +map7 : + (decodesTo1 -> decodesTo2 -> decodesTo3 -> decodesTo4 -> decodesTo5 -> decodesTo6 -> decodesTo7 -> decodesToCombined) + -> SelectionSet decodesTo1 typeLock + -> SelectionSet decodesTo2 typeLock + -> SelectionSet decodesTo3 typeLock + -> SelectionSet decodesTo4 typeLock + -> SelectionSet decodesTo5 typeLock + -> SelectionSet decodesTo6 typeLock + -> SelectionSet decodesTo7 typeLock + -> SelectionSet decodesToCombined typeLock +map7 combine (SelectionSet selectionFields1 selectionDecoder1) (SelectionSet selectionFields2 selectionDecoder2) (SelectionSet selectionFields3 selectionDecoder3) (SelectionSet selectionFields4 selectionDecoder4) (SelectionSet selectionFields5 selectionDecoder5) (SelectionSet selectionFields6 selectionDecoder6) (SelectionSet selectionFields7 selectionDecoder7) = + SelectionSet + (List.concat [ selectionFields1, selectionFields2, selectionFields3, selectionFields4, selectionFields5, selectionFields6, selectionFields7 ]) + (Decode.map7 combine selectionDecoder1 selectionDecoder2 selectionDecoder3 selectionDecoder4 selectionDecoder5 selectionDecoder6 selectionDecoder7) - fragment timestamps on Repository { - createdAt - updatedAt - } -We could do the equivalent of the `timestamps` fragment with the `timestampsFragment` -we define below. +{-| -} +map8 : + (decodesTo1 -> decodesTo2 -> decodesTo3 -> decodesTo4 -> decodesTo5 -> decodesTo6 -> decodesTo7 -> decodesTo8 -> decodesToCombined) + -> SelectionSet decodesTo1 typeLock + -> SelectionSet decodesTo2 typeLock + -> SelectionSet decodesTo3 typeLock + -> SelectionSet decodesTo4 typeLock + -> SelectionSet decodesTo5 typeLock + -> SelectionSet decodesTo6 typeLock + -> SelectionSet decodesTo7 typeLock + -> SelectionSet decodesTo8 typeLock + -> SelectionSet decodesToCombined typeLock +map8 combine (SelectionSet selectionFields1 selectionDecoder1) (SelectionSet selectionFields2 selectionDecoder2) (SelectionSet selectionFields3 selectionDecoder3) (SelectionSet selectionFields4 selectionDecoder4) (SelectionSet selectionFields5 selectionDecoder5) (SelectionSet selectionFields6 selectionDecoder6) (SelectionSet selectionFields7 selectionDecoder7) (SelectionSet selectionFields8 selectionDecoder8) = + SelectionSet + (List.concat [ selectionFields1, selectionFields2, selectionFields3, selectionFields4, selectionFields5, selectionFields6, selectionFields7, selectionFields8 ]) + (Decode.map8 combine selectionDecoder1 selectionDecoder2 selectionDecoder3 selectionDecoder4 selectionDecoder5 selectionDecoder6 selectionDecoder7 selectionDecoder8) - import Github.Object - import Github.Object.Repository as Repository - import Github.Object.StargazerConnection - import Graphql.Field as Field exposing (Field) - import Graphql.Operation exposing (RootQuery) - import Graphql.OptionalArgument exposing (OptionalArgument(..)) - import Graphql.SelectionSet as SelectionSet exposing (SelectionSet, fieldSelection, with, withFragment) - import Iso8601 - import Time exposing (Posix) - type alias Repo = - { nameWithOwner : String - , timestamps : Timestamps - , stargazersCount : Int - } +{-| Useful for Mutations when you don't want any data back. - type alias Timestamps = - { createdAt : Posix - , updatedAt : Posix - } + import Graphql.Operation exposing (RootMutation) + import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) + import StarWars.Mutation as Mutation - repositorySelection : SelectionSet Repo Github.Object.Repository - repositorySelection = - Repository.selection Repo - |> with Repository.nameWithOwner - |> withFragment timestampsFragment - |> with stargazersCount + sendChatMessage : String -> SelectionSet () RootMutation + sendChatMessage message = + Mutation.sendMessage + { message = message } + SelectionSet.empty - timestampsFragment : SelectionSet Timestamps Github.Object.Repository - timestampsFragment = - Repository.selection Timestamps - |> with (Repository.createdAt |> mapToDateTime) - |> with (Repository.updatedAt |> mapToDateTime) +-} +empty : SelectionSet () typeLock +empty = + SelectionSet [] (Decode.succeed ()) - mapToDateTime : Field Github.Scalar.DateTime typeLock -> Field Posix typeLock - mapToDateTime = - Field.mapOrFail - (\(Github.Scalar.DateTime value) -> - Iso8601.toTime value - |> Result.mapError (\_ -> "Failed to parse " ++ value ++ " as Iso8601 DateTime.") - ) - stargazersCount : Field Int Github.Object.Repository - stargazersCount = - Repository.stargazers - (\optionals -> { optionals | first = Present 0 }) - (fieldSelection Github.Object.StargazerConnection.totalCount) +{-| This type is used internally only in the generated code. +-} +type FragmentSelectionSet decodesTo typeLock + = FragmentSelectionSet String (List RawField) (Decoder decodesTo) -Notice that we are using two different techniques for abstraction here. -We use `|> withFragment timestampsFragment` to include a fragment and we use -`|> with stargazersCount`. What's the difference? For the `timestampsFragment`, -we want to pull in some fields at the same level of the `SelectionSet` we are -building up. But for the `stargazersCount`, we are simply extracting a `Field` -which builds up a nested `SelectionSet` that grabs the `totalCount` within the -`stargazers` `Field`. +{-| See the explanation in the Pipeline section. This function is used +to add `SelectionSet`s onto a pipeline. -} -withFragment : SelectionSet a typeLock -> SelectionSet (a -> b) typeLock -> SelectionSet b typeLock -withFragment (SelectionSet selectionFields1 selectionDecoder1) (SelectionSet selectionFields2 selectionDecoder2) = +with : SelectionSet a typeLock -> SelectionSet (a -> b) typeLock -> SelectionSet b typeLock +with (SelectionSet selectionFields1 selectionDecoder1) (SelectionSet selectionFields2 selectionDecoder2) = SelectionSet (selectionFields1 ++ selectionFields2) (Decode.map2 (|>) selectionDecoder1 @@ -264,20 +547,21 @@ withFragment (SelectionSet selectionFields1 selectionDecoder1) (SelectionSet sel ) -{-| Include a hardcoded value. +{-| Include a hardcoded value. This is used analagously to `with` to add values +into a pipeline. - import Api.Enum.Episode as Episode exposing (Episode) - import Api.Object - import Graphql.SelectionSet exposing (SelectionSet, with, hardcoded) + import StarWars.Enum.Episode as Episode exposing (Episode) + import StarWars.Object + import Graphql.SelectionSet as SelectionSet exposing (SelectionSet, with, hardcoded) type alias Hero = { name : String , movie : String } - hero : SelectionSet Hero Api.Interface.Character + hero : SelectionSet Hero StarWars.Interface.Character hero = - Character.commonSelection Hero + SelectionSet.succeed Hero |> with Character.name |> hardcoded "Star Wars" @@ -291,12 +575,23 @@ hardcoded constant (SelectionSet objectFields objectDecoder) = ) -{-| Instead of hardcoding a field like `hardcoded`, `SelectionSet.succeed` hardcodes +{-| Most commonly `succeed` is used to start a pipeline. See the description +in the Pipeline section above for more. + +There are other ways to use `succeed`. It simply takes the value you pass into it +and decodes into that value without looking at the GraphQL response (just like +`Json.Decode.succeed`). + +So instead of hardcoding a field like `hardcoded`, `SelectionSet.succeed` hardcodes an entire `SelectionSet`. This can be useful if you want hardcoded data based on only the type when using a polymorphic type (Interface or Union). + import Graphql.SelectionSet as SelectionSet exposing (SelectionSet, with) + import StarWars.Interface + import StarWars.Interface.Character as Character + type alias Character = - { details : Maybe HumanOrDroid + { typename : HumanOrDroid , name : String } @@ -304,15 +599,214 @@ only the type when using a polymorphic type (Interface or Union). = Human | Droid - hero : SelectionSet Character Swapi.Interface.Character + hero : SelectionSet Character StarWars.Interface.Character hero = - Character.selection Character - [ Character.onDroid (SelectionSet.succeed Droid) - , Character.onHuman (SelectionSet.succeed Human) - ] + SelectionSet.succeed Character + |> with heroType |> with Character.name + heroType : SelectionSet HumanOrDroid StarWars.Interface.Character + heroType = + Character.fragments + { onHuman = SelectionSet.succeed Human + , onDroid = SelectionSet.succeed Droid + } + -} succeed : a -> SelectionSet a typeLock -succeed constant = - SelectionSet [ RawField.Leaf "__typename" [] ] (Decode.succeed constant) +succeed constructor = + SelectionSet [] (Decode.succeed constructor) + + +{-| Combine a `List` of `SelectionSet`s into a single `SelectionSet`. +Note that the `SelectionSet`s must first be coerced into the same type if they +are not already. Usually you'll just want to use `map2` and other functions in +that section of these docs. + + import Graphql.Operation exposing (RootQuery) + import Graphql.OptionalArgument exposing (OptionalArgument(..)) + import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) + import Swapi.Enum.Episode as Episode exposing (Episode) + import Swapi.Interface.Character as Character + import Swapi.Query as Query + + heros : SelectionSet (List String) RootQuery + heros = + Episode.list + |> List.map + (\episode -> + Query.hero + (\optionals -> { optionals | episode = Present episode }) + Character.name + ) + |> SelectionSet.list + +-} +list : List (SelectionSet a typeLock) -> SelectionSet (List a) typeLock +list selections = + selections + |> List.foldl (map2 (::)) (empty |> map (\_ -> [])) + |> map List.reverse + + +{-| Combine several `SelectionSet`s into a single `SelectionSet`. The +`String`s are used as the `Dict` keys and the result of the `SelectionSet`s +will be the values in the `Dict`. Note that the `SelectionSet`s must first +be coerced into the same type if they are not already. Usually you'll just +want to use `map2` and other functions in that section of these docs. + + import Graphql.Operation exposing (RootQuery) + import Graphql.OptionalArgument exposing (OptionalArgument(..)) + import Graphql.SelectionSet as SelectionSet exposing (SelectionSet) + import Swapi.Enum.Episode as Episode exposing (Episode) + import Swapi.Interface.Character as Character + import Swapi.Query as Query + + herosDict : SelectionSet (Dict String String) RootQuery + herosDict = + Episode.list + |> List.map + (\episode -> + ( Episode.toString episode + , Query.hero + (\optionals -> { optionals | episode = Present episode }) + Character.name + ) + ) + |> SelectionSet.dict + +-} +dict : List ( String, SelectionSet a typeLock ) -> SelectionSet (Dict String a) typeLock +dict selections = + selections + |> List.foldl combineDict (empty |> map (\_ -> Dict.empty)) + + +combineDict : ( String, SelectionSet a typeLock ) -> SelectionSet (Dict String a) typeLock -> SelectionSet (Dict String a) typeLock +combineDict ( key, selection ) acc = + map2 (Dict.insert key) selection acc + + +{-| If the map function provided returns an `Ok` `Result`, it will map to that value. +If it returns an `Err`, the _entire_ response will fail to decode. + + import Time exposing (Posix) + import Github.Object + import Github.Object.Repository + import Github.Scalar + -- NOTE: Iso8601 comes from an external dependency in Elm >= 0.19: + -- https://package.elm-lang.org/packages/rtfeldman/elm-iso8601-date-strings/latest/ + import Iso8601 + import Graphql.SelectionSet as SelectionSet exposing (with) + + type alias Timestamps = + { created : Posix + , updated : Posix + } + + + timestampsSelection : SelectionSet Timestamps Github.Object.Repository + timestampsSelection = + SelectionSet.succeed Timestamps + |> with (Repository.createdAt |> mapToDateTime) + |> with (Repository.updatedAt |> mapToDateTime) + + + mapToDateTime : Field Github.Scalar.DateTime typeLock -> Field Posix typeLock + mapToDateTime = + Field.mapOrFail + (\(Github.Scalar.DateTime value) -> + Iso8601.toTime value + |> Result.mapError (\_ -> "Failed to parse " + ++ value ++ " as Iso8601 DateTime.") + +-} +mapOrFail : (decodesTo -> Result String mapsTo) -> SelectionSet decodesTo typeLock -> SelectionSet mapsTo typeLock +mapOrFail mapFunction (SelectionSet field decoder) = + decoder + |> Decode.map mapFunction + |> Decode.andThen + (\result -> + case result of + Ok value -> + Decode.succeed value + + Err errorMessage -> + Decode.fail ("Check your code for calls to mapOrFail, your map function returned an `Err` with the message: " ++ errorMessage) + ) + |> SelectionSet field + + +{-| Effectively turns an attribute that is `String` => `String!`, or `User` => +`User!` (if you're not familiar with the GraphQL type language notation, learn more +[here](http://graphql.org/learn/schema/#type-language)). + +This will cause your _entire_ decoder to fail if the field comes back as null. +It's far better to fix your schema then to use this escape hatch! + +-} +nonNullOrFail : SelectionSet (Maybe decodesTo) typeLock -> SelectionSet decodesTo typeLock +nonNullOrFail (SelectionSet fields decoder) = + decoder + |> Decode.andThen + (\result -> + case result of + Just value -> + Decode.succeed value + + Nothing -> + Decode.fail "Expected non-null but got null, check for calls to nonNullOrFail in your code. Ideally your schema should indicate that this is non-nullable so you don't need to use nonNullOrFail at all." + ) + |> SelectionSet fields + + +{-| Effectively turns a field that is `[String]` => `[String!]`, or `[User]` => +`[User!]` (if you're not familiar with the GraphQL type language notation, learn more +[here](http://graphql.org/learn/schema/#type-language)). + +This will cause your _entire_ decoder to fail if any elements in the list for this +field comes back as null. +It's far better to fix your schema then to use this escape hatch! + +Often GraphQL schemas will contain things like `[String]` (i.e. a nullable list +of nullable strings) when they really mean `[String!]!` (a non-nullable list of +non-nullable strings). You can chain together these nullable helpers if for some +reason you can't go in and fix this in the schema, for example: + + releases : SelectionSet (List Release) Github.Object.ReleaseConnection + releases = + Github.Object.ReleaseConnection.nodes release + |> Field.nonNullOrFail + |> Field.nonNullElementsOrFail + +Without the `Field.nonNull...` transformations here, the type would be +`SelectionSet (Maybe (List (Maybe Release))) Github.Object.ReleaseConnection`. + +-} +nonNullElementsOrFail : SelectionSet (List (Maybe decodesTo)) typeLock -> SelectionSet (List decodesTo) typeLock +nonNullElementsOrFail (SelectionSet fields decoder) = + decoder + |> Decode.andThen + (\result -> + case combineMaybeList result of + Nothing -> + Decode.fail "Expected only non-null list elements but found a null. Check for calls to nonNullElementsOrFail in your code. Ideally your schema should indicate that this is non-nullable so you don't need to use nonNullElementsOrFail at all." + + Just listWithoutNulls -> + Decode.succeed listWithoutNulls + ) + |> SelectionSet fields + + +combineMaybeList : List (Maybe a) -> Maybe (List a) +combineMaybeList listOfMaybes = + let + step maybeElement accumulator = + case maybeElement of + Nothing -> + Nothing + + Just element -> + Maybe.map ((::) element) accumulator + in + List.foldr step (Just []) listOfMaybes