andreas / ocaml-graphql-server

GraphQL servers in OCaml
MIT License
624 stars 60 forks source link

Deriving schema from types #185

Open bram209 opened 4 years ago

bram209 commented 4 years ago

It would be nice if we could derive a schema directly from a type, for example:

type user = {
  id   : int [@description "Unique user identifier"];
  name : string [@name "firstName"];
  role : role;
}
[@@deriving graphql_object]

would generate the following code:

Schema.(obj "user"
  ~fields:(fun _ -> [
    field "id"
      ~doc:"Unique user identifier"
      ~typ:(non_null int)
      ~args:Arg.[]
      ~resolve:(fun info p -> p.id)
    ;
    field "firstName"
      ~typ:(non_null string)
      ~args:Arg.[]
      ~resolve:(fun info p -> p.name)
    ;
    field "role"
      ~typ:(non_null role)
      ~args:Arg.[]
      ~resolve:(fun info p -> p.role)
  ])
)

thoughts?

sgrove commented 4 years ago

What about using SDL to generate the types and resolvers (and use a few custom directives to control the generation)?

So the above could be written as:

"""
User role
"""
# Specify that a GraphQL type should specify an existing OCaml type:
enum Role @useExistingType(name: "Roles.t") {
  admin
  user
}

# Otherwise generate a new type definition (and resolvers) for our GraphQL type
type User {
  id: int!
  """
  Unique user identifier
  """
  firstName: String! @rename(lookupKey: "name") # Specify that the record field name should actually be `name` while the GraphQL field name is `firstName`
  role: role
}
andreas commented 4 years ago

Hey @bram209 😄 I've been toying in my mind with such an idea myself, and I think it could be great for simple GraphQL objects. It's not obvious to me how it can be adapted for more complex use cases though (e.g. fields with arguments).

@sgrove: I really like the idea of a PPX based on the SDL. I've been wondering whether it could generate a functor, where the user supplies the types and resolvers. As an example, the following SDL:

type User {
  id: int!
  friends(first: int): [User!]
}

type Query {
  me: User!
}

schema {
  query: Query
}

... would generate a functor with the following type:

functor (S : sig
  type ctx

  module User : sig
    type t
    val id : t -> int
    val friends : ctx Schema.resolve_info -> t -> first:int option -> t list option
  end

  module Query : sig
    type t
    val me : ctx Schema.resolve_info -> t -> User.t
  end
end) -> sig
  val schema : S.ctx Schema.schema
end
cem2ran commented 4 years ago

Been thinking about this for a while too. (Pardon the Reason syntax), but do you think something like this is viable w.r.t defining resolvers?

let name_resolver = (info, p) => p.name;

type role = 
| [@desc "A regular user"] User 
| [@desc "An admin user"] Admin
| Test;

[@deriving graphql_object]
type user = {
  id: [@desc "Unique user identifier"] int,
  name: [@name "firstName"][@resolver name_resolver] string,
  role,
  age: option(int),
  friends: list(user)
};

https://astexplorer.net/#/gist/842c826d3dbd3ce841f33ff1d7e8ba26/d8ce85315ec79cf0b3906121deb1a7178ea52a83

@sgrove I believe you have done some work on codegen from record type declarations? I'd be happy to go down this rabbit hole with some guidance!

cem2ran commented 4 years ago

W.r.t. the SDL approach, it looks like the sister project (targeting node.js) has a PR open for SDL parsing & printing! https://github.com/sikanhe/reason-graphql/pull/31

andreas commented 4 years ago

W.r.t. the SDL approach, it looks like the sister project (targeting node.js) has a PR open for SDL parsing & printing! sikanhe/reason-graphql#31

Yeah, unfortunately it's with a handrolled parser rather than using Menhir or similar 😕

cem2ran commented 4 years ago

Right :/

Good news though. The following seems to be: https://github.com/tahnik/graphqlx/blob/master/src/language/src/parser.mly

Might be missing things such as description. Maybe now is the time I'll finally get to try out Menhir 😄

sgrove commented 4 years ago

W.r.t. the SDL approach, it looks like the sister project (targeting node.js) has a PR open for SDL parsing & printing! sikanhe/reason-graphql#31

Yeah, unfortunately it's with a handrolled parser rather than using Menhir or similar 😕

I don't know if that's the worst thing for a production project, given the error messages Menhir or similar tend to produce (not that a handrolled parser will give good error messages by default)...