diff --git a/examples/elm.json b/examples/elm.json index 110e0dab5..fbd0fe60c 100644 --- a/examples/elm.json +++ b/examples/elm.json @@ -3,6 +3,7 @@ "source-directories": [ "../src", "src", + "gen", "src/complex" ], "elm-version": "0.19.0", @@ -35,4 +36,4 @@ "direct": {}, "indirect": {} } -} \ No newline at end of file +} diff --git a/examples/gen/Pages/Enum/CacheControlScope.elm b/examples/gen/Pages/Enum/CacheControlScope.elm new file mode 100644 index 000000000..a3c78032a --- /dev/null +++ b/examples/gen/Pages/Enum/CacheControlScope.elm @@ -0,0 +1,52 @@ +-- Do not manually edit this file, it was auto-generated by dillonkearns/elm-graphql +-- https://github.com/dillonkearns/elm-graphql + + +module Pages.Enum.CacheControlScope exposing (CacheControlScope(..), decoder, list, toString) + +import Json.Decode as Decode exposing (Decoder) + + +{-| + + - Public - + - Private - + +-} +type CacheControlScope + = Public + | Private + + +list : List CacheControlScope +list = + [ Public, Private ] + + +decoder : Decoder CacheControlScope +decoder = + Decode.string + |> Decode.andThen + (\string -> + case string of + "PUBLIC" -> + Decode.succeed Public + + "PRIVATE" -> + Decode.succeed Private + + _ -> + Decode.fail ("Invalid CacheControlScope type, " ++ string ++ " try re-running the @dillonkearns/elm-graphql CLI ") + ) + + +{-| Convert from the union type representating the Enum to a string that the GraphQL server will recognize. +-} +toString : CacheControlScope -> String +toString enum = + case enum of + Public -> + "PUBLIC" + + Private -> + "PRIVATE" diff --git a/examples/gen/Pages/InputObject.elm b/examples/gen/Pages/InputObject.elm new file mode 100644 index 000000000..5c121cbf8 --- /dev/null +++ b/examples/gen/Pages/InputObject.elm @@ -0,0 +1,10 @@ +-- Do not manually edit this file, it was auto-generated by dillonkearns/elm-graphql +-- https://github.com/dillonkearns/elm-graphql + + +module Pages.InputObject exposing (placeholder) + + +placeholder : String +placeholder = + "" diff --git a/examples/gen/Pages/Interface.elm b/examples/gen/Pages/Interface.elm new file mode 100644 index 000000000..d9fc72dc8 --- /dev/null +++ b/examples/gen/Pages/Interface.elm @@ -0,0 +1,10 @@ +-- Do not manually edit this file, it was auto-generated by dillonkearns/elm-graphql +-- https://github.com/dillonkearns/elm-graphql + + +module Pages.Interface exposing (placeholder) + + +placeholder : String +placeholder = + "" diff --git a/examples/gen/Pages/Object.elm b/examples/gen/Pages/Object.elm new file mode 100644 index 000000000..a834740c0 --- /dev/null +++ b/examples/gen/Pages/Object.elm @@ -0,0 +1,21 @@ +-- Do not manually edit this file, it was auto-generated by dillonkearns/elm-graphql +-- https://github.com/dillonkearns/elm-graphql + + +module Pages.Object exposing (PageInfo(..), StargazerConnection(..), StargazerEdge(..), User(..)) + + +type PageInfo + = PageInfo + + +type StargazerConnection + = StargazerConnection + + +type StargazerEdge + = StargazerEdge + + +type User + = User diff --git a/examples/gen/Pages/Object/PageInfo.elm b/examples/gen/Pages/Object/PageInfo.elm new file mode 100644 index 000000000..759584ca8 --- /dev/null +++ b/examples/gen/Pages/Object/PageInfo.elm @@ -0,0 +1,43 @@ +-- Do not manually edit this file, it was auto-generated by dillonkearns/elm-graphql +-- https://github.com/dillonkearns/elm-graphql + + +module Pages.Object.PageInfo exposing (endCursor, hasNextPage, hasPreviousPage, starCursor) + +import Graphql.Internal.Builder.Argument as Argument exposing (Argument) +import Graphql.Internal.Builder.Object as Object +import Graphql.Internal.Encode as Encode exposing (Value) +import Graphql.Operation exposing (RootMutation, RootQuery, RootSubscription) +import Graphql.OptionalArgument exposing (OptionalArgument(..)) +import Graphql.SelectionSet exposing (SelectionSet) +import Json.Decode as Decode +import Pages.InputObject +import Pages.Interface +import Pages.Object +import Pages.Scalar +import Pages.ScalarCodecs +import Pages.Union + + +{-| -} +endCursor : SelectionSet (Maybe String) Pages.Object.PageInfo +endCursor = + Object.selectionForField "(Maybe String)" "endCursor" [] (Decode.string |> Decode.nullable) + + +{-| -} +hasNextPage : SelectionSet Bool Pages.Object.PageInfo +hasNextPage = + Object.selectionForField "Bool" "hasNextPage" [] Decode.bool + + +{-| -} +hasPreviousPage : SelectionSet Bool Pages.Object.PageInfo +hasPreviousPage = + Object.selectionForField "Bool" "hasPreviousPage" [] Decode.bool + + +{-| -} +starCursor : SelectionSet (Maybe String) Pages.Object.PageInfo +starCursor = + Object.selectionForField "(Maybe String)" "starCursor" [] (Decode.string |> Decode.nullable) diff --git a/examples/gen/Pages/Object/StargazerConnection.elm b/examples/gen/Pages/Object/StargazerConnection.elm new file mode 100644 index 000000000..935c9621d --- /dev/null +++ b/examples/gen/Pages/Object/StargazerConnection.elm @@ -0,0 +1,37 @@ +-- Do not manually edit this file, it was auto-generated by dillonkearns/elm-graphql +-- https://github.com/dillonkearns/elm-graphql + + +module Pages.Object.StargazerConnection exposing (edges, pageInfo, totalCount) + +import Graphql.Internal.Builder.Argument as Argument exposing (Argument) +import Graphql.Internal.Builder.Object as Object +import Graphql.Internal.Encode as Encode exposing (Value) +import Graphql.Operation exposing (RootMutation, RootQuery, RootSubscription) +import Graphql.OptionalArgument exposing (OptionalArgument(..)) +import Graphql.SelectionSet exposing (SelectionSet) +import Json.Decode as Decode +import Pages.InputObject +import Pages.Interface +import Pages.Object +import Pages.Scalar +import Pages.ScalarCodecs +import Pages.Union + + +{-| -} +edges : SelectionSet decodesTo Pages.Object.StargazerEdge -> SelectionSet (List decodesTo) Pages.Object.StargazerConnection +edges object_ = + Object.selectionForCompositeField "edges" [] object_ (identity >> Decode.list) + + +{-| -} +pageInfo : SelectionSet decodesTo Pages.Object.PageInfo -> SelectionSet decodesTo Pages.Object.StargazerConnection +pageInfo object_ = + Object.selectionForCompositeField "pageInfo" [] object_ identity + + +{-| -} +totalCount : SelectionSet Int Pages.Object.StargazerConnection +totalCount = + Object.selectionForField "Int" "totalCount" [] Decode.int diff --git a/examples/gen/Pages/Object/StargazerEdge.elm b/examples/gen/Pages/Object/StargazerEdge.elm new file mode 100644 index 000000000..c8325e53f --- /dev/null +++ b/examples/gen/Pages/Object/StargazerEdge.elm @@ -0,0 +1,31 @@ +-- Do not manually edit this file, it was auto-generated by dillonkearns/elm-graphql +-- https://github.com/dillonkearns/elm-graphql + + +module Pages.Object.StargazerEdge exposing (cursor, node) + +import Graphql.Internal.Builder.Argument as Argument exposing (Argument) +import Graphql.Internal.Builder.Object as Object +import Graphql.Internal.Encode as Encode exposing (Value) +import Graphql.Operation exposing (RootMutation, RootQuery, RootSubscription) +import Graphql.OptionalArgument exposing (OptionalArgument(..)) +import Graphql.SelectionSet exposing (SelectionSet) +import Json.Decode as Decode +import Pages.InputObject +import Pages.Interface +import Pages.Object +import Pages.Scalar +import Pages.ScalarCodecs +import Pages.Union + + +{-| -} +cursor : SelectionSet String Pages.Object.StargazerEdge +cursor = + Object.selectionForField "String" "cursor" [] Decode.string + + +{-| -} +node : SelectionSet decodesTo Pages.Object.User -> SelectionSet decodesTo Pages.Object.StargazerEdge +node object_ = + Object.selectionForCompositeField "node" [] object_ identity diff --git a/examples/gen/Pages/Object/User.elm b/examples/gen/Pages/Object/User.elm new file mode 100644 index 000000000..6c1fb1a25 --- /dev/null +++ b/examples/gen/Pages/Object/User.elm @@ -0,0 +1,25 @@ +-- Do not manually edit this file, it was auto-generated by dillonkearns/elm-graphql +-- https://github.com/dillonkearns/elm-graphql + + +module Pages.Object.User exposing (name) + +import Graphql.Internal.Builder.Argument as Argument exposing (Argument) +import Graphql.Internal.Builder.Object as Object +import Graphql.Internal.Encode as Encode exposing (Value) +import Graphql.Operation exposing (RootMutation, RootQuery, RootSubscription) +import Graphql.OptionalArgument exposing (OptionalArgument(..)) +import Graphql.SelectionSet exposing (SelectionSet) +import Json.Decode as Decode +import Pages.InputObject +import Pages.Interface +import Pages.Object +import Pages.Scalar +import Pages.ScalarCodecs +import Pages.Union + + +{-| -} +name : SelectionSet String Pages.Object.User +name = + Object.selectionForField "String" "name" [] Decode.string diff --git a/examples/gen/Pages/Query.elm b/examples/gen/Pages/Query.elm new file mode 100644 index 000000000..bef76e048 --- /dev/null +++ b/examples/gen/Pages/Query.elm @@ -0,0 +1,56 @@ +-- Do not manually edit this file, it was auto-generated by dillonkearns/elm-graphql +-- https://github.com/dillonkearns/elm-graphql + + +module Pages.Query exposing (StargazersOptionalArguments, stargazers) + +import Graphql.Internal.Builder.Argument as Argument exposing (Argument) +import Graphql.Internal.Builder.Object as Object +import Graphql.Internal.Encode as Encode exposing (Value) +import Graphql.Operation exposing (RootMutation, RootQuery, RootSubscription) +import Graphql.OptionalArgument exposing (OptionalArgument(..)) +import Graphql.Paginator as Paginator exposing (Paginator) +import Graphql.SelectionSet exposing (SelectionSet) +import Json.Decode as Decode exposing (Decoder) +import Pages.InputObject +import Pages.Interface +import Pages.Object +import Pages.Object.StargazerConnection +import Pages.Scalar +import Pages.ScalarCodecs +import Pages.Union + + +type alias StargazersOptionalArguments = + { after : OptionalArgument String + , before : OptionalArgument String + , first : OptionalArgument Int + , last : OptionalArgument Int + } + + +{-| + + - after - + - before - + - first - + - last - + +-} +stargazers : + Int + -> Paginator direction decodesTo + -> (StargazersOptionalArguments -> StargazersOptionalArguments) + -> SelectionSet decodesTo Pages.Object.StargazerEdge + -> SelectionSet (Paginator direction decodesTo) Pages.Object.User +stargazers pageSize paginator fillInOptionals object_ = + let + filledInOptionals = + fillInOptionals { first = Absent, after = Absent, last = Absent, before = Absent } + |> Paginator.addPageInfo pageSize paginator + + optionalArgs = + [ Argument.optional "first" filledInOptionals.first Encode.int, Argument.optional "after" filledInOptionals.after Encode.string, Argument.optional "last" filledInOptionals.last Encode.int, Argument.optional "before" filledInOptionals.before Encode.string ] + |> List.filterMap identity + in + Object.selectionForCompositeField "stargazers" optionalArgs (Paginator.selectionSet pageSize paginator (Pages.Object.StargazerConnection.edges object_)) identity diff --git a/examples/gen/Pages/Scalar.elm b/examples/gen/Pages/Scalar.elm new file mode 100644 index 000000000..bd6062149 --- /dev/null +++ b/examples/gen/Pages/Scalar.elm @@ -0,0 +1,50 @@ +-- Do not manually edit this file, it was auto-generated by dillonkearns/elm-graphql +-- https://github.com/dillonkearns/elm-graphql + + +module Pages.Scalar exposing (Codecs, Upload(..), defaultCodecs, defineCodecs, unwrapCodecs, unwrapEncoder) + +import Graphql.Codec exposing (Codec) +import Graphql.Internal.Builder.Object as Object +import Graphql.Internal.Encode +import Json.Decode as Decode exposing (Decoder) +import Json.Encode as Encode + + +type Upload + = Upload String + + +defineCodecs : + { codecUpload : Codec valueUpload } + -> Codecs valueUpload +defineCodecs definitions = + Codecs definitions + + +unwrapCodecs : + Codecs valueUpload + -> { codecUpload : Codec valueUpload } +unwrapCodecs (Codecs unwrappedCodecs) = + unwrappedCodecs + + +unwrapEncoder getter (Codecs unwrappedCodecs) = + (unwrappedCodecs |> getter |> .encoder) >> Graphql.Internal.Encode.fromJson + + +type Codecs valueUpload + = Codecs (RawCodecs valueUpload) + + +type alias RawCodecs valueUpload = + { codecUpload : Codec valueUpload } + + +defaultCodecs : RawCodecs Upload +defaultCodecs = + { codecUpload = + { encoder = \(Upload raw) -> Encode.string raw + , decoder = Object.scalarDecoder |> Decode.map Upload + } + } diff --git a/examples/gen/Pages/ScalarCodecs.elm b/examples/gen/Pages/ScalarCodecs.elm new file mode 100644 index 000000000..16eb8525f --- /dev/null +++ b/examples/gen/Pages/ScalarCodecs.elm @@ -0,0 +1,19 @@ +-- Do not manually edit this file, it was auto-generated by dillonkearns/elm-graphql +-- https://github.com/dillonkearns/elm-graphql + + +module Pages.ScalarCodecs exposing (Upload, codecs) + +import Json.Decode as Decode exposing (Decoder) +import Pages.Scalar exposing (defaultCodecs) + + +type alias Upload = + Pages.Scalar.Upload + + +codecs : Pages.Scalar.Codecs Upload +codecs = + Pages.Scalar.defineCodecs + { codecUpload = defaultCodecs.codecUpload + } diff --git a/examples/gen/Pages/Union.elm b/examples/gen/Pages/Union.elm new file mode 100644 index 000000000..03abbdeac --- /dev/null +++ b/examples/gen/Pages/Union.elm @@ -0,0 +1,10 @@ +-- Do not manually edit this file, it was auto-generated by dillonkearns/elm-graphql +-- https://github.com/dillonkearns/elm-graphql + + +module Pages.Union exposing (placeholder) + + +placeholder : String +placeholder = + "" diff --git a/examples/gen/Pages/VerifyScalarCodecs.elm b/examples/gen/Pages/VerifyScalarCodecs.elm new file mode 100644 index 000000000..3a4e87d48 --- /dev/null +++ b/examples/gen/Pages/VerifyScalarCodecs.elm @@ -0,0 +1,10 @@ +-- Do not manually edit this file, it was auto-generated by dillonkearns/elm-graphql +-- https://github.com/dillonkearns/elm-graphql + + +module Pages.VerifyScalarCodecs exposing (placeholder) + + +placeholder : String +placeholder = + "" diff --git a/examples/src/Github/Object/Repository.elm b/examples/src/Github/Object/Repository.elm index e18a81041..a95fd9d37 100644 --- a/examples/src/Github/Object/Repository.elm +++ b/examples/src/Github/Object/Repository.elm @@ -2,7 +2,7 @@ -- https://github.com/dillonkearns/elm-graphql -module Github.Object.Repository exposing (AssignableUsersOptionalArguments, CollaboratorsOptionalArguments, CommitCommentsOptionalArguments, DeployKeysOptionalArguments, DeploymentsOptionalArguments, ForksOptionalArguments, IssueOrPullRequestRequiredArguments, IssueRequiredArguments, IssuesOptionalArguments, LabelRequiredArguments, LabelsOptionalArguments, LanguagesOptionalArguments, MentionableUsersOptionalArguments, MilestoneRequiredArguments, MilestonesOptionalArguments, ObjectOptionalArguments, ProjectRequiredArguments, ProjectsOptionalArguments, ProtectedBranchesOptionalArguments, PullRequestRequiredArguments, PullRequestsOptionalArguments, RefRequiredArguments, RefsOptionalArguments, RefsRequiredArguments, ReleaseRequiredArguments, ReleasesOptionalArguments, RepositoryTopicsOptionalArguments, ShortDescriptionHTMLOptionalArguments, StargazersOptionalArguments, WatchersOptionalArguments, assignableUsers, codeOfConduct, collaborators, commitComments, createdAt, databaseId, defaultBranchRef, deployKeys, deployments, description, descriptionHTML, diskUsage, forkCount, forks, hasIssuesEnabled, hasWikiEnabled, homepageUrl, id, isArchived, isFork, isLocked, isMirror, isPrivate, issue, issueOrPullRequest, issues, label, labels, languages, license, licenseInfo, lockReason, mentionableUsers, milestone, milestones, mirrorUrl, name, nameWithOwner, object, owner, parent, primaryLanguage, project, projects, projectsResourcePath, projectsUrl, protectedBranches, pullRequest, pullRequests, pushedAt, ref, refs, release, releases, repositoryTopics, resourcePath, shortDescriptionHTML, sshUrl, stargazers, updatedAt, url, viewerCanAdminister, viewerCanCreateProjects, viewerCanSubscribe, viewerCanUpdateTopics, viewerHasStarred, viewerPermission, viewerSubscription, watchers) +module Github.Object.Repository exposing (AssignableUsersOptionalArguments, CollaboratorsOptionalArguments, CommitCommentsOptionalArguments, DeployKeysOptionalArguments, DeploymentsOptionalArguments, ForksOptionalArguments, IssueOrPullRequestRequiredArguments, IssueRequiredArguments, IssuesOptionalArguments, LabelRequiredArguments, LabelsOptionalArguments, LanguagesOptionalArguments, MentionableUsersOptionalArguments, MilestoneRequiredArguments, MilestonesOptionalArguments, ObjectOptionalArguments, ProjectRequiredArguments, ProjectsOptionalArguments, ProtectedBranchesOptionalArguments, PullRequestRequiredArguments, PullRequestsOptionalArguments, RefRequiredArguments, RefsOptionalArguments, RefsRequiredArguments, ReleaseRequiredArguments, ReleasesOptionalArguments, RepositoryTopicsOptionalArguments, ShortDescriptionHTMLOptionalArguments, StargazersOptionalArguments, WatchersOptionalArguments, assignableUsers, codeOfConduct, collaborators, commitComments, createdAt, databaseId, defaultBranchRef, deployKeys, deployments, description, descriptionHTML, diskUsage, forkCount, forks, hasIssuesEnabled, hasWikiEnabled, homepageUrl, id, isArchived, isFork, isLocked, isMirror, isPrivate, issue, issueOrPullRequest, issues, label, labels, languages, license, licenseInfo, lockReason, mentionableUsers, milestone, milestones, mirrorUrl, name, nameWithOwner, object, owner, parent, primaryLanguage, project, projects, projectsResourcePath, projectsUrl, protectedBranches, pullRequest, pullRequests, pushedAt, ref, refs, release, releases, repositoryTopics, resourcePath, shortDescriptionHTML, sshUrl, stargazers, stargazers3, stargazersPaginated, updatedAt, url, viewerCanAdminister, viewerCanCreateProjects, viewerCanSubscribe, viewerCanUpdateTopics, viewerHasStarred, viewerPermission, viewerSubscription, watchers) import Github.Enum.CollaboratorAffiliation import Github.Enum.IssueState @@ -24,8 +24,10 @@ import Github.Union import Graphql.Internal.Builder.Argument as Argument exposing (Argument) import Graphql.Internal.Builder.Object as Object import Graphql.Internal.Encode as Encode exposing (Value) +import Graphql.Internal.Paginator import Graphql.Operation exposing (RootMutation, RootQuery, RootSubscription) import Graphql.OptionalArgument exposing (OptionalArgument(..)) +import Graphql.Paginator as Paginator exposing (Paginator) import Graphql.SelectionSet exposing (SelectionSet) import Json.Decode as Decode @@ -981,6 +983,60 @@ stargazers fillInOptionals object_ = Object.selectionForCompositeField "stargazers" optionalArgs object_ identity +stargazers2 : + Int + -> Paginator direction decodesTo + -> (StargazersOptionalArguments -> StargazersOptionalArguments) + -> SelectionSet decodesTo Github.Object.StargazerEdge + -> SelectionSet (Paginator direction decodesTo) Github.Object.Repository +stargazers2 pageSize paginator fillInOptionals object_ = + let + filledInOptionals = + fillInOptionals { first = Absent, after = Absent, last = Absent, before = Absent, orderBy = Absent } + + optionalArgs = + [ Argument.optional "orderBy" filledInOptionals.orderBy Github.InputObject.encodeStarOrder ] + |> List.filterMap identity + |> List.append (Paginator.pageInfoOptionalArgs pageSize paginator) + in + Object.selectionForCompositeField "stargazers" optionalArgs (Paginator.selectionSet paginator (stargazerEdges object_)) identity + + +stargazers3 : + Int + -> Paginator direction decodesTo + -> (StargazersOptionalArguments -> StargazersOptionalArguments) + -> SelectionSet finalDecodesTo Github.Object.StargazerConnection + -> SelectionSet finalDecodesTo Github.Object.Repository +stargazers3 pageSize paginator fillInOptionals object_ = + let + filledInOptionals = + fillInOptionals { first = Absent, after = Absent, last = Absent, before = Absent, orderBy = Absent } + + optionalArgs = + [ Argument.optional "orderBy" filledInOptionals.orderBy Github.InputObject.encodeStarOrder ] + |> List.filterMap identity + |> List.append (Paginator.pageInfoOptionalArgs pageSize paginator) + in + Object.selectionForCompositeField "stargazers" optionalArgs object_ identity + + +stargazersPaginated : + Int + -> Paginator direction decodesTo + -> (StargazersOptionalArguments -> StargazersOptionalArguments) + -> SelectionSet decodesTo Github.Object.StargazerEdge + -> SelectionSet (Paginator direction decodesTo) Github.Object.Repository +stargazersPaginated pageSize paginator fillInOptionals object_ = + stargazers (fillInOptionals >> Paginator.addPageInfo pageSize paginator) + (Paginator.selectionSet paginator (stargazerEdges object_)) + + +stargazerEdges : SelectionSet decodesTo Github.Object.StargazerEdge -> SelectionSet (List decodesTo) Github.Object.StargazerConnection +stargazerEdges object_ = + Object.selectionForCompositeField "edges" [] object_ (identity >> Decode.list) + + {-| Identifies the date and time when the object was last updated. -} updatedAt : SelectionSet Github.ScalarCodecs.DateTime Github.Object.Repository diff --git a/examples/src/Github/Object/StargazerConnection.elm b/examples/src/Github/Object/StargazerConnection.elm index 11232eed8..6b25d393e 100644 --- a/examples/src/Github/Object/StargazerConnection.elm +++ b/examples/src/Github/Object/StargazerConnection.elm @@ -2,7 +2,7 @@ -- https://github.com/dillonkearns/elm-graphql -module Github.Object.StargazerConnection exposing (edges, nodes, pageInfo, totalCount) +module Github.Object.StargazerConnection exposing (edges, edgesPaginator, nodes, pageInfo, totalCount) import Github.InputObject import Github.Interface @@ -15,6 +15,7 @@ import Graphql.Internal.Builder.Object as Object import Graphql.Internal.Encode as Encode exposing (Value) import Graphql.Operation exposing (RootMutation, RootQuery, RootSubscription) import Graphql.OptionalArgument exposing (OptionalArgument(..)) +import Graphql.Paginator as Paginator exposing (Paginator) import Graphql.SelectionSet exposing (SelectionSet) import Json.Decode as Decode @@ -45,3 +46,13 @@ pageInfo object_ = totalCount : SelectionSet Int Github.Object.StargazerConnection totalCount = Object.selectionForField "Int" "totalCount" [] Decode.int + + +edgesPaginator : + Paginator direction decodesTo + -> SelectionSet decodesTo Github.Object.StargazerEdge + -> SelectionSet (Paginator direction decodesTo) Github.Object.StargazerConnection +edgesPaginator paginator object_ = + Paginator.selectionSet + paginator + (Object.selectionForCompositeField "edges" [] object_ (identity >> Decode.list)) diff --git a/examples/src/Github/Query.elm b/examples/src/Github/Query.elm index 4cbcc41e7..e1b448d5a 100644 --- a/examples/src/Github/Query.elm +++ b/examples/src/Github/Query.elm @@ -2,7 +2,7 @@ -- https://github.com/dillonkearns/elm-graphql -module Github.Query exposing (CodeOfConductRequiredArguments, LicenseRequiredArguments, MarketplaceCategoriesOptionalArguments, MarketplaceCategoryRequiredArguments, MarketplaceListingRequiredArguments, MarketplaceListingsOptionalArguments, NodeRequiredArguments, NodesRequiredArguments, OrganizationRequiredArguments, RateLimitOptionalArguments, RepositoryOwnerRequiredArguments, RepositoryRequiredArguments, ResourceRequiredArguments, SearchOptionalArguments, SearchRequiredArguments, TopicRequiredArguments, UserRequiredArguments, codeOfConduct, codesOfConduct, license, licenses, marketplaceCategories, marketplaceCategory, marketplaceListing, marketplaceListings, meta, node, nodes, organization, rateLimit, relay, repository, repositoryOwner, resource, search, topic, user, viewer) +module Github.Query exposing (CodeOfConductRequiredArguments, LicenseRequiredArguments, MarketplaceCategoriesOptionalArguments, MarketplaceCategoryRequiredArguments, MarketplaceListingRequiredArguments, MarketplaceListingsOptionalArguments, NodeRequiredArguments, NodesRequiredArguments, OrganizationRequiredArguments, RateLimitOptionalArguments, RepositoryOwnerRequiredArguments, RepositoryRequiredArguments, ResourceRequiredArguments, SearchOptionalArguments, SearchRequiredArguments, TopicRequiredArguments, UserRequiredArguments, codeOfConduct, codesOfConduct, license, licenses, marketplaceCategories, marketplaceCategory, marketplaceListing, marketplaceListings, meta, node, nodes, organization, rateLimit, relay, repository, repositoryOwner, resource, search, searchPaginated, topic, user, viewer) import Github.Enum.SearchType import Github.InputObject @@ -14,8 +14,10 @@ import Github.Union import Graphql.Internal.Builder.Argument as Argument exposing (Argument) import Graphql.Internal.Builder.Object as Object import Graphql.Internal.Encode as Encode exposing (Value) +import Graphql.Internal.Paginator import Graphql.Operation exposing (RootMutation, RootQuery, RootSubscription) import Graphql.OptionalArgument exposing (OptionalArgument(..)) +import Graphql.Paginator as Paginator exposing (Backward, Forward, Paginator) import Graphql.SelectionSet exposing (SelectionSet) import Json.Decode as Decode exposing (Decoder) @@ -330,6 +332,19 @@ search fillInOptionals requiredArgs object_ = Object.selectionForCompositeField "search" (optionalArgs ++ [ Argument.required "query" requiredArgs.query Encode.string, Argument.required "type" requiredArgs.type_ (Encode.enum Github.Enum.SearchType.toString) ]) object_ identity +searchPaginated : + Int + -> Paginator direction decodesTo + -> (SearchOptionalArguments -> SearchOptionalArguments) + -> SearchRequiredArguments + -> SelectionSet (List decodesTo) Github.Object.SearchResultItemConnection + -> SelectionSet (Paginator direction decodesTo) RootQuery +searchPaginated pageSize paginator fillInOptionals requiredArgs object_ = + search (fillInOptionals >> Paginator.addPageInfo pageSize paginator) + requiredArgs + (Paginator.selectionSet pageSize paginator object_) + + type alias TopicRequiredArguments = { name : String } diff --git a/examples/src/GithubPagination.elm b/examples/src/GithubPagination.elm index 4e706a855..e406e3a5a 100644 --- a/examples/src/GithubPagination.elm +++ b/examples/src/GithubPagination.elm @@ -3,7 +3,6 @@ module GithubPagination exposing (main) import Browser import Github.Enum.SearchType import Github.Object -import Github.Object.PageInfo import Github.Object.Repository as Repository import Github.Object.SearchResultItemConnection import Github.Object.SearchResultItemEdge @@ -16,64 +15,24 @@ import Graphql.Document as Document import Graphql.Http import Graphql.Operation exposing (RootQuery) import Graphql.OptionalArgument as OptionalArgument exposing (OptionalArgument(..)) +import Graphql.Paginator as Paginator exposing (Paginator) import Graphql.SelectionSet as SelectionSet exposing (SelectionSet, with) -import Html exposing (button, div, h1, p, pre, text) +import Html exposing (button, div, h1, input, p, pre, text) import Html.Events exposing (onClick) import PrintAny -import RemoteData exposing (RemoteData) type alias Response = - Paginator (List Repo) String + Paginator Paginator.Forward Repo -type alias Paginator dataType cursorType = - { data : dataType - , paginationData : PaginationData cursorType - } - - -query : Maybe String -> SelectionSet Response RootQuery -query cursor = - Query.search - (\optionals -> - { optionals - | first = Present 1 - , after = OptionalArgument.fromMaybe cursor - } - ) - { query = "language:Elm" - , type_ = Github.Enum.SearchType.Repository - } - searchSelection - - -searchSelection : SelectionSet Response Github.Object.SearchResultItemConnection -searchSelection = - SelectionSet.succeed Paginator - |> with searchResultFieldEdges - |> with (Github.Object.SearchResultItemConnection.pageInfo searchPageInfoSelection) - - -type alias PaginationData cursorType = - { cursor : Maybe cursorType - , hasNextPage : Bool - } - - -searchPageInfoSelection : SelectionSet (PaginationData String) Github.Object.PageInfo -searchPageInfoSelection = - SelectionSet.succeed PaginationData - |> with Github.Object.PageInfo.endCursor - |> with Github.Object.PageInfo.hasNextPage - - -searchResultFieldNodes : SelectionSet (List Repo) Github.Object.SearchResultItemConnection -searchResultFieldNodes = - Github.Object.SearchResultItemConnection.nodes searchResultSelection - |> SelectionSet.nonNullOrFail - |> SelectionSet.nonNullElementsOrFail - |> SelectionSet.nonNullElementsOrFail +query : Int -> Paginator Paginator.Forward Repo -> SelectionSet Response RootQuery +query pageSize paginator = + Query.searchPaginated pageSize + paginator + identity + { query = "language:Elm", type_ = Github.Enum.SearchType.Repository } + searchResultFieldEdges searchResultFieldEdges : SelectionSet (List Repo) Github.Object.SearchResultItemConnection @@ -116,30 +75,49 @@ repositorySelection = |> with (Repository.stargazers identity Github.Object.StargazerConnection.totalCount) -makeRequest : Maybe String -> Cmd Msg -makeRequest cursor = - query cursor +makeRequest : Int -> Paginator Paginator.Forward Repo -> Cmd Msg +makeRequest pageSize paginator = + query pageSize paginator |> Graphql.Http.queryRequest "https://api.github.com/graphql" |> Graphql.Http.withHeader "authorization" "Bearer dbd4c239b0bbaa40ab0ea291fa811775da8f5b59" - |> Graphql.Http.send (\result -> result |> RemoteData.fromResult |> GotResponse) + |> Graphql.Http.send GotResponse type Msg = GotResponse RemoteDataResponse | GetNextPage + | CountChanged String type alias Model = - List RemoteDataResponse + -- List RemoteDataResponse + { pageSize : Int + , data : Paginator Paginator.Forward Repo + } + + + +-- type PaginatedRemoteData data +-- = NotLoading +-- | Loading data +-- | MoreToLoad data +-- | AllLoaded type alias RemoteDataResponse = - RemoteData (Graphql.Http.Error Response) Response + Result (Graphql.Http.Error Response) Response init : Flags -> ( Model, Cmd Msg ) init flags = - ( [ RemoteData.Loading ], makeRequest Nothing ) + Paginator.forward + |> (\paginator -> + ( { pageSize = 1 + , data = paginator + } + , makeRequest 1 paginator + ) + ) view : Model -> Html.Html Msg @@ -147,12 +125,16 @@ view model = div [] [ div [] [ h1 [] [ text "Generated Query" ] - , pre [] [ text (Document.serializeQuery (query Nothing)) ] + + -- , pre [] [ text (Document.serializeQuery (query model.pageSize model.data)) ] + ] + , div [] + [ button [ onClick GetNextPage ] [ text <| "Load next " ++ String.fromInt model.pageSize ++ " item(s)..." ] + , input [ Html.Events.onInput CountChanged ] [] ] - , div [] [ button [ onClick GetNextPage ] [ text "Load next page..." ] ] , div [] [ h1 [] [ text "Response" ] - , PrintAny.view model + , PrintAny.view (model.data |> Paginator.nodes) ] ] @@ -161,23 +143,22 @@ update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of GetNextPage -> - case model of - (RemoteData.Success successResponse) :: rest -> - if successResponse.paginationData.hasNextPage then - ( RemoteData.Loading :: model, makeRequest successResponse.paginationData.cursor ) + ( model, makeRequest model.pageSize model.data ) - else - ( model, Cmd.none ) + GotResponse response -> + case response of + Ok successData -> + ( { model | data = successData }, Cmd.none ) _ -> ( model, Cmd.none ) - GotResponse response -> - case model of - head :: rest -> - ( response :: rest, Cmd.none ) + CountChanged newPageSizeString -> + case newPageSizeString |> String.toInt of + Just newPageSize -> + ( { model | pageSize = newPageSize }, Cmd.none ) - _ -> + Nothing -> ( model, Cmd.none ) diff --git a/examples/src/GithubPagination2.elm b/examples/src/GithubPagination2.elm new file mode 100644 index 000000000..fd5995725 --- /dev/null +++ b/examples/src/GithubPagination2.elm @@ -0,0 +1,210 @@ +module GithubPagination2 exposing (main) + +import Browser +import Github.Enum.SearchType +import Github.Object +import Github.Object.Repository as Repository +import Github.Object.SearchResultItemConnection +import Github.Object.SearchResultItemEdge +import Github.Object.StargazerConnection +import Github.Object.StargazerEdge +import Github.Object.User +import Github.Query as Query +import Github.Scalar +import Github.Union +import Github.Union.SearchResultItem +import Graphql.Document as Document +import Graphql.Http +import Graphql.Operation exposing (RootQuery) +import Graphql.OptionalArgument as OptionalArgument exposing (OptionalArgument(..)) +import Graphql.Paginator as Paginator exposing (Paginator) +import Graphql.SelectionSet as SelectionSet exposing (SelectionSet, with) +import Html exposing (Html, button, div, h1, input, p, pre, text) +import Html.Events exposing (onClick) +import PrintAny + + +type alias Response = + ( Int, Paginator Paginator.Forward Stargazer ) + + + +-- queryNew : Int -> Paginator Paginator.Forward Stargazer -> SelectionSet Response RootQuery + + +query : + Int + -> Paginator Paginator.Forward Stargazer + -> SelectionSet ( Int, Paginator Paginator.Forward Stargazer ) RootQuery +query pageSize paginator = + Query.repository { owner = "dillonkearns", name = "elm-graphql" } + (Repository.stargazers3 + pageSize + paginator + identity + (SelectionSet.map2 Tuple.pair + Github.Object.StargazerConnection.totalCount + (connectionSelection paginator) + ) + ) + |> SelectionSet.nonNullOrFail + + + +-- queryOld : Int -> Paginator Paginator.Forward Stargazer -> SelectionSet Response RootQuery +-- queryOld pageSize paginator = +-- Query.repository { owner = "dillonkearns", name = "elm-graphql" } +-- (Repository.stargazersPaginated +-- pageSize +-- paginator +-- identity +-- stargazerSelection +-- ) +-- |> SelectionSet.nonNullOrFail + + +type alias Stargazer = + { login : String + , starredAt : Github.Scalar.DateTime + } + + +connectionSelection : + Paginator Paginator.Forward Stargazer + -> SelectionSet (Paginator Paginator.Forward Stargazer) Github.Object.StargazerConnection +connectionSelection paginator = + Github.Object.StargazerConnection.edgesPaginator paginator stargazerSelection + + +stargazerSelection : SelectionSet Stargazer Github.Object.StargazerEdge +stargazerSelection = + SelectionSet.map2 Stargazer + -- you can grab core data from the Node like this + (Github.Object.StargazerEdge.node Github.Object.User.login) + -- plus you can grab meta-data from the "Edge" (represents the relationship) + Github.Object.StargazerEdge.starredAt + + +makeRequest : Int -> Paginator Paginator.Forward Stargazer -> Cmd Msg +makeRequest pageSize paginator = + query pageSize paginator + |> Graphql.Http.queryRequest "https://api.github.com/graphql" + |> Graphql.Http.withHeader "authorization" "Bearer dbd4c239b0bbaa40ab0ea291fa811775da8f5b59" + |> Graphql.Http.send GotResponse + + +type Msg + = GotResponse RemoteDataResponse + | GetNextPage + | CountChanged String + + +type alias Model = + { pageSize : Int + , paginator : Paginator Paginator.Forward Stargazer + , totalCount : Int + } + + +type alias RemoteDataResponse = + Result (Graphql.Http.Error Response) Response + + +initialPageSize = + 1 + + +init : Flags -> ( Model, Cmd Msg ) +init flags = + Paginator.forward + |> (\paginator -> + ( { pageSize = initialPageSize + , paginator = paginator + , totalCount = -1 + } + , makeRequest initialPageSize paginator + ) + ) + + +view : Model -> Html Msg +view model = + div [] + [ div [] + [ h1 [] [ text "Generated Query" ] + + -- , pre [] [ text (Document.serializeQuery (query model.pageSize model.data)) ] + ] + , div [] + [ p [] [ text <| String.fromInt model.totalCount ] + , button [ onClick GetNextPage ] [ text <| "Load next " ++ String.fromInt model.pageSize ++ " item(s)..." ] + , input [ Html.Events.onInput CountChanged ] [] + , paginationDetailsView model + ] + , div [] + [ h1 [] [ text "Response" ] + , PrintAny.view (model.paginator |> Paginator.nodes) + ] + ] + + +paginationDetailsView : Model -> Html msg +paginationDetailsView model = + div [] + [ "Loaded " + ++ (model.paginator + |> Paginator.nodes + |> List.length + |> String.fromInt + ) + ++ " so far" + |> text + , doneView model + ] + + +doneView model = + Html.text + (if model.paginator |> Paginator.moreToLoad then + "..." + + else + " ✅" + ) + + +update : Msg -> Model -> ( Model, Cmd Msg ) +update msg model = + case msg of + GetNextPage -> + ( model, makeRequest model.pageSize model.paginator ) + + GotResponse response -> + case response of + Ok ( totalCount, successData ) -> + ( { model | totalCount = totalCount, paginator = successData }, Cmd.none ) + + _ -> + ( model, Cmd.none ) + + CountChanged newPageSizeString -> + case newPageSizeString |> String.toInt of + Just newPageSize -> + ( { model | pageSize = newPageSize }, Cmd.none ) + + Nothing -> + ( model, Cmd.none ) + + +type alias Flags = + () + + +main : Program Flags Model Msg +main = + Browser.element + { init = init + , update = update + , subscriptions = \_ -> Sub.none + , view = view + } diff --git a/generator/src/Graphql/Generator/Field.elm b/generator/src/Graphql/Generator/Field.elm index 17c6acb3a..772e0691e 100644 --- a/generator/src/Graphql/Generator/Field.elm +++ b/generator/src/Graphql/Generator/Field.elm @@ -127,6 +127,58 @@ fieldArgsString { fieldArgs } = "(" ++ String.join " ++ " fieldArgs ++ ")" +toPaginatorFieldGenerator : Context -> Type.Field -> FieldGenerator +toPaginatorFieldGenerator ({ apiSubmodule } as context) field = + init context field.name field.typeRef + |> addRequiredArgs field context field.args + |> addOptionalArgs field context field.args + + +paginatorGenerator : Context -> TypeReference -> String -> FieldGenerator +paginatorGenerator context typeRef refName = + let + typeLock = + case ReferenceLeaf.get typeRef of + ReferenceLeaf.Object -> + ModuleName.object context (ClassCaseName.build refName) |> String.join "." + + _ -> + MyDebug.crash "TODO" + + objectArgAnnotation = + interpolate + "SelectionSet decodesTo {0}" + [ typeLock ] + in + { annotatedArgs = [] + , fieldArgs = [] + , decoderAnnotation = Graphql.Generator.Decoder.generateType context typeRef + , decoder = "(Paginator.selectionSet pageSize paginator (Pages.Object.StargazerConnection.edges object_))" + , otherThing = ".selectionForCompositeField" + , letBindings = [] + , objectDecoderChain = + " (" + ++ (Graphql.Generator.Decoder.generateDecoder context typeRef + |> String.join " >> " + ) + ++ ")" + |> Just + , typeAliases = [] + } + |> prependArg + { annotation = objectArgAnnotation + , arg = "object_" + } + |> prependArg + { annotation = "Paginator direction decodesTo" + , arg = "paginator" + } + |> prependArg + { annotation = "Int" + , arg = "pageSize" + } + + toFieldGenerator : Context -> Type.Field -> FieldGenerator toFieldGenerator ({ apiSubmodule } as context) field = init context field.name field.typeRef @@ -177,13 +229,8 @@ addOptionalArgs field context args fieldGenerator = fieldGenerator -type ObjectOrInterface - = Object - | Interface - - -objectThing : Context -> TypeReference -> String -> ObjectOrInterface -> FieldGenerator -objectThing context typeRef refName objectOrInterface = +objectThing : Context -> TypeReference -> String -> FieldGenerator +objectThing context typeRef refName = let typeLock = case ReferenceLeaf.get typeRef of @@ -239,13 +286,18 @@ type LeafRef | UnionLeaf String | EnumLeaf | ScalarLeaf + | PaginatorLeaf String leafType : TypeReference -> LeafRef leafType (Type.TypeReference referrableType isNullable) = case referrableType of Type.ObjectRef refName -> - ObjectLeaf refName + if refName |> String.endsWith "Connection" then + PaginatorLeaf refName + + else + ObjectLeaf refName Type.InterfaceRef refName -> InterfaceLeaf refName @@ -270,13 +322,13 @@ init : Context -> CamelCaseName -> TypeReference -> FieldGenerator init ({ apiSubmodule } as context) fieldName ((Type.TypeReference referrableType isNullable) as typeRef) = case leafType typeRef of ObjectLeaf refName -> - objectThing context typeRef refName Object + objectThing context typeRef refName InterfaceLeaf refName -> - objectThing context typeRef refName Interface + objectThing context typeRef refName UnionLeaf refName -> - objectThing context typeRef refName Interface + objectThing context typeRef refName EnumLeaf -> initScalarField context typeRef @@ -284,6 +336,9 @@ init ({ apiSubmodule } as context) fieldName ((Type.TypeReference referrableType ScalarLeaf -> initScalarField context typeRef + PaginatorLeaf connectionName -> + paginatorGenerator context typeRef connectionName + initScalarField : Context -> TypeReference -> FieldGenerator initScalarField context typeRef = diff --git a/generator/tests/Parser/ConnectionDetectorTests.elm b/generator/tests/Parser/ConnectionDetectorTests.elm new file mode 100644 index 000000000..282131fe5 --- /dev/null +++ b/generator/tests/Parser/ConnectionDetectorTests.elm @@ -0,0 +1,198 @@ +module Parser.ConnectionDetectorTests exposing (all) + +import Expect +import Graphql.Parser.CamelCaseName as CamelCaseName +import Graphql.Parser.ClassCaseName as ClassCaseName +import Graphql.Parser.Scalar as Scalar +import Graphql.Parser.Type as Type exposing (DefinableType(..), IsNullable(..), ReferrableType(..), TypeDefinition(..), TypeReference(..)) +import Test exposing (Test, describe, test) + + +type DetectionResult + = Miss + | SpecViolation + | Match + + +isConnection : Type.Field -> List TypeDefinition -> DetectionResult +isConnection candidateField allDefinitions = + if hasArgs candidateField then + Match + + else + Miss + + +hasPageInfo : List TypeDefinition -> Bool +hasPageInfo allDefinitions = + List.any (\def -> isPageInfo def) + allDefinitions + + +isPageInfo : TypeDefinition -> Bool +isPageInfo (TypeDefinition name typeDef description) = + ClassCaseName.raw name + == "PageInfo" + && (case typeDef of + Type.ObjectType fields -> + List.any (\currentField -> CamelCaseName.raw currentField.name == "endCursor") fields + + _ -> + False + ) + + +hasArgs candidateField = + hasArg "first" candidateField.args + && hasArg "last" candidateField.args + && hasArg "after" candidateField.args + && hasArg "before" candidateField.args + + +hasArg argName args = + List.filter (\arg -> CamelCaseName.raw arg.name == argName) args /= [] + + +all : Test +all = + describe "detect connections" + [ describe "non-objects" + [ test "scalar is not a Connection" <| + \() -> + isConnection + (field "String" "hello") + [] + |> Expect.equal Miss + ] + , describe "objects" + [ -- test "Object with correct name violates convention, should warn" <| + -- \() -> + -- isConnection + -- (fieldNew "totalCount" (Type.Scalar Scalar.Int) NonNullable) + -- [] + -- |> Expect.equal SpecViolation + {- + { + repository(owner: "dillonkearns", name: "elm-graphql") { + stargazers(first: 10, after: null) { + totalCount + pageInfo { + hasNextPage + endCursor + } + edges { + node { + login + } + starredAt + } + } + } + } + + -} + test "strict spec match" <| + \() -> + isConnection + properField + properConnectionExample + |> Expect.equal Match + ] + , describe "has page info" + [ test "same name but doesn't match" <| + \() -> + isPageInfo + (TypeDefinition (ClassCaseName.build "PageInfo") + (ObjectType + [ { args = [], description = Nothing, name = CamelCaseName.build "notRealPageInfo", typeRef = TypeReference (Type.Scalar Scalar.String) Nullable } + ] + ) + Nothing + ) + |> Expect.equal False + , test "present" <| + \() -> + isPageInfo properPageInfo + |> Expect.equal True + ] + ] + + +typeDefinition : String -> DefinableType -> TypeDefinition +typeDefinition classCaseName definableType = + TypeDefinition (ClassCaseName.build classCaseName) definableType Nothing + + +properPageInfo : TypeDefinition +properPageInfo = + typeDefinition "PageInfo" + (Type.ObjectType + [ fieldNew "hasPreviousPage" (Type.Scalar Scalar.Boolean) NonNullable + , fieldNew "hasNextPage" (Type.Scalar Scalar.Boolean) NonNullable + , fieldNew "startCursor" (Type.Scalar Scalar.String) Nullable + , fieldNew "endCursor" (Type.Scalar Scalar.String) Nullable + ] + ) + + +fieldNew : String -> Type.ReferrableType -> IsNullable -> Type.Field +fieldNew fieldName reference isNullable = + { name = CamelCaseName.build fieldName + , description = Nothing + , typeRef = TypeReference reference isNullable + , args = [] + } + + +field : String -> String -> Type.Field +field inputObjectName fieldName = + { name = CamelCaseName.build fieldName + , description = Nothing + , typeRef = TypeReference (Type.InputObjectRef (ClassCaseName.build inputObjectName)) NonNullable + , args = [] + } + + +properConnectionExample = + [ properPageInfo + , TypeDefinition (ClassCaseName.build "Query") + (ObjectType + [ properField + ] + ) + Nothing + , TypeDefinition (ClassCaseName.build "StargazerConnection") (ObjectType [ { args = [], description = Just "", name = CamelCaseName.build "edges", typeRef = TypeReference (List (TypeReference (ObjectRef "StargazerEdge") Nullable)) Nullable }, { args = [], description = Just "", name = CamelCaseName.build "pageInfo", typeRef = TypeReference (ObjectRef "PageInfo") NonNullable }, { args = [], description = Just "", name = CamelCaseName.build "totalCount", typeRef = TypeReference (Type.Scalar Scalar.Int) NonNullable } ]) Nothing + , TypeDefinition (ClassCaseName.build "StargazerEdge") (ObjectType [ { args = [], description = Just "", name = CamelCaseName.build "cursor", typeRef = TypeReference (Type.Scalar Scalar.String) NonNullable }, { args = [], description = Just "", name = CamelCaseName.build "node", typeRef = TypeReference (ObjectRef "User") NonNullable } ]) Nothing + , TypeDefinition (ClassCaseName.build "User") (ObjectType [ { args = [], description = Just "", name = CamelCaseName.build "name", typeRef = TypeReference (Type.Scalar Scalar.String) NonNullable } ]) Nothing + ] + + +properField = + { args = + [ buildArg "after" (Type.Scalar Scalar.String) Nullable + , buildArg "before" (Type.Scalar Scalar.String) Nullable + , buildArg "first" (Type.Scalar Scalar.Int) Nullable + , buildArg "last" (Type.Scalar Scalar.Int) Nullable + ] + , description = Nothing + , name = CamelCaseName.build "stargazers" + , typeRef = TypeReference (ObjectRef "StargazerConnection") NonNullable + } + + +buildArg name topLevelType isNullable = + { description = Nothing + , name = CamelCaseName.build name + , typeRef = TypeReference topLevelType isNullable + } + + + +{- + https://github.com/facebook/relay/blob/master/website/spec/Connections.md + + - Object type + - Ends in "Connection" + - Has "edges" field, List of edge type + - Has "pageInfo" field, non-null, of type PageInfo +-} diff --git a/package-lock.json b/package-lock.json index 4d5ea8a07..4710aa79c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1483,27 +1483,28 @@ } }, "elm-test": { - "version": "0.19.0-rev3", - "resolved": "https://registry.npmjs.org/elm-test/-/elm-test-0.19.0-rev3.tgz", - "integrity": "sha512-+zcutibM0LOG6uT48bMsSGzyPnptgenxBUjNMJFRYuddTrOFVH1dFCKUu512lsvihBUJixaxjIG+DjQbWlpO/Q==", + "version": "0.19.0-rev5", + "resolved": "https://registry.npmjs.org/elm-test/-/elm-test-0.19.0-rev5.tgz", + "integrity": "sha512-D/LS6Db9VIuaM3UffbBkrHmR6oLpm68EYVSru9CeHkVqUQwedVuLmZX6d0jvnU0C8I/dLSEdXlGueaPgTGhNnA==", "dev": true, "requires": { "chalk": "2.1.0", "chokidar": "1.7.0", "cross-spawn": "4.0.0", - "elmi-to-json": "0.19.0", + "elmi-to-json": "0.19.1", "find-parent-dir": "^0.3.0", "firstline": "1.2.1", "fs-extra": "0.30.0", "fsevents": "1.2.4", "glob": "7.1.1", - "lodash": "4.17.10", + "lodash": "4.17.11", "minimist": "^1.2.0", "murmur-hash-js": "1.0.0", "node-elm-compiler": "5.0.1", "split": "1.0.1", "supports-color": "4.2.0", "temp": "0.8.3", + "which": "1.3.1", "xmlbuilder": "^8.2.2" }, "dependencies": { @@ -1554,27 +1555,9 @@ "which": "^1.2.9" } }, - "find-elm-dependencies": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/find-elm-dependencies/-/find-elm-dependencies-2.0.0.tgz", - "integrity": "sha512-lnLilxwdS3U/CSPoMnfUL1u21MBNolv6NF54y4Yds7WxdRMrTBXrmugrcvIGvakWQ2anYuinmBSUR+jUQy+vpA==", - "dev": true, - "requires": { - "firstline": "1.2.0", - "lodash": "4.17.10" - }, - "dependencies": { - "firstline": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/firstline/-/firstline-1.2.0.tgz", - "integrity": "sha1-yfSIbn9/vwr8EtcZQdzgaxkq6gU=", - "dev": true - } - } - }, "fs-extra": { "version": "0.30.0", - "resolved": "http://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.30.0.tgz", "integrity": "sha1-8jP/zAjU2n1DLapEl3aYnbHfk/A=", "dev": true, "requires": { @@ -1599,17 +1582,11 @@ "path-is-absolute": "^1.0.0" } }, - "node-elm-compiler": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/node-elm-compiler/-/node-elm-compiler-5.0.1.tgz", - "integrity": "sha512-Li9NfZTL83/URoUEWly+iHJeOcZRBYUaeIL4MImnB4r21oe/xpkR6JRHjdNjLf1rMtO0tgPyOvuGW4Beytaaow==", - "dev": true, - "requires": { - "cross-spawn": "4.0.0", - "find-elm-dependencies": "2.0.0", - "lodash": "4.17.10", - "temp": "^0.8.3" - } + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true }, "supports-color": { "version": "4.2.0", @@ -1658,9 +1635,9 @@ } }, "elmi-to-json": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/elmi-to-json/-/elmi-to-json-0.19.0.tgz", - "integrity": "sha512-qNrxc1m2KAYbxT22rHyWBjzhYjJkENYgl6Ya7XVL1uxcZPiaINwFEJ7OdpGnLsM79xsWPT0z9yesQtYXKrWE7w==", + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/elmi-to-json/-/elmi-to-json-0.19.1.tgz", + "integrity": "sha512-O0Z3YsYU9OTb1hTDGORWxi69QjQFEIPfZVq/oc1D5lhL3swduHKY8vdKGuo+WlKVdTas99oNIsgL7yojWdYcsQ==", "dev": true, "requires": { "binwrap": "^0.2.0-rc2" diff --git a/package.json b/package.json index 7d1cdfe58..611653ac1 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "test": "elm-test && cd generator && elm-test", "test:watch": "elm-test --watch", "gen:starwars": "npm run build && cd examples && ../bin/elm-graphql --scalar-codecs CustomScalarCodecs https://elm-graphql.herokuapp.com --base Swapi --output src", + "gen:pages": "npm run build && cd examples && ../bin/elm-graphql http://localhost:4000 --base Pages --output gen", "gen:normalize_test": "npm run build && cd ete_tests && ../bin/elm-graphql http://localhost:4000 --base Normalize && cd -", "gen:github": "npm run build && && cd examples && ../bin/elm-graphql --introspection-file examples/github-schema.json --base Github --output src", "approve-compilation": "cd ete_tests && elm make src/NormalizeDemo.elm --output /dev/null && cd - && cd examples && elm make --output /dev/null src/Github.elm src/Starwars.elm src/complex/Main.elm src/SimpleMutation.elm", @@ -31,7 +32,7 @@ "elm-analyse": "^0.13.3", "elm-hot-loader": "0.5.4", "elm-live": "3.0.5", - "elm-test": "^0.19.0-rev3", + "elm-test": "^0.19.0-rev5", "elm-webpack-loader": "5.0.0", "fs-extra": "^5.0.0", "ts-loader": "^3.2.0", diff --git a/examples/src/Github/Object/PageInfo.elm b/src/Graphql/Internal/Paginator.elm similarity index 52% rename from examples/src/Github/Object/PageInfo.elm rename to src/Graphql/Internal/Paginator.elm index 412fa10f1..e5233eb47 100644 --- a/examples/src/Github/Object/PageInfo.elm +++ b/src/Graphql/Internal/Paginator.elm @@ -1,15 +1,5 @@ --- Do not manually edit this file, it was auto-generated by dillonkearns/elm-graphql --- https://github.com/dillonkearns/elm-graphql +module Graphql.Internal.Paginator exposing (CurrentPage, backwardSelection, forwardSelection) - -module Github.Object.PageInfo exposing (endCursor, hasNextPage, hasPreviousPage, startCursor) - -import Github.InputObject -import Github.Interface -import Github.Object -import Github.Scalar -import Github.ScalarCodecs -import Github.Union import Graphql.Internal.Builder.Argument as Argument exposing (Argument) import Graphql.Internal.Builder.Object as Object import Graphql.Internal.Encode as Encode exposing (Value) @@ -19,29 +9,57 @@ import Graphql.SelectionSet exposing (SelectionSet) import Json.Decode as Decode +type alias CurrentPage = + { cursor : Maybe String + , isLoading : Bool + } + + +forwardSelection : SelectionSet CurrentPage connection +forwardSelection = + let + object_ = + Graphql.SelectionSet.map2 CurrentPage + endCursor + hasNextPage + in + Object.selectionForCompositeField "pageInfo" [] object_ identity + + +backwardSelection : SelectionSet CurrentPage connection +backwardSelection = + let + object_ = + Graphql.SelectionSet.map2 CurrentPage + startCursor + hasPreviousPage + in + Object.selectionForCompositeField "pageInfo" [] object_ identity + + {-| When paginating forwards, the cursor to continue. -} -endCursor : SelectionSet (Maybe String) Github.Object.PageInfo +endCursor : SelectionSet (Maybe String) pageInfo endCursor = Object.selectionForField "(Maybe String)" "endCursor" [] (Decode.string |> Decode.nullable) {-| When paginating forwards, are there more items? -} -hasNextPage : SelectionSet Bool Github.Object.PageInfo +hasNextPage : SelectionSet Bool pageInfo hasNextPage = Object.selectionForField "Bool" "hasNextPage" [] Decode.bool {-| When paginating backwards, are there more items? -} -hasPreviousPage : SelectionSet Bool Github.Object.PageInfo +hasPreviousPage : SelectionSet Bool pageInfo hasPreviousPage = Object.selectionForField "Bool" "hasPreviousPage" [] Decode.bool {-| When paginating backwards, the cursor to continue. -} -startCursor : SelectionSet (Maybe String) Github.Object.PageInfo +startCursor : SelectionSet (Maybe String) pageInfo startCursor = Object.selectionForField "(Maybe String)" "startCursor" [] (Decode.string |> Decode.nullable) diff --git a/src/Graphql/Paginator.elm b/src/Graphql/Paginator.elm new file mode 100644 index 000000000..19c4efbad --- /dev/null +++ b/src/Graphql/Paginator.elm @@ -0,0 +1,132 @@ +module Graphql.Paginator exposing (Backward, Forward, PageInfo, Paginator, addPageInfo, backward, forward, moreToLoad, nodes, pageInfoOptionalArgs, selectionSet) + +import Graphql.Internal.Builder.Argument exposing (Argument) +import Graphql.Internal.Encode +import Graphql.Internal.Paginator exposing (CurrentPage) +import Graphql.OptionalArgument as OptionalArgument exposing (OptionalArgument(..)) +import Graphql.SelectionSet exposing (SelectionSet) + + +type Paginator direction data + = Paginator (PaginatorRecord data) + + +type alias PaginatorRecord data = + { nodes : List data + , currentPage : CurrentPage + , direction : Direction + } + + +forward : Paginator Forward node +forward = + Paginator + { nodes = [] + , currentPage = { cursor = Nothing, isLoading = True } + , direction = Forward + } + + +backward : Paginator Backward node +backward = + Paginator + { nodes = [] + , currentPage = { cursor = Nothing, isLoading = True } + , direction = Backward + } + + +moreToLoad : Paginator direction data -> Bool +moreToLoad (Paginator paginator) = + paginator.currentPage.isLoading + + +nodes : Paginator direction node -> List node +nodes (Paginator paginator) = + paginator.nodes + + +selectionSet : + Paginator direction decodesTo + -> SelectionSet (List decodesTo) typeLock + -> SelectionSet (Paginator direction decodesTo) typeLock +selectionSet (Paginator paginator) selection = + Graphql.SelectionSet.map3 PaginatorRecord + (selection + |> Graphql.SelectionSet.map + (\newNodes -> + case paginator.direction of + Forward -> + paginator.nodes ++ newNodes + + Backward -> + newNodes ++ paginator.nodes + ) + ) + (case paginator.direction of + Forward -> + Graphql.Internal.Paginator.forwardSelection + + Backward -> + Graphql.Internal.Paginator.backwardSelection + ) + (Graphql.SelectionSet.succeed paginator.direction) + |> Graphql.SelectionSet.map Paginator + + +type alias PageInfo pageInfo = + { pageInfo + | first : OptionalArgument Int + , last : OptionalArgument Int + , before : OptionalArgument String + , after : OptionalArgument String + } + + +{-| TODO +-} +addPageInfo : Int -> Paginator direction data -> PageInfo pageInfo -> PageInfo pageInfo +addPageInfo pageSize (Paginator paginator) optionals = + case paginator.direction of + Forward -> + { optionals + | first = Present pageSize + , after = OptionalArgument.fromMaybe paginator.currentPage.cursor + } + + Backward -> + { optionals + | last = Present pageSize + , before = OptionalArgument.fromMaybe paginator.currentPage.cursor + } + + +pageInfoOptionalArgs : Int -> Paginator direction data -> List Argument +pageInfoOptionalArgs pageSize (Paginator paginator) = + case paginator.direction of + Forward -> + [ Graphql.Internal.Builder.Argument.Argument "first" (Graphql.Internal.Encode.int pageSize) |> Just + , Maybe.map (\cursor -> Graphql.Internal.Builder.Argument.Argument "after" (Graphql.Internal.Encode.string cursor)) paginator.currentPage.cursor + ] + |> List.filterMap identity + + Backward -> + [ Graphql.Internal.Builder.Argument.Argument "last" (Graphql.Internal.Encode.int pageSize) |> Just + , Maybe.map (\cursor -> Graphql.Internal.Builder.Argument.Argument "before" (Graphql.Internal.Encode.string cursor)) paginator.currentPage.cursor + ] + |> List.filterMap identity + + +type Forward + = ForwardValue + + +type Backward + = BackwardValue + + +{-| Uses [the relay protocol](https://facebook.github.io/relay/graphql/connections.htm). +-} +type Direction + = Forward + | Backward diff --git a/tests/PaginationTests.elm b/tests/PaginationTests.elm new file mode 100644 index 000000000..9d820d2df --- /dev/null +++ b/tests/PaginationTests.elm @@ -0,0 +1,178 @@ +module PaginationTests exposing (all) + +import Dict +import Expect +import Graphql.Document +import Graphql.Http.GraphqlError as GraphqlError +import Graphql.Internal.Builder.Object as Object +import Graphql.Paginator as Paginator exposing (Paginator) +import Graphql.SelectionSet as SelectionSet +import Json.Decode as Decode +import String.Interpolate exposing (interpolate) +import Test exposing (Test, describe, test) + + +all : Test +all = + describe "pagination" + [ describe "backward" + [ test "reverse pagination items are added in correct order" <| + \() -> + backwardPageOfRequests "4" "5" "6" + |> Decode.decodeString + (Paginator.selectionSet 3 Paginator.backward edgesSelection |> Graphql.Document.decoder) + |> expectPaginator (ExpectStillLoading [ "4", "5", "6" ]) + , test "reverse pagination items are added in correct order after second request" <| + \() -> + backwardPageOfRequests "4" "5" "6" + |> Decode.decodeString (Paginator.selectionSet 3 Paginator.backward edgesSelection |> Graphql.Document.decoder) + |> Result.andThen + (\paginator -> + backwardPageOfRequests "1st" "2nd" "3rd" + |> Decode.decodeString (Paginator.selectionSet 3 paginator edgesSelection |> Graphql.Document.decoder) + ) + |> expectPaginator (ExpectStillLoading [ "1st", "2nd", "3rd", "4", "5", "6" ]) + ] + , describe "forward" + [ test "paginated items are added in correct order" <| + \() -> + forwardPageOfRequests "1st" "2nd" "3rd" + |> Decode.decodeString + (Paginator.selectionSet 3 Paginator.forward edgesSelection |> Graphql.Document.decoder) + |> expectPaginator (ExpectDoneLoading [ "1st", "2nd", "3rd" ]) + , test "second page is added in correct order" <| + \() -> + forwardPageOfRequests "1st" "2nd" "3rd" + |> Decode.decodeString (Paginator.selectionSet 3 Paginator.forward edgesSelection |> Graphql.Document.decoder) + |> Result.andThen + (\paginator -> + forwardPageOfRequests "4" "5" "6" + |> Decode.decodeString (Paginator.selectionSet 3 paginator edgesSelection |> Graphql.Document.decoder) + ) + |> expectPaginator (ExpectDoneLoading [ "1st", "2nd", "3rd", "4", "5", "6" ]) + ] + ] + + +backwardPageOfRequests : String -> String -> String -> String +backwardPageOfRequests item1 item2 item3 = + interpolate + """ + { + "data": { + "edges": [ + { + "node": { + "login3832528868": "{0}" + }, + "starredAt987198633": "2019-02-24T21:49:48Z" + }, + { + "node": { + "login3832528868": "{1}" + }, + "starredAt987198633": "2019-02-24T23:30:53Z" + }, + { + "node": { + "login3832528868": "{2}" + }, + "starredAt987198633": "2019-02-25T00:26:03Z" + } + ], + "pageInfo": { + "startCursor12867311": "Y3Vyc29yOnYyOpIAzglq1vk=", + "hasPreviousPage3880003826": true + } + } + } + """ + [ item1, item2, item3 ] + + +forwardPageOfRequests : String -> String -> String -> String +forwardPageOfRequests item1 item2 item3 = + interpolate + """ + { + "data": { + "edges": [ + { + "cursor": "Y3Vyc29yOnYyOpIAzglq1vk=", + "node": { + "login3832528868": "{0}" + }, + "starredAt987198633": "2019-02-24T21:49:48Z" + }, + { + "cursor": "Y3Vyc29yOnYyOpIAzglq7e0=", + "node": { + "login3832528868": "{1}" + }, + "starredAt987198633": "2019-02-24T23:30:53Z" + }, + { + "cursor": "Y3Vyc29yOnYyOpIAzglq-Mo=", + "node": { + "login3832528868": "{2}" + }, + "starredAt987198633": "2019-02-25T00:26:03Z" + } + ], + "pageInfo": { + "endCursor12867311": "Y3Vyc29yOnYyOpIAzglq-Mo=", + "hasNextPage3880003826": false + } + } +} + """ + [ item1, item2, item3 ] + + +edgesSelection = + Object.selectionForCompositeField "edges" [] loginFieldTotal (identity >> Decode.list) + + +loginFieldTotal = + Object.selectionForCompositeField "node" [] loginField identity + + +loginField = + Object.selectionForField "String" "login" [] Decode.string + + +type PaginationExpection node + = ExpectStillLoading (List node) + | ExpectDoneLoading (List node) + + +expectPaginator : PaginationExpection node -> Result error (Paginator direction node) -> Expect.Expectation +expectPaginator expectation result = + case expectation of + ExpectStillLoading expectedNodes -> + case result of + Ok paginator -> + { moreToLoad = paginator |> Paginator.moreToLoad + , nodes = paginator |> Paginator.nodes + } + |> Expect.equal + { moreToLoad = True + , nodes = expectedNodes + } + + Err error -> + Expect.fail (Debug.toString error) + + ExpectDoneLoading expectedNodes -> + case result of + Ok paginator -> + { moreToLoad = paginator |> Paginator.moreToLoad + , nodes = paginator |> Paginator.nodes + } + |> Expect.equal + { moreToLoad = False + , nodes = expectedNodes + } + + Err error -> + Expect.fail (Debug.toString error)