Leto Generator
Generates package:leto_schema
's GraphQLSchema
s from annotated Dart classes and functions. This is a code-first generator which will generate different GraphQL elements based on annotations in Dart code.
Usage
Usage is very simple. You just need @GraphQLObject()
annotation
on any class you want to generate an object type for. And the @Query()
, @Mutation()
or Subscription()
annotations for resolver functions.
Individual fields can have a @GraphQLDocumentation()
or @GraphQLField()
annotation, to provide information
like descriptions, deprecation reasons, etc.
There are many more annotations that you can explore in the annotations section.
Add the following dependencies to your pubspec.yaml
:
dependencies:
leto_schema:
dependencies:
leto_generator:
build_runner:
Annotate your classes, fields and functions:
import 'package:leto_schema/leto_schema.dart';
part 'file_name.g.dart';
@GraphQLObject()
class ObjectName {
final String fieldName;
const ObjectName({required this.fieldName});
}
@Query()
ObjectName getObject(ReqCtx ctx, String name) {
return ObjectName(fieldName: name);
}
Run the code generator:
dart run build_runner watch --delete-conflicting-outputs
A file_name.g.dart
should be generated with the ObjectName
's GraphQLObjectType
and a field for
the getObject
query along with a lib/graphql_api.schema.dart
file with the GraphQLSchema
for your project.
This schema will have the getObject
resolver in the root Query
type.
Table of contents
- Examples
- Annotations (Decorators)
- Dart Type to GraphQLType coercion
- Resolvers
- Global Configuration (build.yaml)
Examples
Multiple examples with tests can be found in the examples folder.
Most of the examples showcased in the main documentation also use code generation.
Annotations (Decorators)
All annotations with documentation and the supported configuration parameters can be found in package:leto_schema
's decorators file.
Outputs
Annotations for GraphQL Output Types.
GraphQLObject
Generate GraphQLObjectType
s and Interfaces with this annotation. The constructor provides a couple of parameters to configure the generated fields.
GraphQLField
Configures the generation of a GraphQLObjectField
in a GraphQLObjectType
.
In this example, the omitFields
parameter is used to omit all fields by default from the generation. Also an usage of the interfaces
parameter is shown, a better approach for specifying interfaces is shown in the Interfaces section.
final customInterface = objectType<Object>(
'ClassConfig2Interface',
fields: [
graphQLString.nonNull().field('value'),
],
isInterface: true,
);
@GraphQLObject(omitFields: true, interfaces: ['customInterface'])
class ClassConfig2 {
@GraphQLField()
final String value;
@GraphQLField(nullable: true)
final String valueOverridden;
final String notFound;
@GraphQLField(name: 'renamedValue2')
final String value2;
const ClassConfig2({
required this.value,
required this.valueOverridden,
required this.notFound,
required this.value2,
});
}
Fields annotated with @GraphQLField()
will appear in the type definition, but notFound
will not since omitFields: true
and notFound
is not annotated with @GraphQLField()
. The type implements the interface specified in the annotation.
type ClassConfig2 implements ClassConfig2Interface {
value: String!
valueOverridden: String
renamedValue2: String!
}
The following class uses the nullableFields
parameter to override the default nullability type inference. When true, all fields will be nullable by default.
@GraphQLObject(nullableFields: true, name: 'RenamedClassConfig')
class ClassConfig {
@GraphQLDocumentation(deprecationReason: 'value deprecated')
@GraphQLField()
final String value;
final String valueOverridden;
final String? valueNull;
@GraphQLField(nullable: true)
final String value2;
ClassConfig({
required this.value2,
required this.value,
required this.valueOverridden,
this.valueNull,
});
}
The previous Dart code will generate a GraphQL Object Type with the following definition:
type RenamedClassConfig {
value: String! @deprecated(reason: "value deprecated")
valueOverridden: String
valueNull: String
value2: String
}
Interfaces
You may use abstract classes to specify that a given class annotated with @GraphQLObject()
should generate a GraphQLInterface. Implemented classes that generate GraphQLInterfaces will appear as an interface of a the generated Object or Interface.
The following annotated Dart classes show the behavior.
@GraphQLObject()
abstract class NestedInterface {
Decimal get dec;
}
@GraphQLObject()
abstract class NamedInterface {
String? get name;
}
@GraphQLObject()
class NestedInterfaceImpl implements NestedInterface {
@override
final Decimal dec;
final String? name;
NestedInterfaceImpl(this.name, this.dec);
}
@GraphQLObject()
class NestedInterfaceImpl2 implements NestedInterfaceImpl {
@override
final Decimal dec;
@override
final String? name;
final String name2;
NestedInterfaceImpl2({
required this.dec,
required this.name,
required this.name2,
});
}
@GraphQLObject()
class NestedInterfaceImpl3 extends NestedInterfaceImpl
implements NamedInterface {
final String name3;
NestedInterfaceImpl3({
required Decimal dec,
required String? name,
required this.name3,
}) : super(name, dec);
}
Will generate the following GraphQL definitions:
interface NestedInterface {
dec: Decimal!
}
interface NamedInterface {
name: String
}
type NestedInterfaceImpl implements NestedInterface {
dec: Decimal!
name: String
}
type NestedInterfaceImpl2 implements NestedInterface {
dec: Decimal!
name: String
name2: String!
}
type NestedInterfaceImpl3 implements NamedInterface & NestedInterface {
name3: String!
dec: Decimal!
name: String
}
GraphQLUnion
Unions allow you to specify that a given value can be one of multiple possible objects. For code generation we use freezed-like unions where factory constructors specify the different properties for the different objects. Other annotations such as @GraphQLField()
, @GraphQLDocumentation()
and freezed's @Default
will also work as shown in the example.
@GraphQLObject()
@freezed
class UnionA with _$UnionA {
const factory UnionA.a1({
// five with default
@Default(5) int one,
}) = _UnionA1;
const factory UnionA.a2({
@JsonKey(fromJson: decimalFromJson, toJson: decimalToJson)
@Deprecated('custom deprecated msg')
Decimal? dec,
}) = _UnionA2;
const factory UnionA.a3({
@GraphQLDocumentation(description: 'description for one') List<int>? one,
}) = UnionA3;
const factory UnionA.a4({
@GraphQLField(name: 'oneRenamed') required List<int> one,
}) = _UnionA4;
factory UnionA.fromJson(Map<String, Object?> json) => _$UnionAFromJson(json);
}
union UnionA = UnionA1 | UnionA2 | UnionA3 | UnionA4
type UnionA1 {
"""five with default"""
one: Int!
}
type UnionA2 {
dec: Decimal @deprecated(reason: "custom deprecated msg")
}
type UnionA3 {
"""description for one"""
one: [Int!]
}
type UnionA4 {
oneRenamed: [Int!]!
}
If you don't use package:freezed
, your can still generate unions with the same Dart definition, but actually defining the constructors for each possible object in the union:
GraphQLAttachments unionNoFreezedAttachments() => const [ElementComplexity(50)];
@AttachFn(unionNoFreezedAttachments)
@GraphQLDocumentation(
description: '''
Description from annotation.
Union generated from raw Dart classes''',
)
@GraphQLUnion(name: 'UnionNoFreezedRenamed')
class UnionNoFreezed {
const factory UnionNoFreezed.a(String value) = UnionNoFreezedA.named;
const factory UnionNoFreezed.b(int value) = UnionNoFreezedB;
}
@GraphQLObject()
class UnionNoFreezedA implements UnionNoFreezed {
final String value;
const UnionNoFreezedA.named(this.value);
}
@GraphQLObject()
class UnionNoFreezedB implements UnionNoFreezed {
final int value;
const UnionNoFreezedB(this.value);
}
@Query()
List<UnionNoFreezed> getUnionNoFrezzed() {
return const [UnionNoFreezed.a('value'), UnionNoFreezed.b(12)];
}
Which generates code for the following GraphQL definitions:
"""
Description from annotation.
Union generated from raw Dart classes
"""
union UnionNoFreezedRenamed @cost(complexity: 50) = UnionNoFreezedA | UnionNoFreezedB
type UnionNoFreezedA {
value: String!
}
type UnionNoFreezedB {
value: Int!
}
Inputs
Annotations for GraphQL Input Types.
GraphQLInput
Specifies that a given class should generate a GraphQLInputObject
. All classes annotated with @GraphQLInput()
should provide a fromJson
factory or static method as shown in the following examples. You can use packages such us json_serializable
to generate the serialization code.
GraphQLArg
This annotation allows you to specify a default value for Input types in the schema. The type with a default value should support de-serializing the provided default value or should be able to be serialized with a toJson
method. This will also work for arguments in resolvers as shown in the Resolver Inputs section.
@GraphQLInput(name: 'InputMNRenamed')
class InputMN {
final String name;
final InputM? parent;
final Json json;
final List<Json> jsonListArgDef;
final List<List<InputM>?>? parentNullDef;
static List<List<InputM>?> parentNullDefDefault() => [
null,
[
const InputM(
name: 'defaultName',
nested: [],
nestedNullItem: [],
ints: [0, 0],
doubles: [0, 0.1],
)
]
];
const InputMN({
required this.name,
this.parent,
this.json = const JsonList([JsonNumber(1)]),
@GraphQLArg(defaultCode: 'const [JsonMap({})]')
required this.jsonListArgDef,
@GraphQLArg(defaultFunc: parentNullDefDefault) this.parentNullDef,
});
factory InputMN.fromJson(Map<String, Object?> json) {
return InputMN(
name: json['name']! as String,
parent: json['parent'] != null
? InputM.fromJson(json['parent']! as Map<String, Object?>)
: null,
json: Json.fromJson(json['json']),
jsonListArgDef: List.of(
(json['jsonListArgDef'] as List).map(
(Object? e) => Json.fromJson(e),
),
),
parentNullDef: json['parentNullDef'] != null
? List.of(
(json['parentNullDef']! as List<Object?>).map(
(e) => e == null
? null
: List.of(
(e as List<Object?>).map(
(e) => InputM.fromJson(e as Map<String, Object?>),
),
),
),
)
: null,
);
}
Map<String, Object?> toJson() => {
'name': name,
'parent': parent,
'json': json,
'jsonListArgDef': jsonListArgDef,
if (parentNullDef != null) 'parentNullDef': parentNullDef,
};
}
Generic input types are supported. However the API may change in the future. Your fromJson
method should have generic argument factories as parameters, functions that return the generic instance from a serialized value. You can use the @JsonSerializable(genericArgumentFactories: true)
if using json_serializable
as shown in the example.
@GraphQLInput()
@JsonSerializable(genericArgumentFactories: true)
class InputGen<T> {
final String name;
final T generic;
const InputGen({
required this.name,
required this.generic,
});
factory InputGen.fromJson(
Map<String, Object?> json,
T Function(Object?) fromJsonT,
) =>
_$InputGenFromJson(json, fromJsonT);
Map<String, Object?> toJson() => {'name': name, 'generic': generic};
}
Resolver Inputs
// TODO: 1G @FromCtx()
Type.fromCtx;
Authentication (admin|role);
For resolvers, you just specify the type that you want as input and the input GraphQL type will be included in the generated field definition. You can use the @GraphQLArg()
annotation to specify a default value or specify the default value directly in the dart code if it can be a const
Dart definition.
List<Decimal?> _defaultListDecimalNull() => [null, Decimal.parse('2')];
GraphQLType<dynamic, dynamic> _timestampsType() =>
graphQLTimestamp.list().nonNull();
@GraphQLEnum()
enum EnumValue { v1, v2, v3 }
final enumCustomGraphQLType = enumType<int>(
'EnumCustom',
{
'TWO': 2,
'THREE': 3,
},
);
const testManyDefaultsGraphQLStr =
'testManyDefaults(str: String! = "def", intInput: Int! = 2,'
' doubleInput: Float! = 3.0, doubleInputNull: Float = 4.2,'
' boolean: Boolean! = true, listStr: [String!]! = ["dw", "dd2"],'
' listDecimalNull: [Decimal] = [null, "2"],'
' listUri: [Uri!]! = ["http://localhost:8060/"],'
' date: Date! = "2021-03-24T00:00:00.000",'
' gen: InputGenIntReq = {name: "gen", generic: 2},'
' enumValue: EnumValue! = v1, enumCustom: EnumCustom = THREE,'
' enumCustomList: [EnumCustom!]! = [TWO],'
' timestamps: [Timestamp]! = [1611446400000, null],'
' json: Json! = {d: [2]}): String!';
@Query()
String testManyDefaults({
String str = 'def',
int intInput = 2,
double doubleInput = 3,
double? doubleInputNull = 4.2,
bool boolean = true,
List<String> listStr = const ['dw', 'dd2'],
@GraphQLArg(defaultFunc: _defaultListDecimalNull)
List<Decimal?>? listDecimalNull,
@GraphQLArg(defaultCode: "[Uri.parse('http://localhost:8060/')]")
required List<Uri> listUri,
@GraphQLArg(defaultCode: 'DateTime.parse("2021-03-24")')
required DateTime date,
@GraphQLArg(defaultCode: "InputGen(name: 'gen', generic: 2)")
InputGen<int>? gen,
EnumValue enumValue = EnumValue.v1,
@GraphQLDocumentation(typeName: 'enumCustomGraphQLType')
int enumCustom = 3,
@GraphQLDocumentation(
typeName: 'enumCustomGraphQLType.nonNull().list().nonNull()',
)
List<int> enumCustomList = const [2],
@GraphQLArg(defaultCode: '[DateTime.parse("2021-01-24"), null]')
@GraphQLDocumentation(type: _timestampsType)
required List<DateTime?> timestamps,
Json json = const Json.map({
'd': Json.list([Json.number(2)])
}),
}) {
Other
Other miscellaneous decorators include the general GraphQLDocumentation
, GraphQLEnum
to generate a GraphQLEnumType
and AttachFn
to specify attachments.
GraphQLDocumentation
Dart comments for all elements will be taken as the description in the generated GraphQLType or Field. Also, Dart's @Deprecated()
annotation can be used for setting the deprecationReason
for fields, input fields, arguments and enum values. Another way, which will override the previous two, is by using the @GraphQLDocumentation()
with the description
and deprecationReason
params.
The GraphQLType of a field, input field, argument or class can be configured using the type
or typeName
params. More information in the GraphQLDocumentation type parameters section.
GraphQLEnum
Enums work as expected using the @GraphQLEnum()
annotation. The valuesCase
parameter can be used to specify the case of the generated GraphQL enum definition. Some example of simple enums:
/// comments for docs
@GraphQLEnum(name: 'SimpleEnumRenamed')
enum SimpleEnum {
@AttachFn(simpleVariantAttachments)
simpleVariantOne,
SIMPLE_VARIANT_TWO,
}
GraphQLAttachments simpleVariantAttachments() => const [CustomAttachment()];
@GraphQLEnum(valuesCase: EnumNameCase.snake_case)
enum SnakeCaseEnum {
@GraphQLDocumentation(description: 'description from annotation')
@Deprecated('custom deprecated')
variantOne,
/// Documentation for variant two
variantTwo,
}
The SimpleEnum
Dart enum will generate the following GraphQL definition:
enum ClassEnum @cost(complexity: 2) {
VARIANT_ONE
"""The second variant docs"""
VARIANT_TWO
errorRenamed
}
And the SnakeCaseEnum
Dart enum will generate the following GraphQL definition:
enum SnakeCaseEnum {
"""description from annotation"""
variant_one @deprecated(reason: "custom deprecated")
"""Documentation for variant two"""
variant_two
}
You can also provide a custom enum class, you will need to annotate each static variant with the @GraphQLEnumVariant()
decorator and have a Class.values
static getter. All variants should be of the same type as the Enum class. An example of this is shown in the following code snippet.
GraphQLAttachments classEnumAttachments() => const [ElementComplexity(2)];
@AttachFn(classEnumAttachments)
@GraphQLEnum(valuesCase: EnumNameCase.CONSTANT_CASE)
@immutable
class ClassEnum {
final int code;
final bool isError;
const ClassEnum._(this.code, this.isError);
@GraphQLEnumVariant()
static const variantOne = ClassEnum._(100, false);
/// The second variant docs
@GraphQLEnumVariant()
static const variantTwo = ClassEnum._(201, false);
@GraphQLEnumVariant(name: 'errorRenamed')
@AttachFn(variantErrorAttachments)
static const variantErrorThree = ClassEnum._(300, true);
static GraphQLAttachments variantErrorAttachments() =>
const [CustomAttachment()];
static const List<ClassEnum> values = [
variantOne,
variantTwo,
variantErrorThree,
];
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is ClassEnum && other.code == code && other.isError == isError;
}
@override
int get hashCode => code.hashCode ^ isError.hashCode;
}
AttachFn
You can use the AttachFn
decorator over a class, field or function or argument to specify Attachments for a GraphQLElement. You pass a function that returns a list of attachments. More documentation on the usage can be found in usage for code generation.
Generics
You can use generics with code generation. For example a simple wrapper around an error like this:
@GraphQLObject()
class ErrCodeInterface<T extends Object> {
final String? message;
final T code;
const ErrCodeInterface(this.code, [this.message]);
}
Will generate something a function that returns a GraphQLType
with the generic as a GraphQLType
passed as argument:
final _types =
HotReloadableDefinition<Map<String, GraphQLObjectType<ErrCodeInterface>>>(
(_) => {});
/// Auto-generated from [ErrCodeInterface].
GraphQLObjectType<ErrCodeInterface<T>>
errCodeInterfaceGraphQLType<T extends Object>(
GraphQLType<T, Object> tGraphQLType, {
String? name,
}) {
final __name = name ?? 'ErrCodeInterface${tGraphQLType.printableName}';
if (_types.value[__name] != null) {
return _types.value[__name]! as GraphQLObjectType<ErrCodeInterface<T>>;
}
final __types = objectType<ErrCodeInterface<T>>(
__name,
isInterface: false,
interfaces: [],
);
_types.value[__name] = __types;
__types.fields.addAll(
[
graphQLString.field('message', resolve: (obj, ctx) => obj.message),
tGraphQLType.nonNull().field('code', resolve: (obj, ctx) => obj.code),
],
);
return __types;
}
It also has an optional name property to override the default generic name that is constructed
from the base name and the generic GraphQLType
s. This name can be used with the genericTypeName
.
//TODO: 2G genericTypeName should be usable in other situations. Maybe fields or in GraphQLDocumentation
for all
Generics Input
You can also use generics for inputs like the following example. In this case, the fromJson
factory should receive a
function that parses the generic values (as many function as there are generic type parameters).
@GraphQLInput()
@JsonSerializable(genericArgumentFactories: true)
class InputGen<T> {
final String name;
final T generic;
const InputGen({
required this.name,
required this.generic,
});
factory InputGen.fromJson(
Map<String, Object?> json,
T Function(Object?) fromJsonT,
) =>
_$InputGenFromJson(json, fromJsonT);
Map<String, Object?> toJson() => {'name': name, 'generic': generic};
}
Dart Type to GraphQLType coercion
Dart types specified as fields of classes or input parameters in resolvers will be coerced into GraphQLType
s using the following rules.
Default type mappings
Dart Type | GraphQL Type |
---|---|
String |
String |
int |
Int |
double /num |
Float |
bool |
Boolean |
String /int (with id as name, configurable) |
ID |
List<T> |
T |
DateTime |
Date (custom scalar) |
Uri |
Uri (custom scalar) |
BigInt |
BigInt (custom scalar) |
By default, a non-nullable Dart String
(or any type) represents a GraphQL String!
and a nullable Dart String?
represents a GraphQL String
, however that can be configured for each field, class or for the whole project.
Provided type annotations
Using the provided type annotations, a GraphQLType will be created for the given class or enum. You can use any type with a type annotation as part of another type or in the definition of a resolver function.
Class.graphQLType static getter
If you have control over the class definition, you can provide a static graphQLType
getter in the class. This will be used as the GraphQL type in other parts of code generation.
Typically used for scalar types that you control (not from another package) or for generic types that need more customization (usage of GraphQLUnionType.extractInner
for example).
Some examples of this are the Json
, PageInfo
and Result<T, E>
provided types.
It is important when using a static graphQLType
getter that it returns the same GraphQLType instance each time it is called. This prevents Leto from the same seeing a different GraphQLType each time the type is used and creating a cyclical dependency. An example:
final fooGraphQLType = GraphQLScalarTypeValue<Foo, String>(
name: 'Foo',
deserialize: (_, serialized) => fooFromJson(serialized)!,
serialize: (value) => fooToJson(value)!,
validate: (key, input) => (input is String)
? ValidationResult.ok(input)
: ValidationResult.failure(
['Expected $key to be a String.'],
),
description: 'A Foo.',
specifiedByURL: null,
);
Foo? fooFromJson(Object? value) => value == null ? null : Foo(value.toString());
String? fooToJson(Foo? value) => value?.bar;
class Foo {
String bar;
Foo(this.bar);
static GraphQLScalarTypeValue<Foo, String> get graphQLType => fooGraphQLType;
}
customTypes in build.yaml
Another way of mapping a Dart type to a GraphQLType
instance is by using the "customTypes" build.yaml
global config option explained in the customTypes section. This will override (take precedence over) all other type mappings.
Useful for types that you don't control like the Decimal
type from https://github.com/a14n/dart-decimal or IsoDuration
from https://github.com/mzdm/iso_duration_parser.
@GraphQLDocumentation(type: Function, typeName: String)
If you want to customize a single field or argument with a GraphQLType different from the one inferred by the library, you can provide a static function (GraphQLType Function()
) which returns the expected type in the type
parameter of @GraphQLDocumentation(type: )
. For the same purpose, we provide a typeName
String value, which should be the getter (in Dart code) of the expected GraphQLType
. The code generation will throw an exception if you provide both at the same time.
Resolvers
Resolvers execute the main logic in an executable GraphQLSchema
. These are functions that represent queries, mutations or subscriptions.
A Class annotated with @GraphQLObject()
will generate resolver fields for all its methods and properties. Resolver input parameters were discussed in the inputs section.
TODO: 2G BeforeResolver
Function Resolvers
You can use the @Query()
, @Mutation()
and @Subscription()
annotation to specify that a given method or function is a field in the given root object type (Query, Mutation or Subscription root objects).All function annotated with @Subscription()
should return a Stream
of values.
These annotations have the following parameters:
- name (default: the name of the method)
This will be the name of the GraphQL field.
- genericTypeName (default: null)
When the return type is a generic type, you can override the GraphQL type name with a custom String. Most generic types provide a default name composed from the type parameters (using GraphQLType.printableName
, for example), so this parameter is usually not required.
- TODO: 1G nullable return type
Class Resolvers
With @ClassResolver()
you can specify that a set of fields will be resolved by executing the methods of the decorated class. This allows you to group the fields into a separate class which give you a couple of nice features. Since in Dart all classes specify an "interface" this could be useful if you want to unit test the interface or implement the class resolver's API in other contexts. This also allows you to easily access common dependencies shared between the fields in the resolver class. Instead of using final dependencyName = dependencyRef.get(ctx);
in each field's resolver method body, you could create a field or getter within the class.
In order for Leto to have an instance of the resolver you need to provide a way of creating or getting the class resolver before executing any of the methods. We provide two main tool for that:
Resolver.ref
A static variable that implements BaseRef<FutureOr<Resolver?>>
. For example, a ScopedRef
. This will use the Ctx
of the field's resolver to access an instance of the class resolver with final FutureOr<Resolver> instance = Resolver.ref.get(ctx)!;
and then call the method instance.fieldName(...arguments)
where fieldName
is the name of the method.
instantiateCode
Available in: build.yaml
, ClassResolver
.
If you want to use a custom dependency injection library or method, you can provide an "instantiateCode" String which will be used as a getter to the class resolver instance. You can use the Ctx
of the field in the provided code with the ctx
variable and the resolver Dart class name with "{{name}}" as a template variable within the String. The value should be cast to a FutureOr<Resolver>
type if necessary. A variable of type Resolver
or Future<Resolver>
is fine, no need to explicitly add the as FutureOr<Resolver>
suffix.
final resolversTypeMap = <Type, Object Function(Ctx)>{
Resolver: (Ctx ctx) => Resolver(),
};
const diByMapType = ClassResolver(
instantiateCode: 'resolversTypeMap[{{name}}]!(ctx) as {{name}}',
);
@diByMapType
class Resolver {
@Query()
String getName() => '';
}
In the example above we first instantiate the annotation and then apply it to the class, this would allow you to reuse the diByMapType
annotation in multiple classes if you want to use the same dependency injection method for all of them.
- fieldName
Nested objects in resolvers.
Global Configuration (build.yaml)
You can make global configuration for the code generation with the build.yaml
file at the root of your project (next to your pubspec.yaml
). The following configurations can be specified:
Config | Description | Type | Default |
---|---|---|---|
nullableFields | Whether to make all fields nullable by default | bool | false |
omitFields | Whether to omit all fields from the Schema. You will need to annotate each one with GraphQLField |
bool | false |
omitPrivateFields | Whether to omit private fields from the Schema | bool | true |
omitFieldsNamed | A list of field names to omit by default | List<String> | 'toJson', 'toString', 'compareTo', 'toMap' |
instantiateCode | The dependency injection code used for all Class Resolvers | String? | null |
customTypes | Types for which a custom GraphQLType is provided | List<CustomTypes> | [] |
enumValuesCase | The Enum values case type (CamelCase, snake_case, ...) | EnumNameCase? | null |
Fields
Global configurations for fields
TODO: 1G Name for ID GraphQLType (default: "id")
nullableFields (default: false)
Available in: build.yaml
, GraphQLObject
, GraphQLField
.
When true
, this will make all fields nullable by default. If you want to make a field non-nullable, you will need to configure it in the class' GraphQLObject
annotation, which applies to all the class' fields, or in the field's GraphQLField
annotation.
omitFields (default: false)
Available in: build.yaml
, GraphQLObject
, GraphQLField
.
When true
, this will omit all fields from being generated by default. If you want to generate a specific field, you will need to configure it in the class' GraphQLObject
annotation, which applies to all the class' fields, or in the field's GraphQLField
annotation.
omitPrivateFields (default: true)
Available in: build.yaml
, GraphQLObject
.
When true
, this will omit all fields that start with a underscore "_".
Following the GraphQL spec, fields that start with double underscore "__" are not allowed, they will be always be omitted.
Resolvers
Global configurations for resolvers
instantiateCode (default: null)
Available in: build.yaml
, ClassResolver
.
You can globally configure the instantiateCode
argument for ClassResolvers.
customTypes
This will allow you to specify a custom mapping from given Dart type to a GraphQLType
. The mapping is done with Dart type name
provided as a field in the build.yaml
configuration. This will override all other mapping discussed in coercing types section. It is a list of objects with the following properties:
- String name;
The name of the Dart type, if the name matches a Dart type's name during code generation, the getter
(next configuration) will be used as it's GraphQLType
.
- String getter;
The getter is the name of the Dart getter or property of returns the GraphQLType
. It should be located in the file pointed by the import
property.
- String import;
The file path where the GraphQLType
's getter
can be found.
Example
To support the Decimal
type from https://github.com/a14n/dart-decimal you can use the following code:
import 'package:decimal/decimal.dart';
import 'package:leto_schema/leto_schema.dart';
export 'package:decimal/decimal.dart';
final decimalGraphQLType = GraphQLScalarTypeValue<Decimal, String>(
name: 'Decimal',
deserialize: (_, serialized) => decimalFromJson(serialized)!,
serialize: (value) => decimalToJson(value)!,
validate: (key, input) => (input is num || input is String) &&
Decimal.tryParse(input.toString()) != null
? ValidationResult.ok(input.toString())
: ValidationResult.failure(
['Expected $key to be a number or a numeric String.'],
),
description: 'A number that allows computation without losing precision.',
specifiedByURL: null,
);
Decimal? decimalFromJson(Object? value) =>
value == null ? null : Decimal.parse(value as String);
String? decimalToJson(Decimal? value) => value?.toString();
And specify the following config in the build.yaml file.
target:
default:
builders:
leto_generator:graphql_types:
options:
customTypes:
- name: "Decimal"
import: "package:<your_package_name>/<path_to_implementation>.dart"
getter: "decimalGraphQLType"
leto_generator:graphql_resolvers:
options:
customTypes:
- name: "Decimal"
import: "package:<your_package_name>/<path_to_implementation>.dart"
getter: "decimalGraphQLType"