Giter Site home page Giter Site logo

Map field decoder about elm-graphql HOT 12 CLOSED

dillonkearns avatar dillonkearns commented on August 18, 2024 2
Map field decoder

from elm-graphql.

Comments (12)

xtian avatar xtian commented on August 18, 2024 1

We've run into this as well. We are basically handling it like this:

import Api.Object
import Api.Scalar
import Graphqelm.Field as Field
import Graphqelm.SelectionSet as SS

type alias SomeRecord =
    { int : Int
    , string : String
    }

selection : SS.SelectionSet (Result String SomeRecord) Api.Object.SomeRecord
selection =
    Api.Object.SomeRecord.selection (Result.map2 SomeRecord)
        |> SS.with
            (Api.Object.SomeRecord.intScalarField
                |> Field.map (\(Api.Scalar.IntScalar string) -> String.toInt string)
            )
        |> SS.with (Api.Object.SomeRecord.stringField |> Field.map Ok)

However this is inconvenient for selection sets with a large number of fields since we have to define our own Result.map<N> functions for any number of fields > 5.

from elm-graphql.

dillonkearns avatar dillonkearns commented on August 18, 2024 1

Thanks for sharing the additional use case @xtian, that's helpful! And thanks Sebastian for starting the discussion.

I think I need to think this over some more, as there are a lot of implications and possible considerations here. But here is my initial gut feeling on it.

Based on the use cases you described, Sebastian, I'd be a little concerned about moving the library in this direction to introduce functions to chain decoders and allow the entire response decoder to fail. The goal of the library is to feel extremely safe, and "if it compiles it works," which would be in conflict with this approach. At least in these two use cases it would concern me because it feels like workarounds for problems that would be best served by server-side solutions.

