Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exception thrown if a field resolver returns anything but a IObj #55

Closed
stijnopheide opened this issue May 3, 2017 · 3 comments
Closed

Comments

@stijnopheide
Copy link
Contributor

If a lacinia resolver returns anything other than clojure.lang.IObj an exception is being generated. The reason for this is that it is somewhere in the call chain trying to attach meta-data to the returned value.

java.lang.ClassCastException: datomic.query.EntityMap cannot be cast to clojure.lang.IObj

This however is a bit awkward when working with plain Java return types like e.g. EntityMap from datomic.

We have worked around this by defining a custom resolver function (needed anyway to do namespaced keywords conversion).

(defn find-object-type
  [v]
  (some->> v
           (keys)
           (filter #(= "id" (name %)))
           (first)
           (namespace)))

(defn default-field-resolver
  "This is a function that accepts a field name (a keyword) and
  returns a field resolver function for the field. This includes
  transforming to datomic namespaced keywords and wrapping/unwrapping
  DatomicEntity objects."
  [field-name]
  ^ResolverResult (fn [ctx args v]
                    (let [value (if (instance? DatomicEntity v) ;; DatomicEntity is a record with only one field, the entity
                                  (get (.entity v) (keyword (find-object-type (.entity v)) (name field-name)))
                                  (get v field-name))]
                      (resolve-as
                        (if (instance? datomic.Entity value)
                          (wrappers/->DatomicEntity value)
                          value)))))

This is however only part of the solution as each root query needs to wrap it's datomic entity results in the clojure record.

We also would like our resolvers to return manifold deferred's instead of a lacinia promise because of code readability. This approach suffers the same issue as with a Datomic Entity and combining the two becomes more and more a problem.

Is there any infrastructure available to extend lacinia's default resolver functionality (not sure if I'm using the right terminology here). What would be sufficient for this use case:

  1. a function that is applied before the returned resolver result is being passed down to the type tagging functionality. Here you could wrap everything.
  2. the default resolver function that can unwrap results and get the values out for a given field (i.e. what you can specify now as ':default-field-resolver').

An example:

(defn default-field-resolver
  [field-name]
  ^ResolverResult (fn [ctx args v]
                    (resolve-as
                      (if (instance? DatomicEntity v)
                        (get (.entity v) (keyword (find-object-type (.entity v)) (name field-name)))
                        (get v field-name)))))

(defn default-resolver-middleware
  [resolver]
  (fn [ctx args v]
    (let [result (resolver ctx args v)]
      (if (instance? datomic.Entity result)
        (wrappers/->DatomicEntity result)
        result))))

(lacinia.schema/compile
  {:default-field-resolver default-field-resolver
   :default-middleware     default-resolver-middleware})
@stijnopheide
Copy link
Contributor Author

After giving it some thought, I could walk the schema to update the resolvers with the default-resolver-middleware function. But still, it would be easier if a datomic Entity (or any other Java object) would be a valid response object from a resolver.

@hlship
Copy link
Member

hlship commented May 8, 2017

We might need to look into changing the internal representation of a resolved value; today it is a map or seq of maps. We need a way to provide the value and its type tag when, as in your case, it doesn't support metadata. I would suggest a specialized record type with slots for the raw value and the type; we could then get away from using metadata for this purpose.

In terms of manifold and etc., we have informally discussed adding a schema compile option of a function to decorate resolver functions; this decorator could be responsible for converting a manifold deferred into a Lacinia ResolverResult ... ah, looking up, just like the middleware you describe.

@hlship
Copy link
Member

hlship commented May 9, 2017

See #58 for the part about middleware. I glanced over the Manifold docs, and it looks quite reasonable to add a wrapper to got from Manifold's Deferred to Lacinia's ResolverResult.

@hlship hlship changed the title requirement of lacinia resolvers to return a clojure.lang.IObj too strict? Exception thrown if a field resolver returns anything but a IObj May 9, 2017
@hlship hlship added this to the 0.17.0 milestone May 15, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

2 participants