Open Horusiath opened 8 years ago
This is the easiest part. Here's the builder:
type ObjectDefBuilder<'Val>(name : string) =
[<CustomOperation("description")>] member this.Description(f: ObjectDefinition<'Val>, value)
= {f with Description = Some value}
[<CustomOperation("addfield")>] member this.AddField(f: ObjectDefinition<'Val>, field: FieldDef<'Val>)
= {f with FieldsFn = lazy (f.FieldsFn.Value |> Map.add field.Name field)}
[<CustomOperation("field")>] member this.Field(f: ObjectDefinition<'Val>, name, typedef, description, args, resolve)
= this.Field(f, Define.Field(name, typedef, description , args, resolve))
member this.Zero() : ObjectDefinition<'Val> =
{
ObjectDefinition.Name = name
Description = None
FieldsFn = lazy Map.empty
Implements = [||]
IsTypeOf = None
}
member this.Yield(()) = this.Zero()
member this.Run(f: ObjectDefinition<'Val>): ObjectDef<'Val> = upcast f
member o.Bind(x,f) = f x
let outputObject<'Val> (name : string)
= new ObjectDefBuilder<'Val>(name)
// Sample
let MetadataType =
outputObject<IEnvelope WithContext> "Metadata" {
description "description"
addfield (Define.Field("createdBy", Int)) // the way to use Define.***
field "updatedAt" String "Last modified" [] (fun _ {data = e} -> e.UpdatedAt |> fmtDate)
}
I'd try to let partial field definitions such as:
let MetadataType =
outputObject<IEnvelope WithContext> "Metadata" {
description "description"
field "createdBy" Int
field "updatedAt" String {
description "Last modified"
resolve (fun _ {data = e} -> e.UpdatedAt |> fmtDate)
}
}
I didn't saw that earlier. It looks nice indeed 👍
Do we really need a custom way in F# to describe the schema? There is already the GraphQL schema language. What about writing the schema first and use a type provider to get a typed contract that we have to provide an implementation for.
Something like http://graphql.org/graphql-js/
var { graphql, buildSchema } = require('graphql');
// Construct a schema, using GraphQL schema language
var schema = buildSchema(`
type Query {
hello: String
}
`);
// The root provides a resolver function for each API endpoint
var root = {
hello: () => {
return 'Hello world!';
},
};
// Run the GraphQL query '{ hello }' and print out the response
graphql(schema, '{ hello }', root).then((response) => {
console.log(response);
});
but statically typed with a type provider.
@Lenne231 there are several issues with following graphql-js desing:
I think, we could actually simplify current schema declarations, but idea would radically change the API. Here it is: while we allowed users to define resolvers automatically, this is not default option. By default we need to provide type defs for every type we want to include in the schema. The idea here is to only be explicit about root object type, and leave the rest to be inferred as type dependency tree.
Example:
[<Interface>]
type Animal =
abstract member Name: string option
type Dog =
{ Id: string
Name: string option
Barks: bool }
interface Animal with
member x.Name = x.Name
type Cat =
{ Id: string
Name: string option
Meows: bool }
interface Animal with
member x.Name = x.Name
type Root() =
[<Query>]
member x.Animals(ctx: GQLContext, first:int, ?after:string = ""): Animal seq = ???
[<Query("dog")>] // setup GraphQL field name explicitly
member x.GetDog(id: string): Dog option = ???
[<Mutation>]
member x.AddAnimal(input:AnimalInput): Animal = ???
[<Subscription>]
member x.AnimalAdded(id:string) : IObservable<Animal> = ???
let schema = Schema<Root>(options)
would be translated into following GraphQL schema definition (naming convention could be configured via options, camelCase by default):
interface Animal {
name: String
}
type Dog implements Animal {
id: String!
name: String
barks: Boolean
}
type Cat implements Animal {
id: String!
name: String
meows: Boolean
}
type Query {
animals($first: Int, $after: String = ""): [Animal]!
dog(id: String!): Dog
}
type Mutation {
addAnimal($input:AnimalInput): Animal!
}
type Subscription {
...
}
More description:
Obsolete
attribute could be leveraged to GraphQL schema itself - members marked as Obsolete
would also appear as deprecated
in GraphQL schema.GQLContext
, an equivalent of ResolveFieldContext
, representing contextual information about currently executed query segment itself. If method doesn't define such parameter, we don't need to generate context at all.Description
(unless you know how to generate graphql description directly from xml doc comments, maybe via Roslyn?) or NoSchema
(to explicitly state that this field/method should not be exposed as part of GraphQL schema).Type system would be explicitly mapped between GQL type defs and F# types:
type A = | B of v1:int * v2:string | C of OtherType
would generate actually 3 GQL types: BCase
, CCase
and union A = BCase | CCase
- naming convention with Case
suffix would be applied to DU cases.Connection<Animal>
GQL type def would be AnimalConnection
. This conforms to Relay naming conventions. Have to add that consuming the API in this form:
https://github.com/GT-CHaRM/CHaRM.Backend/blob/master/src/CHaRM.Backend/Schema/Item.fs
makes it much more simpler and easier to implement than the current form.
We talked with @johnberzy-bazinga about idea of using cexprs for defining schemas, i.e instead of current:
have something like:
This topic is to discuss about that. Is it even possible? Is it feasible?