sangria-graphql / sangria

Scala GraphQL implementation
https://sangria-graphql.github.io
Apache License 2.0
1.96k stars 226 forks source link

"Type ... cannot be used as an input" compilation error #202

Closed scf37 closed 7 years ago

scf37 commented 7 years ago

I got compilation errors trying to implement simple mutation api. Possibly I'm doing something wrong but I wasn't able to figure out what exactly.

Error:

Error:(18, 83) Type sangria.util.tag.@@[me.scf37.graphql1.PermissionInput,sangria.marshalling.FromInput.InputObjectResult] cannot be used as an input. Please consider defining an implicit instance of `FromInput` for it.
   implicit val MutationApiType = deriveContextObjectType[Ctx, MutationApi, Unit] {_.mutationApi}

Error:(18, 83) not enough arguments for method createWithoutDefault: (implicit fromInput: sangria.marshalling.FromInput[sangria.util.tag.@@[me.scf37.graphql1.PermissionInput,sangria.marshalling.FromInput.InputObjectResult]], implicit res: sangria.schema.ArgumentType[sangria.util.tag.@@[me.scf37.graphql1.PermissionInput,sangria.marshalling.FromInput.InputObjectResult]])sangria.schema.Argument[res.Res].
Unspecified value parameters fromInput, res.
   implicit val MutationApiType = deriveContextObjectType[Ctx, MutationApi, Unit] {_.mutationApi}

Code:

case class Permission(id: String, name: String, description: String)
case class PermissionInput(name: String, description: String)
case class Ctx(mutationApi: MutationApi)

trait MutationApi {
    @GraphQLField
    def permission(permission: PermissionInput): Permission = ???
}

object Main {
   implicit val PermissionType = deriveObjectType[Ctx, Permission]()
   implicit val PermissionInputType = deriveInputObjectType[PermissionInput]()
   implicit val MutationApiType = deriveContextObjectType[Ctx, MutationApi, Unit] {_.mutationApi}
}

Environment:

scalaVersion := "2.12.1"
Oracle 64-Bit Java "1.8.0_112"
Tried Sangria 1.0.0-RC5 and 4fe3952
sbt 0.13.13
jonas commented 7 years ago

When using macros to derive context objects with methods you need to also declare an implicit JSON decoder for each of the input type associated with the method arguments. This is what is hinted in:

Please consider defining an implicit instance of `FromInput`

If you are using Spray JSON this would be something like:

object Main extends spray.json.DefaultJsonProtocol {
   implicit val PermissionType = deriveObjectType[Ctx, Permission]()
   implicit val PermissionInputType = deriveInputObjectType[PermissionInput]()
   implicit val PermissionInputJson = jsonFormat2(PermissionInput)
   implicit val MutationApiType = deriveContextObjectType[Ctx, MutationApi, Unit] {_.mutationApi}
}
scf37 commented 7 years ago

It worked for me. In general, PLEASE consider adding scaladoc at least to public API. All that functional code with heaps of implicits is damn hard to decipher.

scf37 commented 7 years ago

Still, why deriveInputObjectType requires implicit instance of FromInput[T] if I can declare the same InputObjectType[T] manually w/o any FromInput-s?

implicit val PermissionInputType2: InputObjectType[PermissionInput] = InputObjectType[PermissionInput](
    name = "PermissionInput",
    description = "PermissionInput description",
    fieldsFn = () => List(InputField("name", StringType), InputField("description", StringType)))
OlegIlyenko commented 7 years ago

Still, why deriveInputObjectType requires implicit instance of FromInput[T] if I can declare the same InputObjectType[T] manually w/o any FromInput-s?

The FromInput is not required by the deriveInputObjectType itself, but instead it is required later on, when you are defining an Argument (or in your case when you are using deriveObjectType which creates field arguments under the hood).

In general, PLEASE consider adding scaladoc at least to public API.

I agree, scaladoc can be very helpful. During the development, I tried to spend all available time to on main high-level documentation though: http://sangria-graphql.org/learn/. I found that in many cases scaladoc does not provide a good entry point or high-level documentation, which is quite important, especially for people who are just starting with the library. So I prioritized the main documentation site.

That said, I still do value the scaladoc-style documentation. It just takes a lot of time and effort to write and maintain it. So I would really appreciate any in this area. If you figured out particular aspect of the library that was not clear, it would be really great if you could help me out with documenting it either in form of scaladoc or in main documentation site. Just a small PR already would be a big help for other people who are facing the same challenge.

OlegIlyenko commented 7 years ago

Closing this issue due to inactivity, but please feel free to reopen it if you have further questions

timzaak commented 7 years ago

@OlegIlyenko I use json4s, and have tried

case class Comment(id: Long,fromId:Long, toId: Long, content: String)
import sangria.marshalling.json4s.native._
implicit val formats = Serialization.formats(NoTypeHints)
implicit def  a(s:String) = Serialization.read[Comment](s)
implicit def  b(s:Comment) = Serialization.write(s)

but this does not work. Is there any good idea?

OlegIlyenko commented 7 years ago

@timzaak In this case you are using dynamic serialisation that json4s provides. In theory you can define FromInput and ToInput type class instances for this mechanism. Something along these lines:

implicit def json4sJacksonFromInput[T : Manifest]: FromInput[T] =
      new FromInput[JValue] {
        val marshaller = Json4sJacksonResultMarshaller

        def fromResult(node: marshaller.Node) =
          Serialization.read[T](s)
      }

but it's super dangerous since it's completely runtime and is defined for all possible types.

I would suggest you to use type class based serialisation with org.json4s.JsonFormat, org.json4s.Writer, org.json4s.Reader. You would also need to define FromInput and ToInput for these. Alternatively you can use library like circe which provides a lot of convenience for this.