Closed tim2CF closed 6 years ago
Hey @tim2CF great question. I'll start by saying this: You seem to be conflating GraphQL types with the data types returned by resolvers, when really there is no such association at all.
So for example lets consider the following GraphQL type:
query do
field :current_user, :user do
resolve &SomeModule.current_user/3
end
end
object :user do
field :name, :string
field :age, :integer
end
It sounds like you're saying that you'd expect Absinthe to generate a user type, and that the &SomeModule.current_user/3
would be expected to return this type. What type would we generate though? The &SomeModule.current_user/3
function can return literally any elixir value.
Can you provide a more concrete proposal here?
Hello @benwilson512 ! Ok, let's consider we have this module (I just put query argument to your example)
defmodule Schema do
query name: "RootQueryType" do
field :user, :user do
arg :id, non_null(:id)
resolve &Resolver.user/2
end
end
object :user do
field :id, :integer
field :name, :string
field :age, :integer
end
end
Then compiler will generate couple Elixir modules / structures / types from this code, something like
defmodule Schema.User do
defstruct [
:id,
:name,
:age
]
@type t :: %__MODULE__{
id: nil | integer,
name: nil | String.t,
age: nil | integer
}
end
defmodule Schema.RootQueryType.Field.User do
defstruct [
:id
]
@type t :: %__MODULE__{
id: integer
}
end
So, function-resolver &Resolver.user/2
should have a spec
@spec user(Schema.RootQueryType.Field.User.t, Absinthe.Resolution.t) :: Schema.User.t
And should accept arguments and return values as typed Elixir structures, not maps. The same idea for mutations and subscriptions.
This is definitely well beyond any functionality we plan to add to Absinthe. For one thing, how could it possibly know to generate structs? What if a user is a process? What if it's some other data type. There needn't be a 1:1 mapping of GraphQL types to internal schema data types.
If someone wants to create a secondary library that auto generates this stuff by introspecting an Absinthe schema they are welcome to, but this is not functionality we plan to add to Absinthe. Absinthe is flexible, the data you use to power resolvers can be literally anything, we can't limit it to structs.
For one thing, how could it possibly know to generate structs? What if a user is a process? What if it's some other data type.
But Absinthe schema provides full info about request and response types? field
macro always accept type, so compiler always knows should it generate structure (if type is object
, list of objects or other nested type that contains object) or not (if type is scalar)
I can not imagine example when object
type can not be represented as Elixir structure
I can not imagine example when object type can not be represented as Elixir structure
This is the fundamental disconnect I think: There is no contract between the return value of a resolution function and the response shape. Absinthe makes absolutely no restrictions about the Elixir data types you are allowed to use. While structs are common, it's also VERY common to use GraphQL field names that differ from the struct names. IE:
field :is_admin, :boolean, resolve: fn parent, _, _ -> {:ok, parent.admin?} end
What would Absinthe do in this case? Generating a struct type spec with is_admin
would be wrong. Moreover structs are tied to modules, what module would it use?
Could you provide example, I think now I don't understand
Here
field :is_admin, :boolean
Type of this field is boolean, of course no reason to generate structure for standard types, in function specs standard boolean type can be used. I'm talking about 4 cases where structures and custom types can be useful
1) for queries 2) for mutations 3) for subscriptions 4) for objects (custom user types)
Ok, I think now I understand this - you mean middlewares
field :hello, :string do
middleware MyApp.Web.Authentication
resolve &get_the_string/2
middleware HandleError, :foo
end
Example from documentation - typespec of resolver here related to middlewares, not to graphql schema
I mean for a field within an object:
object :user do
field :is_admin, :boolean, resolve: fn parent, _, _ -> {:ok, parent.admin?} end
end
Trying to generate a struct definition from this would result in an is_admin
struct field, which would be wrong.
Setting aside the technical challenges, I think the approach is in general would undermine a core feature of using GraphQL: Your external schema and your internal data model can be different.
If this is something you really want to have happen you're welcome to write a third party library to do so. I'm nominally on vacation so I may not follow further responses for a while.
Has this been revisited somewhere? I can't seem to find any information on generating typespecs and not providing them means I'm bound to write them manually.
Hello. I have a question about graphql schema code generation and about resolvers. Graphql schema is strict thing with fixed amount of specific fields of specific types. But why it is not expressed in Elixir? I think it would be reasonable to operate in resolvers not just with maps, but with auto-generated (from schema) Elixir structures and typespecs.
For example similar thing was implemented for Google Protobuf here https://github.com/coingaming/exprotobuf In this library every Protobuf message generates in compile-time relevant Elixir structure and typespec for it, because in
.proto
file is provided all information about fields and types.The same information is provided in graphql schema. Structures/typespecs simplify development process a lot - if schema was changed (for example changed field name), but related resolver code was not changed - then compiler will say about it (with maps - will not).
So the question: why in absinthe resolvers data is just a map, but not structures with typespcs