dillonkearns / elm-graphql

Autogenerate type-safe GraphQL queries in Elm.

Home Page:https://package.elm-lang.org/packages/dillonkearns/elm-graphql/latest

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

SelectionSets with Page Cursors

dotnetspec opened this issue · comments

(I am aware of the example on this topic, and I will continue to review it. However, I don't believe it can instruct me on the cause of the issue below (?)):
My selection set definitions:

userSelectionSet : SelectionSet Data.Users.FUser SRdb.Object.User
userSelectionSet =
    Graphql.SelectionSet.succeed Data.Users.FUser
        |> Graphql.SelectionSet.with SRdb.Object.User.id_
        |> Graphql.SelectionSet.with SRdb.Object.User.active
        |> Graphql.SelectionSet.with SRdb.Object.User.description
        |> Graphql.SelectionSet.with SRdb.Object.User.email
        |> Graphql.SelectionSet.with SRdb.Object.User.ts_
        |> Graphql.SelectionSet.with SRdb.Object.User.mobile
        |> Graphql.SelectionSet.with SRdb.Object.User.username

userPageSelectionSet : SelectionSet Data.Users.FUserPage SRdb.Object.UserPage
userPageSelectionSet =
    Graphql.SelectionSet.succeed Data.Users.FUserPage
        |> Graphql.SelectionSet.with SRdb.Object.UserPage.after
        |> Graphql.SelectionSet.with SRdb.Object.UserPage.before
        |> Graphql.SelectionSet.with userSelectionSet
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

gives me:

The argument is:
    SelectionSet (Data.Users.FUser -> Data.Users.FUserPage) SRdb.Object.UserPage

But (|>) is piping it to a function that expects:
    SelectionSet (Data.Users.FUser -> Data.Users.FUserPage) SRdb.Object.User

userSelectionSet, the argument passed to function with, is a 'SelectionSet Data.Users.FUser SRdb.Object.User'. Why is the compiler seeing the argument as Object.UserPage and not Object.User?
Thanks ...

I write this as a separate comment although it comes from my attempts to address the same issue as above:
In the example at
https://package.elm-lang.org/packages/dillonkearns/elm-graphql/latest/Graphql-SelectionSet#with
it works because it references a function (mapToDateTime) that maps to values of the same type: Github.Scalar.DateTime.
However, the record fields that I want to populate from a db query are all of different types. I want to map to this record:

type alias FUser =
    { id_ : SRdb.ScalarCodecs.Id
    , active : Bool
    , description : Maybe String
    , email : Maybe String
    , ts_ : SRdb.ScalarCodecs.Long
    , mobile : Maybe String
    , username : String
    }

from a UserPage selectionSet defined as:

userPageSelectionSet : SelectionSet Data.Users.FUserPage SRdb.Object.UserPage
userPageSelectionSet =
    Graphql.SelectionSet.succeed Data.Users.FUserPage
        |> Graphql.SelectionSet.with SRdb.Object.UserPage.after
        |> Graphql.SelectionSet.with SRdb.Object.UserPage.before
        |> Graphql.SelectionSet.with SRdb.Object.UserPage.data

The closest I can get without linter errors only refers to a single value (Id):

userIdFragment : SelectionSet Id SRdb.Object.User
userIdFragment =
    Graphql.SelectionSet.map Id
        (SRdb.Object.User.id_
            |> mapToDateTime
        )

mapToDateTime : SelectionSet Id SRdb.Object.User -> SelectionSet String SRdb.Object.User
mapToDateTime =
    Graphql.SelectionSet.mapOrFail
        (\(SRdb.Scalar.Id value) ->
            Ok value
                |> Result.mapError
                    (\_ ->
                        "Failed to parse "
                            ++ value
                            ++ " as Iso8601 DateTime."
                    )
        )

and even then I haven't related it to SRdb.Object.UserPage.data in the userPageSelectionSet
What changes do I need to make this possible? thanks ...

I think the basic issue is that you need to select on a field of the SRdb.Object.UserPage object. The userSelectionSet function does not select fields on the SRdb.Object.UserPage, instead it selects fields on the SRdb.Object.User object.

Assuming the SRdb.Object.UserPage represents paging, there's probably a field in the GraphQL schema to select the list of results in the page. For example, in one of my projects I have a paging object with the items field to select the actual items in the page. The function that elm-graphql generates for that field looks like this:

items : SelectionSet decodesTo Api.Object.Course -> SelectionSet (List decodesTo) Api.Object.CoursePaged

Note in the type signature that it selects a field on the Api.Object.CoursePaged object, and expects a selection on the Api.Object.Course object to indicate what sub-fields to query. Also note that the provided selection set can result in any type (the decodesTo type parameter, in your case an FUser struct) but the resulting selection set results in a List of whatever that
thing is (in your case List FUser).

You probably have a similar function in the SRdb.Object.UserPage module. The code in your second comment makes be think that the function is called data.

The full code probably would look something like this:

userPageSelectionSet : SelectionSet Data.Users.FUserPage SRdb.Object.UserPage
userPageSelectionSet =
    Graphql.SelectionSet.succeed Data.Users.FUserPage
        |> Graphql.SelectionSet.with SRdb.Object.UserPage.after
        |> Graphql.SelectionSet.with SRdb.Object.UserPage.before
        |> Graphql.SelectionSet.with (SRdb.Object.UserPage.data userSelectionSet)

I'm not quite sure what you are trying to do in your second comment.

Apologies if the second comment wasn't clear. The point really is that I'm aware of Dillon's example on this issue and I'm attempting to leverage on that. In fact this is what I now have (based on the example):

type alias PaginationData cursorType =
    { cursor : Maybe cursorType
    , hasNextPage : Maybe String
    , data : List (Maybe Data.Users.FUser)
    }

userPageSelectionSet : SelectionSet (PaginationData String) SRdb.Object.UserPage
userPageSelectionSet =
    Graphql.SelectionSet.succeed PaginationData
        |> Graphql.SelectionSet.with SRdb.Object.UserPage.after
        |> Graphql.SelectionSet.with SRdb.Object.UserPage.before
        |> Graphql.SelectionSet.with (SRdb.Object.UserPage.data userSelectionSet)

I needed to add an extra 'data' field to the 'PaginationData' to handle the current issue. I no longer get an error for the UserPageSelectionSet, although I suspect I haven't handled this correctly as PaginationData didn't originally include the actual data returned. However, it will assist me to get to compile (there are other issues outstanding with other types) and then perhaps I can go on from there.
Thank you for your assistance. Please don't hesitate to add any further comments as I am grateful for any pointers I can get on this ...

I'll add some type signatures that might make whats going on a bit clearer - just in case you need a bit more info:

Graphql.SelectionSet.succeed PaginationData

: SelectionSet (Maybe cursorType -> Maybe String -> List (Maybe Data.Users.FUser) -> PaginationData cursorType) a

We can call succeed to create a SelectionSet containing whatever we want. In this case, we put a function inside the SelectionSet that, when given all of its arguments, will return a PaginationData struct.

The Graphql.SelectionSet.with function takes a SelectionSet that contains a function and gives that function the first argument it needs. The value that with provides as the first argument is whatever field selection you give with.

Graphql.SelectionSet.succeed PaginationData
   |> Graphql.SelectionSet.with SRdb.Object.UserPage.after

: SelectionSet (Maybe String -> List (Maybe Data.Users.FUser) -> PaginationData FUserCursor) SRdb.Object.UserPage

After using with to supply the first argument to the construction function that is inside the SelectionSet, the function now only needs two more arguments to construct a PaginationData: a Maybe String and a List (Maybe Data.Users.FUser).

Let's use with to give it the remaining arguments:

Graphql.SelectionSet.succeed PaginationData
    |> Graphql.SelectionSet.with SRdb.Object.UserPage.after
    |> Graphql.SelectionSet.with SRdb.Object.UserPage.before
    |> Graphql.SelectionSet.with (SRdb.Object.UserPage.data userSelectionSet)

: SelectionSet (PaginationData FUserCursor) SRdb.Object.UserPage

Now the SelectionSet will result in a PaginationData struct, rather than a function that will construct a PaginationData struct, because we've applied all of the arguments using the with function.

Note what would have happened if the PaginationData struct didn't require that final data field. In that case the constructor function would have had this type: Maybe cursorType -> Maybe String -> PaginationData cursorType.

Graphql.SelectionSet.succeed PaginationData
    |> Graphql.SelectionSet.with SRdb.Object.UserPage.after
    -- After the next line all of the arguments to the PaginationData constructor will have been applied
    -- and the `SelectionSet` would have the type `SelectionSet (PaginationData FUserCursor) SRdb.Object.UserPage`
    |> Graphql.SelectionSet.with SRdb.Object.UserPage.before
    -- Here the `with` function would attempt to apply another value to the function inside
    -- the SelectionSet, but the SelectionSet doesn't contain a function at this point - it contains a struct!
    -- This will result in a type error.
    |> Graphql.SelectionSet.with (SRdb.Object.UserPage.data userSelectionSet) 

Let me know if that helps clarify!

Yes, it did help to clarify how I need to think about putting the SelectionSets together and has enabled me to more closely match the example. I now have:

type alias Paginator dataType cursorType =
    { data : dataType
    , paginationData : PaginationData cursorType
    }

type alias PaginationData cursorType =
    { cursor : Maybe cursorType
    , hasNextPage : Maybe String
    }

userPageSelectionSet : SelectionSet (Paginator (List (Maybe Data.Users.FUser)) String) SRdb.Object.UserPage
userPageSelectionSet =
    Graphql.SelectionSet.succeed Paginator
        |> Graphql.SelectionSet.with (SRdb.Object.UserPage.data userSelectionSet)
        |> Graphql.SelectionSet.with userPaginationSelectionSet


userPaginationSelectionSet : SelectionSet (PaginationData String) SRdb.Object.UserPage
userPaginationSelectionSet =
    Graphql.SelectionSet.succeed PaginationData
        |> Graphql.SelectionSet.with SRdb.Object.UserPage.after
        |> Graphql.SelectionSet.with SRdb.Object.UserPage.before

Which compiles. I now have everything I need in the Paginator record and this should serve as a template for my two other types (Players and Rankings). I will apply the same principles and return if they present their own separate challenges in terms of SelectionSet definition (if not I can close the issue, of course). In the meantime, thanks again for your help ...

I have a related but different issue here, so I will close this one as it has been resolved.