Fork me on GitHub
#graphql
<
2017-12-06
>
souenzzo09:12:20

Lacinia do a get on the value of every relevant element from the map returned from resolver. There lacinia do it? It's a get? A select-keys?

boubalou13:12:35

It does filter out keys that aren’t expected by your model in the schema. The way it does, I don’t know, you could inspect the code base, its open source!

souenzzo14:12:30

I really dig into code, but didn't find. 😛

boubalou16:12:03

What would be the reason to know that? Any specific issue that caused something un-desired?

souenzzo16:12:15

I want to allow customize this function, maybe by some attr on schema..

hlship18:12:30

I'm not following the question; perhaps you could pose it in terms of a query, a resolved map, and an expected result map?

souenzzo23:12:57

I find it! I just understand that I just want a resolve on types

{:objects {:user {:fields {:name {:type    'String
                                  :resolve (fn [_ _ c]
                                             (get c :user/nome))}}}}}
Not working 😄

andrewtropin14:12:56

Hi, can anyone show me an example, how to report to user of graphql api an error?

sashton15:12:51

@andrewtropin errors in a graphql result should be under a top-level errors field. see the examples partway down this page: http://graphql.org/learn/validation/ Or, are you asking a about how to do it using a specific graphql implementation?

andrewtropin15:12:21

Yep, I want to know how to do it in lacinia

guy15:12:46

(defn resolve-as
  "Invoked by field resolvers to wrap a simple return value as a ResolverResult.

  The two-arguments version is a convienience around using [[with-error]].

  This is an immediately realized ResolverResult."
  ([resolved-value]
   (->ResolverResultImpl resolved-value))
  ([resolved-value resolver-errors]
   (->ResolverResultImpl (with-error resolved-value resolver-errors))))
There is this function you could use i expect

sashton15:12:20

yes, using the [resolved-value resolver-errors] variety

andrewtropin15:12:42

Yep, already found resolve-as, but it works not as expected for me

guy15:12:00

How do you use it?

guy15:12:18

I get my resolvers to return either the object or that resolve-as basically

andrewtropin15:12:36

(defn resolve-token [ctx args value] (resolve/resolve-as {:test :value} {:message :help}))

sashton15:12:44

the errors should be a vector

sashton15:12:56

[{:message :help}]

sashton15:12:17

well maybe not

guy15:12:34

What unexpected behaviour happens?

andrewtropin15:12:46

{ "data": { "get_token": { "token": "{:resolved_value #object[com.walmartlabs.lacinia.resolve$with_error$reify__40981 0x5538705f \"com.walmartlabs.lacinia.resolve$with_error$reify__40981@5538705f\"]}" } } }

andrewtropin15:12:02

instead of top-level :errors key

guy15:12:46

thats interesting

guy15:12:00

are u calling that inside the resolver?

guy15:12:21

