Schema Builder
SchemaBuilder
is the recommended way of creating ISchema
s. It provides methods to create types and connect them to each other with fields.
Creating Root Types
[Fact]
public void Create_Query()
{
/* Given */
var builder = new SchemaBuilder();
builder.Query(out var query);
/* When */
var sut = builder.Build();
/* Then */
Assert.Equal(query, sut.Query);
}
[Fact]
public void Create_Mutation()
{
/* Given */
var builder = new SchemaBuilder();
builder.Query(out _);
builder.Mutation(out var mutation);
/* When */
var sut = builder.Build();
/* Then */
Assert.Equal(mutation, sut.Mutation);
}
[Fact]
public void Create_Subscription()
{
/* Given */
var builder = new SchemaBuilder();
builder.Query(out _);
builder.Mutation(out _);
builder.Subscription(out var subscription);
/* When */
var sut = builder.Build();
/* Then */
Assert.Equal(subscription, sut.Subscription);
}
Creating types
[Fact]
public void Create_Object()
{
/* Given */
var builder = new SchemaBuilder();
const string name = "Object1";
/* When */
builder.Object(
name: name,
out var object1,
description: "Description",
interfaces: new InterfaceType[]
{
/*interfaceType*/
},
directives: new DirectiveInstance[]
{
/*directive*/
});
/* Then */
Assert.Equal(name, object1.Name);
Assert.True(builder.TryGetType<ObjectType>(object1.Name, out _));
}
[Fact]
public void Create_Interface()
{
/* Given */
var builder = new SchemaBuilder();
const string name = "Interface1";
/* When */
builder.Interface(
name: name,
out var interface1,
description: "Description",
directives: new DirectiveInstance[]
{
/*directive*/
});
/* Then */
Assert.Equal(name, interface1.Name);
Assert.True(builder.TryGetType<InterfaceType>(interface1.Name, out _));
}
[Fact]
public void Create_Union()
{
/* Given */
var builder = new SchemaBuilder();
builder.Object("Object1", out var object1);
builder.Object("Object2", out var object2);
const string name = "Union1";
/* When */
builder.Union(
name: name,
out var union1,
description: "Description",
directives: new DirectiveInstance[]
{
/*directive*/
},
possibleTypes: new[] {object1, object2});
/* Then */
Assert.Equal(name, union1.Name);
Assert.True(builder.TryGetType<UnionType>(union1.Name, out _));
}
[Fact]
public void Create_Enum()
{
/* Given */
var builder = new SchemaBuilder();
const string name = "Enum1";
/* When */
builder.Enum(
name: name,
enumType: out var enum1,
description: "Description",
values =>
values.Value(
value: "VALUE1",
description: "Description",
directives: new DirectiveInstance[]
{
/*directive*/
},
deprecationReason: null),
directives: new DirectiveInstance[]
{
/*directive*/
}
);
/* Then */
Assert.Equal(name, enum1.Name);
Assert.True(builder.TryGetType<EnumType>(enum1.Name, out _));
}
[Fact]
public void Create_Scalar()
{
/* Given */
var builder = new SchemaBuilder();
const string name = "Url";
/* When */
builder.Scalar(
name: name,
out var url,
converter: new StringConverter(),
description: "Description",
directives: new DirectiveInstance[]
{
/*directive*/
});
/* Then */
Assert.Equal(name, url.Name);
Assert.True(builder.TryGetType<ScalarType>(url.Name, out _));
}
[Fact]
public void Create_Scalar_without_converter()
{
/* Given */
var builder = new SchemaBuilder();
const string name = "Url";
/* When */
builder.Scalar(
name: name,
out var url,
description: "Description",
directives: new DirectiveInstance[]
{
/*directive*/
});
/* Then */
Assert.Equal(name, url.Name);
Assert.True(builder.TryGetType<ScalarType>(url.Name, out _));
}
[Fact]
public void Create_InputObject()
{
/* Given */
var builder = new SchemaBuilder();
const string name = "InputObject1";
/* When */
builder.InputObject(
name: name,
out var object1,
description: "Description",
directives: new DirectiveInstance[]
{
/*directive*/
});
/* Then */
Assert.Equal(name, object1.Name);
Assert.True(builder.TryGetType<InputObjectType>(object1.Name, out _));
}
[Fact]
public void Create_DirectiveType()
{
/* Given */
var builder = new SchemaBuilder();
const string name = "Deprecated";
/* When */
builder.DirectiveType(
name: name,
out var object1,
locations: new[]
{
DirectiveLocation.FIELD
},
description: "Description",
args => args.Arg(
name: "Reason",
type: ScalarType.String,
defaultValue: "Deprecated",
description: "Description")
);
/* Then */
Assert.Equal(name, object1.Name);
}
Build and validate schema
Normal (will throw ValidationException if schema is not valid)
[Fact]
public void Build()
{
/* Given */
var builder = new SchemaBuilder()
// query is required to build schema
.Query(out _);
/* When */
var schema = builder.Build();
/* Then */
Assert.IsAssignableFrom<ISchema>(schema);
Assert.IsType<SchemaGraph>(schema);
Assert.NotNull(schema.Query);
}
Without throwing validation exception
[Fact]
public void Build_and_validate_schema()
{
/* Given */
var builder = new SchemaBuilder()
// query is required to build schema
.Query(out _);
/* When */
var (schema, validationResult) = builder.BuildAndValidate();
/* Then */
Assert.True(validationResult.IsValid);
Assert.IsAssignableFrom<ISchema>(schema);
Assert.IsType<SchemaGraph>(schema);
Assert.NotNull(schema.Query);
}
Connecting types using fields
[Fact]
public void Create_Object_field()
{
/* Given */
var builder = new SchemaBuilder();
builder.Object(
name: "Object1",
out var object1);
/* When */
builder.Connections(connect =>
{
connect.Field(
owner: object1,
fieldName: "field1",
to: ScalarType.Int,
description: "Description",
resolve: null,
subscribe: null,
directives: new DirectiveInstance[]
{
/* directive */
},
args => args.Arg(
name: "arg1",
type: ScalarType.Boolean,
defaultValue: true,
description: "Description")
);
});
/* Then */
var isDefined = false;
builder.Connections(connect => isDefined = connect.TryGetField(object1, "field1", out _));
Assert.True(isDefined);
}
[Fact]
public void Create_Interface_field()
{
/* Given */
var builder = new SchemaBuilder();
builder.Interface(
name: "Interface1",
out var interface1);
/* When */
builder.Connections(connect =>
{
connect.Field(
owner: interface1,
fieldName: "field1",
to: ScalarType.Int,
description: "Description",
resolve: null,
subscribe: null,
directives: new DirectiveInstance[]
{
/* directive */
},
args => args.Arg(
name: "arg1",
type: ScalarType.Boolean,
defaultValue: true,
description: "Description")
);
});
/* Then */
var isDefined = false;
builder.Connections(connect => isDefined = connect.TryGetField(interface1, "field1", out _));
Assert.True(isDefined);
}
[Fact]
public void Create_InputObject_field()
{
/* Given */
var builder = new SchemaBuilder();
builder.InputObject(
name: "Input1",
out var input1);
/* When */
builder.Connections(connect => connect
.InputField(
owner: input1,
fieldName: "field1",
to: ScalarType.NonNullBoolean,
defaultValue: true,
description: "Descriptipn",
directives: new DirectiveInstance[]
{
/* directive */
})
);
/* Then */
var isDefined = false;
builder.Connections(connect => isDefined = connect.TryGetInputField(input1, "field1", out _));
Assert.True(isDefined);
}
Configuring new schema based on existing schema
[Fact]
public void Use_existing_schema()
{
/* Given */
var existingSchema = new SchemaBuilder()
.Query(out var query)
.Connections(connect =>
connect.Field(query, "field1", ScalarType.String)
)
.Build();
/* When */
var schema = new SchemaBuilder().Import(existingSchema)
.Connections(connect => connect
.Field(query, "field2", ScalarType.Int)
)
.Build();
/* Then */
var queryFields = schema.GetFields(query.Name).ToList();
Assert.Single(queryFields, f => f.Key == "field1");
Assert.Single(queryFields, f => f.Key == "field2");
}
Merging schemas
[Fact]
public void Merge_schemas()
{
/* Given */
var schema1 = new SchemaBuilder()
.Query(out var query1)
.Connections(connect =>
connect.Field(query1, "field1", ScalarType.String)
)
.Build();
var schema2 = new SchemaBuilder()
.Query(out var query2)
.Connections(connect =>
connect.Field(query2, "field2", ScalarType.Int)
)
.Build();
/* When */
var schema = new SchemaBuilder().Merge(schema1, schema2).Build();
/* Then */
var queryFields = schema.GetFields(query1.Name).ToList();
Assert.Single(queryFields, f => f.Key == "field1");
Assert.Single(queryFields, f => f.Key == "field2");
}
Making executable schema
[Fact]
public async Task Make_executable_schema()
{
/* Given */
var builder = new SchemaBuilder()
.Sdl(@"
type Query {
field1: Int!
}
schema {
query: Query
}
"
);
var resolvers = new ObjectTypeMap
{
{
"Query", new FieldResolversMap
{
{
"field1", async context =>
{
await Task.Delay(1);
return Resolve.As(1);
}
}
}
}
};
/* When */
var executableSchema = SchemaTools.MakeExecutableSchema(
builder: builder,
resolvers: resolvers,
subscribers: null,
converters: null,
directives: null);
/* Then */
var result = await Executor.ExecuteAsync(
new ExecutionOptions
{
Document = Parser.ParseDocument(@"{ field1 }"),
Schema = executableSchema
});
result.ShouldMatchJson(
@"{
""data"": {
""field1"": 1
}
}");
}
Special cases
[Fact]
public void Build_with_circular_reference_between_two_objects()
{
/* Given */
var builder = new SchemaBuilder();
/* When */
var schema = builder
.Object("Object1", out var obj1)
.Object("Object2", out var obj2)
.Connections(connect => connect
.Field(obj1, "obj1-obj2", obj2)
.Field(obj2, "obj2-obj1", obj1)
.Field(obj1, "scalar", ScalarType.Int))
.Query(out var query)
.Connections(connect => connect
.Field(query, "query-obj1", obj1))
.Build();
/* Then */
var object1 = schema.GetNamedType<ObjectType>(obj1.Name);
var object1ToObject2 = schema.GetField(object1.Name, "obj1-obj2");
var object2 = schema.GetNamedType<ObjectType>(obj2.Name);
var object2ToObject1 = schema.GetField(object2.Name, "obj2-obj1");
Assert.Equal(object1, object2ToObject1.Type);
Assert.Equal(object2, object1ToObject2.Type);
}