From the docs:
In order to discover other GraphQL types, the macros use implicits. So if you derive interdependent types, make sure to make them implicitly available in the scope.
Is this in reference to something like a deriveObjectType[RequestService, HasBInside]
where, due to the use of the derive
macro, there is no reference to the B
field in the file?
Another case here about circular object types:
In some cases you need to define a GraphQL schema that contains recursive types or has circular references in the object graph. Sangria supports such schemas by allowing you to provide a no-arg function that creates ObjectType fields instead of an eager list of fields. ... In most cases you also need to define (at least one of) these types with lazy val.
Is there some general scala background knowledge here that would hint at why this is necessary? Perhaps something related to forward referencing?
lazy
and implicit
declarations are common Scala. A lazy
variable declaration is one that isn't initialized until it's accessed the first time during runtime. This gets around the compiler errors when a variable declaration isn't found. That's why they are recommending you use lazy
on at least one variable when you have a circular reference.
@erikdstock As for implicit
this declares something that is searchable within package scopes by the compiler. If you're using the deriveObjectType[Context, Object]
that's using compiler-time reflection. So if you have nested objects within a parent object you should have code that declares those types above and as implicit
object ParentType {
def apply[F[_]: Effect]: ObjectType[MasterRepo[F], Parent] = {
implicit val entryType: ObjectType[MasterRepo[F], Entry] = deriveObjectType[MasterRepo[F], Entry]
implicit val itemType: ObjectType[MasterRepo[F], Item] = deriveObjectType[MasterRepo[F], Item]
ObjectType(
"Parent",
() =>
fields[MasterRepo[F], Parent](
Field(
"entries",
ListType(itemType),
resolve = _.value.entries
)
)
)
}
}
The implicit items at the top are located by the compiler when the Parent
object is realized
Thanks @kamisama-rney - i think I understand that much, with the exception of how the implicit
keyword actually works. Example: a lazy val
is a different object than a val
, but implicit
is just a flag. Minimal example:
trait CartTypes {
val itemType: ObjectType[RequestServices, Item]
lazy val cartType: ObjectType[Unit, Cart] = ObjectType[RequestServices, Cart](
/* ... */
fields = Field(
"items",
ListType(ItemType),
resolve = c => c.ctx.getItems(c.value.id)
)
)
}
trait ItemTypes {
implicit val cartType: ObjectType[RequestServices, ShoppingCart]
lazy val itemType = deriveObjectType[RequestServices, Item]()
}
case class Schema()(implicit val ec: ExecutionContextExecutor) extends CartTypes with ItemTypes {
val QueryType = ObjectType(
"Query",
fields[RequestServices, Unit]( /* the types are used */ )
)
}
My impression from the above is that
lazy val
s are due to interdependence on each otherval itemType: ObjectType[RequestServices, Item]
means the member is necessary to be implemented in the concrete Schema
implementation (straightforward)implicit val cartType: ObjectType[RequestServices, ShoppingCart]
does the same but adds an additional flag to the compiler to make this val available within the deriveObjectType
macro. This is because we don't refer to it directly as with an ObjectType
literal. The ItemTypes is allowed to make its implemented cartType
implicit.Is this all correct? I think part of my confusion was on lazy vs implicit and when the name bound to the variable needs to match (vs just the type). I realize some of this is just scala basics :/
final case class Thing(name: String)
implicit val objectTypeThing = deriveObjectType[SecureContext,Thing]()
trait MyMutationWithContext {
@GraphQLField
def createThing(
// ctx: User,
name: String
): Future[Option[Thing]]
}
val myMutationType =
deriveContextObjectType[SecureContext, MyMutationWithContext, Unit](_ => (??? : MyMutationWithContext))
User
- I'm new to this and don't really see a way to get that to work (I tried all the derivation methods)
SecureContext
instead of User
in there would be fine too
MyMutationWithContext
will be a field of the context already, which implies I can pass a user (which is a field in the context) at construction time
Domain name: sangria-graphql.org
Registry Domain ID: D176971954-LROR
Registrar WHOIS Server: whois.namecheap.com
Registrar URL: http://www.namecheap.com
Updated Date: 2018-09-25T18:52:13.00Z
Creation Date: 2015-07-28T21:48:18.00Z
Registrar Registration Expiration Date: 2020-07-28T21:48:18.00Z
val QueryType = ObjectType(
"Query",
fields[MyContext, Unit](
Field(
"CSO",
ListType(FlightOverviewType),
arguments = List(
Argument("showCancelled", BooleanType, defaultValue = false),
Argument("first", IntType, defaultValue = 9999999), // TODO how to make this optional?
Argument("before", StringType, defaultValue = "encoded flight leg key"),
Argument("after", StringType, defaultValue = "encoded flight leg key")
),
resolve = c => {
c.ctx
.dao
.getFlightOverviewWithUserInputs(
c.arg[Boolean]("showCancelled"),
Some(c.arg[Int]("first")),
Some(c.arg[String]("before")),
Some(c.arg[String]("after"))
)
.getOrElse(Future(Seq.empty))
}
),
Field(
"pageInfo",
PageInfoType,
resolve = c => ??? // want to use the result of getFlightOverviewWithUserInputs
)
)
)
pageInfo
. The information needed to construct pageInfo
is all there in the CSO
is sangria bring updated anymore ?
yes, i believe twitter announced an intent to continue supporting it, and there are folks here as well, but there are still big holes in the docs, sites including the playgrounds have gone down, etc
Unit
like ObjectType[Unit, User]
, but is there any real reason to do this? Perhaps for scalar values that have zero links to other parts of the schema but otherwise it seems like you would always want to give a context.
ObjectType
definitions colocated with their models, but they now require the relay helpers (idFields
, nodeInterface
etc) so they can be recognized as Node/PossibleNodeType
, which uses an implicit conversion. However Node.definition()
requires those types as well. Is it possible to define them in separate files, or do I need to give up and combine my objects into a single schema file?
x-posting from the scala channel, which is more active and because I think this might just be an issue of how scala implicit conversions work:
In short, I have an ObjectType
(from the plain sangria schema) that I need to pass as an arg of type PossibleNodeObject*
. The relay PossibleNodeObject
uses an implicit conversion, and basically requires that the ObjectType
conform to a graphql relay interface. That part is working within the same file/trait but not via trait composition, because my shared relay code requires all of these ObjectTypes be passed to it. I've tried this in my relay types trait:
type NodeType[C, V] = ObjectType[C, V] with PossibleNodeObject[C, Node]
val UserType: NodeType[RequestServices, UserId]
which appears to work for that trait, but then when I actually define UserType
(extending my RelayTypes) in another file I get "expected ObjectType PossibleNodeObject, found ObjectType". Am I trying to use an implicit conversion where it isn't actually possible?
IO
and IO.attempt
is called to draw it out as an Either
, we still don't see the Left
of the throwable. I'll be investing some time to make a small reproducible project to see if it's inherent to our setup, or whether it's something insidious in sangria itself, but just curious if any travel knowledge or "gotchas" are known or sound similar. Throwables in mutations are actually still logging and caught as far as I'm aware.
Another way to phrase this might be: Is there a type I can import from
sangria-relay
that encompasses an ObjectType that conforms to a Node type?
I also have this problem. If you found a solution, I'm interested to hear it. Without defining everything in the same SchemaDefinition as one file, I get this problem.