{:resolved_value #object[com.walmartlabs.lacinia.resolve$with_error$reify__40981 0x5538705f \"com.walmartlabs.lacinia.resolve$with_error$reify__40981@5538705f\"]}

guy15:12:28

this is what you usually get when you just call resolve-as

guy15:12:30

i believe

guy15:12:54

But when thats the return value of the resolver, lacinia deals with it i think

guy15:12:03

and converts it for you to :errors

andrewtropin15:12:26

Yep, I just call it inside resolver, you can see it few messages earlier

sashton15:12:36

what version of lacinia?

guy15:12:59

it looks like u have wrapped the error in map then perhaps

guy15:12:09

"token": "{:resolved_value #object[com.walmartlabs.lacinia.resolve$with_error$reify__40981 0x5538705f \"com.walmartlabs.lacinia.resolve$with_error$reify__40981@5538705f\"]}"

guy15:12:17

are you doing something like

guy15:12:37

{:token .. }
as your return value?

guy15:12:58

(i’m making a lot of assumptions sorry)

andrewtropin15:12:57

(defn resolve-token
  [ctx args value]
  (resolve/resolve-as {:test :value} {:message :help}))
it is a resolver for token field

guy15:12:05

interesting :thinking_face:

guy15:12:49

and you’ve done something like

{:resolve-token resolve-token}
For your attach-resolvers?

andrewtropin15:12:58

{
  "data": {
    "get_token": {
      "token": "{:resolved_value #object[com.walmartlabs.lacinia.resolve$with_error$reify__40981 0x5538705f \"com.walmartlabs.lacinia.resolve$with_error$reify__40981@5538705f\"]}"
    }
  }
}
And that what I get in graphiql.

sashton15:12:23

I get an error when I try a keyword as the message {:message :help}, but it works as a string {:message "help"}. can you try that?

guy15:12:40

@sashton i get the same, thats really interesting

guy15:12:55

Why does having :help as a keyword cause it to fail do you think?

sashton15:12:29

it’s expecting string messages

guy15:12:40

oooh nice one

guy15:12:31

(str "Errors must be nil, a map, or a sequence of maps "
                         "each containing, at minimum, a :message key.")
to
(str "Errors must be nil, a map, or a sequence of maps "
                         "each containing, at minimum, a :message key with a string value.")
Perhaps?

sashton15:12:38

would you be interested in submitting a PR for that? if not I will.

andrewtropin15:12:48

:get-token           {:type    :String
                        :args    {:login    {:type :String}
                                  :password {:type :String}}
                        :resolve :authn/get-token}
(defn resolve-token
  [ctx args value]
  (resolve/resolve-as "test" {:message "help"}))
{
  "errors": [
    {
      "message": "Path de-references through a scalar type.",
      "query-path": [
        "get_token"
      ],
      "locations": [
        {
          "line": 31,
          "column": 12
        }
      ],
      "field": "token"
    }
  ]
}

andrewtropin15:12:26

I moved resolver one level up and now getting an errors key, but it's not what I want

andrewtropin15:12:29

If I use this resolver for inner field it works as I described below. For "root" mutation it shows an error, but different from I passed to resolve-as

andrewtropin15:12:16

It seems like lacinia doesn't check if it has a ResolverResult type

andrewtropin15:12:03

I spotted the problem. I have wrappers for my resolvers, which converts cases, which breaks all things =( Sorry for distracting you.

andrewtropin15:12:39

(defn wrap-resolver [f]
  (fn [context arguments value]
    (transform-to-sc (f context
                        (transform-to-kc arguments)
                        (transform-to-kc value)))))

(attach-resolvers (transform-map-values graphql-resolvers wrap-resolver))

 
Something like that.

andrewtropin15:12:28

Problem solved. It works as expected without those wrappers. Thank you for help.

andrewtropin08:12:00

Oh, thanks a lot, but I already solved it with :default-field-resolver

guy15:12:05

:thumbsup:

guy15:12:14

I didn’t do much but im glad you got it fixed!

oliy18:12:54

I've got re-graph to its first release: https://github.com/oliyh/re-graph

oliy18:12:15

The server side story for graphql is great with Lacinia and lacinia-pedestal, but I found the client side almost non-existent

oliy18:12:42

re-graph should make the client side a happy place to be!

guy19:12:43

oh i see sorry its a client too

oliy19:12:55

Hi @guy venia is for constructing the query itself using data structures instead of strings

oliy19:12:09

re-graph is for communication between the client and the server

guy19:12:11

yeah sorry i mistread

guy19:12:13

misread*

oliy19:12:36

So they complement each other, you could use them together (I intend to do this as soon as my PR for venia is merged!)

admay20:12:15

Anyone have documentation on or examples of lacinia-pedestal route set up?

admay20:12:40

Specifically how to define routes and add non-graphql related routes for things like pinging the server and echoing a request

sashton20:12:40

@admay wouldn’t the non-graphql things just be separate routes?

(route/expand-routes [["/graphql" :get graphql-interceptors :route-name ::graphql-get]
                      ["/ping" :get ping-interceptor :route-name ::ping]])

admay20:12:44

I’ve never used Pedestal before, I’m more of a Ring/Compojure guy myself. In the Lacinia docs, there’s a section in the service map function (http://walmartlabs.github.io/lacinia-pedestal/com.walmartlabs.lacinia.pedestal.html#var-service-map) about defining routes but I have no idea how to actually use it or wrap around it

sashton21:12:52

@admay that’s a new part of the api that hadn’t noticed. here’s an example that i created using that: https://github.com/sashton/lacinial-pedestal-demo/blob/master/src/graphql_demo/server.clj#L32-L35 I created the project following the steps in the lacinia-pedestal readme: https://github.com/walmartlabs/lacinia-pedestal/blob/master/README.md To see it in action:

$ lein run

$ curl localhost:8888/graphql -X POST -H "content-type: application/graphql" -d '{ hello }'
{"data":{"hello":"world"}}
$ curl localhost:8888/ping
Ping
$ curl localhost:8888/pong
Pong
Just to demonstrate, ping is a handler (like a ring-style), pong is a pedestal interceptor.

hlship21:12:52

@admay I updated the tutorial last week to cover the use of service-map: http://lacinia.readthedocs.io/en/latest/tutorial/pedestal.html

admay21:12:55

@sashton That looks exactly like what I need to do! Thanks for the code!

sashton21:12:04

@hlship It doesn’t look like the tutorial covers adding non-graphql routes

hlship21:12:23

True, that'll come later I'm sure!

hlship21:12:51

With 0.23.0/0.5.0 out, I'm deciding on what the next tutorial will be. Probably start introducing mutations, but with an Atom as the database.

hlship21:12:02

Oh, look. ReadTheDocs now puts ads at the bottom of each page. That's what we get for leaning on their free offering.

admay22:12:03

@hlship I like the idea of the next bit of functionality being mutations and then maybe using a union type IRL for an update with the object you’re updating and a status object. In other words, add an update mutation and return either the updated record or some kind of status type thing and a failure message. I’m not sure how ‘by the books’ the practice is but those failure messages have been useful to my team in the past

hlship22:12:45

To be honest, I'm still stuggling with understanding/codifying the best approach to errors. The main challenge is whether to use error maps (e.g., with resolve-as) or a union type (as you discussed) for handling expected errors. Also, classifying errors into categories, such as expected, unexpected, system failure, etc.

hlship22:12:06

I've paid lipservice to doing this virtually first in designing your schema but haven't acted on that as yet.

hlship22:12:59

We have seen some confusions in our iOS and Android teams about what to do; one team treats any error in the :errors result key as a total failure of the request (such that we actually remove errors in most cases).

hlship22:12:36

But we do need to come up with a reasonable way of marking errors that represent a failure that might be presented to the user ("That gizmo does not exist." or "You don't have authorization to frob that floob.") vs. something more problematic ("java.lang.NullPointerException: We're Screwed").

hlship22:12:40

That is, I would say that in addition to the :message key, there should be a :severity key, and perhaps a :kind key (where :kind is a unique name for the type of exception, such as :object/missing or :auth/insufficient-access).

hlship22:12:14

And :severity might be :warning, :error, :catastrophe.

hlship22:12:30

Just thinking aloud however.

admay22:12:22

This is all good sounding stuff! The reason our status type came to be was a lack of understanding in how to allow errors to propagate to the requestor. We would have a lot of ‘you can’t mutate that’ or ‘that thing doesn’t exist’ type errors that are far from app breaking. Our solution was to check the return value of our database transaction and based on that either return the thing as a thing or return a failure status type with a bit of information. It was so useful because it allowed the requestor to see things like where conditions from their query, the type that they’re using to query, etc… while keeping the ‘uh oh’ logic on the front end nice and simple.

if (thing.isAnError)
    displayTheError(thing)
else
    displayTheThing(thing)
We we’re able to avoid having to write a lot of code in our already complicated front end by leveraging the error output on the back end

admay22:12:08

As far as how Lacinia handles it, I like the idea of the error map returning the error status and message given there’s some ability to work with said map.

admay22:12:59

I would actually prefer that so that the behavior came for free rather than having to build the schema around handling errors

hlship22:12:10

And what Lacinia adds is that it automatically extends the provided error map with a lot of context (e.g., path to field in query, arguments, line/column in query document).

admay22:12:42

Without forcing developers to have to parse a request to find all that information

timgilbert23:12:40

Anyone got a quick link for hooking in authentication to lacinia-pedestal? I'd like to auth against JWT generated by a separate (Ring) auth server via buddy, and I'm still getting up to speed on pedestal

admay23:12:50

Maybe this’ll help

timgilbert23:12:02

Isn't that ring-based, though? Will ring middleware work with pedestal? I'm starting from this: http://lacinia.readthedocs.io/en/latest/tutorial/pedestal.html

admay23:12:55

There is a pong-interceptor function in there that you can go off

timgilbert23:12:16

Interesting. Thanks, I will give it a look

admay23:12:58

Maybe you can create an interceptor to parse some user info from the request, perform the authentication, and then assoc the auth token into the request on it’s way out