diff --git a/content/tools/_index.md b/content/tools/_index.md index 6b1130a..47a7020 100644 --- a/content/tools/_index.md +++ b/content/tools/_index.md @@ -8,8 +8,8 @@ menu: parent: Tools --- -[![Maven Central](https://img.shields.io/maven-central/v/com.graphql-java-kickstart/graphql-java-tools.svg)](https://maven-badges.herokuapp.com/maven-central/com.graphql-java-kickstart/graphql-java-tools) -[![TravisCI Build](https://travis-ci.org/graphql-java-kickstart/graphql-java-tools.svg?branch=master)](https://travis-ci.org/graphql-java-kickstart/graphql-java-tools) +[![Maven Central](https://maven-badges.sml.io/maven-central/io.github.graphql-java-kickstart/graphql-java-tools/badge.svg)](https://central.sonatype.com/artifact/io.github.graphql-java-kickstart/graphql-java-tools) +[![Github Build](https://github.com/graphql-java-kickstart/graphql-java-tools/actions/workflows/snapshot.yml/badge.svg)](https://github.com/graphql-java-kickstart/graphql-java-tools/actions/workflows/snapshot.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=graphql-java-kickstart_graphql-java-tools&metric=alert_status)](https://sonarcloud.io/dashboard?id=graphql-java-kickstart_graphql-java-tools) [![GitHub contributors](https://img.shields.io/github/contributors/graphql-java-kickstart/graphql-java-tools)](https://github.com/graphql-java-kickstart/graphql-java-tools/graphs/contributors) [![Discuss on GitHub](https://img.shields.io/badge/GitHub-discuss-orange)](https://github.com/graphql-java-kickstart/graphql-java-tools/discussions) diff --git a/content/tools/directives/index.md b/content/tools/directives/index.md index 22427a1..c4cd3dc 100644 --- a/content/tools/directives/index.md +++ b/content/tools/directives/index.md @@ -9,7 +9,7 @@ menu: url: /tools/directives/ --- -See [Schema Directives](https://www.graphql-java.com/documentation/v16/sdl-directives/) +See [Schema Directives](https://www.graphql-java.com/documentation/directives) for a detailed explanation about directives including examples on how to define them in the SDL and to create the required classes. @@ -67,6 +67,12 @@ SchemaParser.newParser() .makeExecutableSchema(); ``` +If several wirings apply to the same element, each one gets the element returned by the previous one. + +Arguments the SDL leaves out are filled in with their default from the directive definition, so the wiring sees the default instead of `null`. + +An enum that's only used by directive definitions doesn't need a Java class. Its values are then passed to the wiring as `String`s. Add the enum to the [type dictionary]({{< relref "/tools/schema-definition/index.md#type-dictionary" >}}) to get the enum constants instead. + ## Supported locations Support for directives is currently limited to the following locations: @@ -83,3 +89,5 @@ Support for directives is currently limited to the following locations: Meaning directives for the following locations are currently not yet supported: * `SCALAR` + +Directives on `schema` and `extend schema` don't run a wiring, but they're added to the schema's applied directives. Like everywhere else, they have to be declared, unless `allowUndeclaredDirectives` is set in the [schema parser options]({{< relref "/tools/schema-parser-options/index.md" >}}). diff --git a/content/tools/getting-started/index.md b/content/tools/getting-started/index.md index 9b9b832..1962b3f 100644 --- a/content/tools/getting-started/index.md +++ b/content/tools/getting-started/index.md @@ -34,7 +34,7 @@ Add the `graphql-java-tools` dependency: ```gradle dependencies { - compile 'com.graphql-java-kickstart:graphql-java-tools:13.0.3' + implementation 'io.github.graphql-java-kickstart:graphql-java-tools:14.3.0' } ``` @@ -44,15 +44,15 @@ Add the `graphql-java-tools` dependency: ```xml - com.graphql-java-kickstart + io.github.graphql-java-kickstart graphql-java-tools - 13.0.3 + 14.3.0 ``` ## Using the latest development build -Snapshot versions of the current `master` branch are available on JFrog. Check the next snapshot version on +Snapshot versions of the current `master` branch are available on the Maven Central snapshot repository. Check the next snapshot version on [Github](https://github.com/graphql-java-kickstart/graphql-java-tools/blob/master/pom.xml#L7) ### Build with Gradle @@ -62,7 +62,7 @@ Add the Snapshot repository: ```gradle repositories { mavenCentral() - maven { url "https://oss.jfrog.org/artifactory/oss-snapshot-local" } + maven { url = 'https://central.sonatype.com/repository/maven-snapshots/' } } ``` @@ -73,9 +73,12 @@ Add the Snapshot repository: ```xml - oss-snapshot-local - jfrog - https://oss.jfrog.org/artifactory/oss-snapshot-local + central-portal-snapshots + Central Portal Snapshots + https://central.sonatype.com/repository/maven-snapshots/ + + false + true always diff --git a/content/tools/schema-definition/index.md b/content/tools/schema-definition/index.md index dcc4857..fd99a46 100644 --- a/content/tools/schema-definition/index.md +++ b/content/tools/schema-definition/index.md @@ -28,6 +28,12 @@ SchemaParser.newParser() Query { } ``` Multiple sources will be concatenated together in the order given, allowing you to modularize your schema if desired. +Line numbers in syntax errors count from the start of each file or string, and errors in a file end with its name. To name a schema string too, pass the name as well: + +```java +SchemaParser.newParser() + .schemaString(usersSchema, "users.graphqls") +``` ## Resolvers and Data Classes @@ -104,6 +110,17 @@ When given a `BookResolver` instance, GraphQL Java Tools first attempts to map f If there is a matching method on the resolver, the data class instance is passed as the first argument to the resolver function. This does not apply to root resolvers, since those don't have a data class to resolve for. An optional parameter can be defined to inject the `DataFetchingEnvironment`, and must be the last argument. +A resolver also applies to subclasses and implementations of its data class, so a `GraphQLResolver` resolves fields for a data class `Thing extends Base`. +A resolver for a generic class, like `GraphQLResolver>`, applies to every `Page`. +When several resolvers apply, the one for the most specific type is tried first. + +In Kotlin, a resolver method can also be an extension function on the data class: +```kotlin +class BookResolver(private val authorRepository: AuthorRepository) : GraphQLResolver { + fun Book.author(): Author = authorRepository.findById(authorId) +} +``` + ### Root Resolvers Since the Query/Mutation/Subscription objects are root GraphQL objects, they don't have an associated data class. In those cases, any resolvers implementing `GraphQLQueryResolver`, `GraphQLMutationResolver`, or `GraphQLSubscriptionResolver` will be searched for methods that map to fields in their respective root types. Root resolver methods can be spread between multiple resolvers, but a simple example is below: @@ -156,7 +173,7 @@ Last of all, if the data class implements`java.util.Map` then: *Note:* Methods on `java.lang.Object` are excluded from method matching, for example a field named `class` will require a method named `getFieldClass` defined. -*Note:* If one of the values of a type backed by a `java.util.Map` is non-scalar then this type will need to be added to the `type dictionary` (see below). After adding this type to the dictionary, GraphQL Java Tools will however still be able to find the types used in the fields of this added type. +*Note:* If one of the values of a type backed by a `java.util.Map` is non-scalar, on its own or in a list, then this type will need to be added to the `type dictionary` (see below). After adding this type to the dictionary, GraphQL Java Tools will however still be able to find the types used in the fields of this added type. ## Enum Types @@ -187,6 +204,7 @@ class AdditionInput { ## Interfaces and Union Types GraphQL interface/union types are automatically resolved from the schema and the list of provided classes, and require no extra work outside of the schema. +Members and implementors that no resolver returns need to be in the [type dictionary](#type-dictionary), unless the class of their union or interface is sealed. Although not necessary, it's generally a good idea to have java interfaces that correspond to your GraphQL interfaces to keep your code understandable. ## Scalar Types @@ -213,6 +231,17 @@ SchemaParser.newParser() .dictionary("Book", BookClassWithIncorrectName.class) ``` +Several input types can share a class, e.g. Jackson's `ObjectNode`. Other types need a class each. + +If the class of a union or interface is a Kotlin sealed class or interface, or a Java sealed type, its members and implementors don't need to be added. GraphQL Java Tools uses the subclass with the same simple name as the GraphQL type: +```kotlin +// union Pet = NormalPet | WeirdPet +sealed class Pet { + data class NormalPet(val name: String) : Pet() + data class WeirdPet(val classification: String) : Pet() +} +``` + ## Making the graphql-java Schema Instance After you've passed all relevant schema files/class to the parser, call `.build()` and `.makeExecutableSchema()` to get a graphql-java `GraphQLSchema`: diff --git a/content/tools/schema-parser-options/index.md b/content/tools/schema-parser-options/index.md index 02b59e8..d381efe 100644 --- a/content/tools/schema-parser-options/index.md +++ b/content/tools/schema-parser-options/index.md @@ -23,8 +23,10 @@ Options: .graphQLContext(Map.of(CustomContext.class, context)); ``` * `genericWrappers`: Allows defining your own generic classes that should be unwrapped when matching Java types to GraphQL types. You must supply the class and the index (zero-indexed) of the wrapped generic type. For example: If you want to unwrap type argument `T` of `Future`, you must pass `Future.class` and `0`. + Use `GenericWrapper.withTransformer` to also convert the value at runtime. If the transformer returns `null`, the field is `null`. * `useDefaultGenericWrappers`: Defaults to `true`. Tells the parser whether or not to add it's own list of well-known generic wrappers, such as `Future` and `CompletableFuture`. + graphql-java only waits on a `CompletionStage`, so a method declared to return a `Future` has to return one, such as a `CompletableFuture`. Any other future fails the field with an error, unless a generic wrapper transformer converts it. * `allowUnimplementedResolvers`: Defaults to `false`. Allows a schema to be created even if not all GraphQL fields have resolvers. Intended only for development, it will log a warning to remind you to turn it off for production. Any unimplemented resolvers will throw errors when queried. * `missingResolverDataFetcher`: Allows you to provide custom behavior for missing GraphQL fields. * `inputArgumentOptionalDetectOmission`: Defaults to `false`. By default, the parser will treat omitted or null method input arguments as `Optional.empty` in resolvers. If you prefer, you can disable this behavior. @@ -53,8 +55,19 @@ Options: * Guice * Spring AOP * Weld -* `introspectionEnabled`: Defaults to `true`. When set to `false` this option will disable schema introspection via `NO_INTROSPECTION_FIELD_VISIBILITY`. See [Field Visibility](https://www.graphql-java.com/documentation/master/fieldvisibility/). -* `fieldVisibility`: Provide a graphql field visibility implementation. This option overrides `introspectionEnabled` when used. See [Field Visibility](https://www.graphql-java.com/documentation/master/fieldvisibility/). +* `introspectionEnabled`: Defaults to `true`. When set to `false` this option will disable schema introspection via `NO_INTROSPECTION_FIELD_VISIBILITY`. See [Field Visibility](https://www.graphql-java.com/documentation/field-visibility). +* `fieldVisibility`: Provide a graphql field visibility implementation. This option overrides `introspectionEnabled` when used. See [Field Visibility](https://www.graphql-java.com/documentation/field-visibility). * `coroutineContext` / `coroutineContextProvider`: Provide a kotlin coroutine context to be used with suspend functions of resolvers. + A suspend function runs on the calling thread until it first suspends, and continues on this context after that, so wrap blocking calls in `withContext(Dispatchers.IO)`. + To run them in your own `CoroutineScope`, e.g. to cancel them with the request, put it in the execution input's context: + ```java + var executionInput = ExecutionInput.newExecutionInput() + .query(query) + .graphQLContext(Map.of(CoroutineScope.class, scope)); + ``` * `typeDefinitionFactory`: See [Type Definition Factory]({{< relref "/tools/type-definition-factory/index.md" >}}). * `includeUnusedTypes`: Defaults to `false`. By default, the parser will ignore unused type definitions in the schema. Enable this option to include them regardless. +* `allowUndeclaredDirectives`: Defaults to `false`. Allows applying directives that aren't declared in the schema, e.g. the Apollo Federation directives imported through `@link`. They're only added as applied directives, with argument types guessed from their values. Directives with enum or input object arguments still have to be declared. +* `unmappedInputFields`: Defaults to `WARN`. What to do about input type fields that the class of a resolver argument has no property for. When a client sends such a field, Jackson fails to convert the argument, or drops the value if the `ObjectMapper` ignores unknown properties. + The classes are checked when the schema is built, with each field's own `ObjectMapper`, and `WARN` logs a warning for each of these fields. `FAIL` fails the build instead, and `IGNORE` skips the check. To leave a field unmapped on purpose, add it to `@JsonIgnoreProperties` on the class. + Classes with a custom deserializer, a builder or a delegating creator aren't checked, and neither are arguments bound to a `Map` or a `JsonNode`.