Is there an issue filed for the relay code that generates a schema with edges: [User] (with no !s)? I feel that it's really a feature, not a problem, that this library makes it a little annoying to deal with these types because it causes you to fix the root issue instead of creating a workaround on the frontend. It's best to fix the schema. (Granted, sometimes you don't own the Schema, but hopefully people can report issues in these cases too).

And for the case of the createdAt field, it looks like it's coming back from the server as a String not a DateTime scalar. I haven't used the Rails GraphQL library, but with Elixir/Absinthe you would have a resolver for DateTimes that guarantees that the format is an ISO date.

scalar :datetime, name: "DateTime" do
  serialize &DateTime.to_iso8601/1
  parse &parse_datetime/1
end

(More details in the docs on custom scalar types). There would be no need to check if it's valid and fail the decoder because it's guaranteed to be in that format.

My gut feeling is that maybe there is some middle ground approach that allows you to have a SelectionSet with all Result types and map them into a single Result. But I don't think that would address your concern Sebastian.

I need to do some more thinking on this, let's continue the discussion. I'm very much open to hearing other perspectives on this! I'm still forming my thoughts on it.

from elm-graphql.

sporto avatar sporto commented on August 18, 2024 1

@dillonkearns I will try this out hopefully today.

from elm-graphql.

itsgreggreg avatar itsgreggreg commented on August 18, 2024

My take on it is that we have 2 options:

  1. Assume that all data that comes in is always and forever valid, because graphql.

    • In sporto's example this would mean that the API could never return a "null" or "[null]" for UserEdge.node
    • In xtian's example this would mean that the API could never return a String that doesn't decode to an Int for SomeRecord.intScalarField
  2. Concede that the API could possibly return invalid data at which point we would need a SelectionSetResult type much akin to regular Json.Decode

I've not yet formed a solid opinion on which of these is the reasonable assumption.

from elm-graphql.

sporto avatar sporto commented on August 18, 2024

I don't quite follow the proposed approach with something like a DateTime scalars in Elm. We do have them in our Api. If understand well, in Elixir the library will parse that for you and give you a record with the date already parsed, is that right?

In ruby we do the same (if the date doesn't parse, ruby will throw an exception).

In the Elm side they are represented as type DateTime = DateTime String. I can think of these options:

Use the scalar as is in the record

So doing nothing resolves to:

type alias Thing = { createdAt: DateTime }

In this case we still need to unwrap and parse the contained string later if we want to do any operations with this.

Unwrap

In some places we unwrap immediatelly using Field.map so end up with {createdAt: String}. So we still need to parse this.

Unwrap and parse

We could parse using Field.map, but will end up with {createdAt: Result Time}. Doesn't seems great as you will need to handle the result for something that is supposed to be valid.

Wrap the whole GraphQL response in Result

Or wrap the whole response in a result like @xtian's example. Nice approach, never occurred to me. But this mean that you will need to deal with this extra Result later.


But what you might like is:

type alias Thing =
    { createdAt: Time }

I'm missing something about how something like a DateTime scalar is meant to be used?

from elm-graphql.

dillonkearns avatar dillonkearns commented on August 18, 2024

@sporto Yeah, my suggestion was just that you use the DateTime scalar rather than just a String to make it a little more clear semantically (and you know then that your backend is enforcing that contract so it will succeed). In that case you can of course use the Debug.crash trick since you're sure it won't fail. But that's not ideal since the whole page crashes if for some reason you're wrong.

I've done some experimentation with your proposed approach @sporto and I'm starting to like the simplicity of transforming values with it. I think I'd like to go with that approach despite the risks.

Ultimately it's a difficult balance because we want to maintain the library's guarantee that if your generated code is up-to-date (or even if it's not but the changes are backwards compatible), the decoders are guaranteed to succeed. I do worry about the idea of a single false assumption invalidating the entire API response because I want to give users something better than the experience of having to rerun your code to see if it's working! I want users to trust that "if it compiles it works," (such a liberating feeling, especially coming from JS!). I also worry about making it too easy to ignore poor schemas rather than fixing them at the source.

On the other hand, data is often unusable anyway if some of your assumptions are incorrect. And it's also common to have certain semantic ideas like DateTimes where we know that our backend will enforce a contract and guarantee that it will have certain properties (like being parseable as an ISO date-time)... and you want to be able to take advantage of those contracts easily. Or sometimes you don't control the API yourself so you're somewhat helpless.

Here's what I came up with for your suggested approach @sporto, I think it is quite nice in a lot of ways. Here's an example:

createdAt : Field Date Github.Object.Repository
createdAt =
    Repository.createdAt
        |> Field.mapOrFail
            (\(Github.Scalar.DateTime dateTime) ->
                Date.fromString dateTime
            )

And this use case of having scalars that should be transformed really does feel like it needs first-class support. It does make this library less pure in a sense, but at the same time much more powerful.

I also played around with some functions for assuming that fields are non-null:

Query.selection Response
    |> with (Query.human { id = Swapi.Scalar.Id "1001" } human |> Field.nonNullOrFail)
releases : SelectionSet ReleaseInfo Github.Object.ReleaseConnection
releases =
    Github.Object.ReleaseConnection.selection ReleaseInfo
        |> with Github.Object.ReleaseConnection.totalCount
        |> with (Github.Object.ReleaseConnection.nodes release |> Field.nonNullOrFail |> Field.nonNullElementsOrFail)

Any thoughts on this? I'm going to be on vacation for a couple of weeks so I won't be able to do any work for a bit. I'll try to wrap up some things before I leave tomorrow!

I think that @xtian's approach of mapping several fields into a single Result may also be something to explore in the future. It could potentially offer users an in-between approach that maintains the safety guarantees yet reduces the number of Maybes and Results. A safer alternative to the ...OrFail functions. I like this idea because it feels like it's inline with Elm's explicitness and the goals of this library. Interesting idea to dig into, but I'll leave that for another time!

Thanks @sporto and @xtian and @itsgreggreg for sharing your ideas and your use cases, it's incredibly helpful, and it really allows me move the library beyond what I could do with just my own perspective. I'm excited that this library is continuing to evolve and improve!

from elm-graphql.

sporto avatar sporto commented on August 18, 2024

It depends on where do you want to handle possible failures. For me, having a decoder that is guaranted to succeed is not that important if I'm going to have a Debug.crash later or a parsing error. Personally, I rather see the fail earlier in the process.

I really like that approach |> Field.nonNullOrFail or |> Field.mapOrFail if that is doable. Folks can choose not to use this and have the current guarantee.

from elm-graphql.

dillonkearns avatar dillonkearns commented on August 18, 2024

I published a new package release with these Result mapping functions, let me know what you all think!

http://package.elm-lang.org/packages/dillonkearns/graphqelm/9.1.0/Graphqelm-Field

from elm-graphql.

dillonkearns avatar dillonkearns commented on August 18, 2024

@sporto or anyone else, any feedback or requests to add here before I close this issue?

from elm-graphql.

sporto avatar sporto commented on August 18, 2024

I tried these, they work great. Thanks!

from elm-graphql.

xtian avatar xtian commented on August 18, 2024

from elm-graphql.

dillonkearns avatar dillonkearns commented on August 18, 2024

Great, thank you all for the great ideas and feedback. There are some tough tradeoffs here but I'm really happy we examined all the options thoroughly and I'm pleased with this addition to the library. Thank you so much for being so involved and helping improve the library!

from elm-graphql.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.