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.