Skip to content

Commit

Permalink
Implement default hydration
Browse files Browse the repository at this point in the history
  • Loading branch information
gnawf committed Dec 2, 2024
1 parent a90fbb1 commit d211e72
Show file tree
Hide file tree
Showing 50 changed files with 2,089 additions and 279 deletions.
10 changes: 10 additions & 0 deletions lib/src/main/java/graphql/nadel/Nadel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import graphql.nadel.schema.QuerySchemaGenerator
import graphql.nadel.schema.SchemaTransformationHook
import graphql.nadel.util.getLogger
import graphql.nadel.util.getNotPrivacySafeLogger
import graphql.nadel.validation.NadelSchemaValidationFactory
import graphql.nadel.validation.NadelSchemaValidation
import graphql.parser.InvalidSyntaxException
import graphql.parser.Parser
import graphql.schema.GraphQLSchema
Expand Down Expand Up @@ -243,6 +245,8 @@ class Nadel private constructor(

private var blueprintHint = NadelValidationBlueprintHint { false }

private var nadelValidation: NadelSchemaValidation? = null

fun schemas(schemas: NadelSchemas): Builder {
this.schemas = schemas
return this
Expand Down Expand Up @@ -368,6 +372,11 @@ class Nadel private constructor(
return this
}

fun schemaValidation(nadelValidation: NadelSchemaValidation): Builder {
this.nadelValidation = nadelValidation
return this
}

fun build(): Nadel {
val (engineSchema, services) = schemas ?: schemaBuilder.build()

Expand All @@ -386,6 +395,7 @@ class Nadel private constructor(
transforms = transforms,
introspectionRunnerFactory = introspectionRunnerFactory,
blueprintHint = blueprintHint,
nadelValidation = nadelValidation ?: NadelSchemaValidationFactory.create(),
),
services = services,
engineSchema = engineSchema,
Expand Down
5 changes: 2 additions & 3 deletions lib/src/main/java/graphql/nadel/NextgenEngine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ internal class NextgenEngine(
transforms: List<NadelTransform<out Any>>,
introspectionRunnerFactory: NadelIntrospectionRunnerFactory,
blueprintHint: NadelValidationBlueprintHint,
nadelValidation: NadelSchemaValidation,
) {
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private val services: Map<String, Service> = services.strictAssociateBy { it.name }
Expand All @@ -100,9 +101,7 @@ internal class NextgenEngine(
),
new = lazy {
try {
NadelSchemaValidation(
NadelSchemas(engineSchema, services)
).validateAndGenerateBlueprint()
nadelValidation.validateAndGenerateBlueprint(NadelSchemas(engineSchema, services))
} catch (e: Exception) {
getLogger<NextgenEngine>().error("Unable to create validated blueprint", e)
null
Expand Down
3 changes: 3 additions & 0 deletions lib/src/main/java/graphql/nadel/definition/NadelDefinition.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package graphql.nadel.definition

interface NadelDefinition
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package graphql.nadel.definition

import graphql.schema.GraphQLDirective
import graphql.schema.GraphQLEnumType
import graphql.schema.GraphQLFieldsContainer
import graphql.schema.GraphQLInputObjectType
import graphql.schema.GraphQLInterfaceType
import graphql.schema.GraphQLNamedType
import graphql.schema.GraphQLObjectType
import graphql.schema.GraphQLScalarType
import graphql.schema.GraphQLUnionType

sealed interface NadelSchemaMemberCoordinates {
val name: String

sealed interface ArgumentParent : NadelSchemaMemberCoordinates {
fun argument(name: String): Argument {
return Argument(parent = this, name = name)
}
}

sealed interface AppliedDirectiveParent : NadelSchemaMemberCoordinates {
fun appliedDirective(name: String): AppliedDirective {
return AppliedDirective(parent = this, name = name)
}
}

sealed interface FieldContainer : NadelSchemaMemberCoordinates {
fun field(name: String): Field {
return Field(parent = this, name = name)
}
}

sealed interface DefaultValueHolder : NadelSchemaMemberCoordinates
sealed interface Type : NadelSchemaMemberCoordinates
sealed interface ImplementingType : NadelSchemaMemberCoordinates

sealed interface ChildCoordinates {
val parent: NadelSchemaMemberCoordinates
}

data class Object(
override val name: String,
) : NadelSchemaMemberCoordinates,
Type,
ImplementingType,
AppliedDirectiveParent,
FieldContainer

data class Interface(
override val name: String,
) : NadelSchemaMemberCoordinates,
Type,
ImplementingType,
AppliedDirectiveParent,
FieldContainer

data class Scalar(
override val name: String,
) : NadelSchemaMemberCoordinates,
Type,
AppliedDirectiveParent

data class Enum(
override val name: String,
) : NadelSchemaMemberCoordinates,
Type,
AppliedDirectiveParent

data class Union(
override val name: String,
) : NadelSchemaMemberCoordinates,
Type,
AppliedDirectiveParent

data class InputObject(
override val name: String,
) : NadelSchemaMemberCoordinates,
Type,
AppliedDirectiveParent {
fun field(name: String): InputObjectField {
return InputObjectField(parent = this, name = name)
}
}

data class Directive(
override val name: String,
) : NadelSchemaMemberCoordinates,
Type,
ArgumentParent

data class Field(
override val parent: FieldContainer,
override val name: String,
) : NadelSchemaMemberCoordinates,
ChildCoordinates,
ArgumentParent,
AppliedDirectiveParent

data class Argument(
override val parent: ArgumentParent,
override val name: String,
) : NadelSchemaMemberCoordinates,
ChildCoordinates,
AppliedDirectiveParent,
DefaultValueHolder

data class AppliedDirective(
override val parent: AppliedDirectiveParent,
override val name: String,
) : NadelSchemaMemberCoordinates,
ChildCoordinates,
ArgumentParent

data class InputObjectField(
override val parent: InputObject,
override val name: String,
) : NadelSchemaMemberCoordinates,
ChildCoordinates,
AppliedDirectiveParent,
DefaultValueHolder
}

fun GraphQLUnionType.coordinates(): NadelSchemaMemberCoordinates.Union {
return NadelSchemaMemberCoordinates.Union(name)
}

fun GraphQLInterfaceType.coordinates(): NadelSchemaMemberCoordinates.Interface {
return NadelSchemaMemberCoordinates.Interface(name)
}

fun GraphQLEnumType.coordinates(): NadelSchemaMemberCoordinates.Enum {
return NadelSchemaMemberCoordinates.Enum(name)
}

fun GraphQLInputObjectType.coordinates(): NadelSchemaMemberCoordinates.InputObject {
return NadelSchemaMemberCoordinates.InputObject(name)
}

fun GraphQLObjectType.coordinates(): NadelSchemaMemberCoordinates.Object {
return NadelSchemaMemberCoordinates.Object(name)
}

fun GraphQLScalarType.coordinates(): NadelSchemaMemberCoordinates.Scalar {
return NadelSchemaMemberCoordinates.Scalar(name)
}

fun GraphQLDirective.coordinates(): NadelSchemaMemberCoordinates.Directive {
return NadelSchemaMemberCoordinates.Directive(name)
}

fun GraphQLFieldsContainer.coordinates(): NadelSchemaMemberCoordinates.FieldContainer {
return when (this) {
is GraphQLObjectType -> coordinates()
is GraphQLInterfaceType -> coordinates()
else -> throw IllegalArgumentException(javaClass.name)
}
}

fun GraphQLNamedType.coordinates(): NadelSchemaMemberCoordinates.Type {
return when (this) {
is GraphQLUnionType -> coordinates()
is GraphQLInterfaceType -> coordinates()
is GraphQLEnumType -> coordinates()
is GraphQLInputObjectType -> coordinates()
is GraphQLObjectType -> coordinates()
is GraphQLScalarType -> coordinates()
else -> throw IllegalArgumentException(javaClass.name)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package graphql.nadel.definition.hydration

import graphql.language.ArrayValue
import graphql.language.DirectiveDefinition
import graphql.language.ObjectValue
import graphql.nadel.definition.hydration.NadelDefaultHydrationDefinition.Keyword
import graphql.nadel.engine.util.parseDefinition
import graphql.schema.GraphQLAppliedDirective
import graphql.schema.GraphQLDirectiveContainer
import graphql.schema.GraphQLNamedType

fun GraphQLNamedType.hasDefaultHydration(): Boolean {
return (this as? GraphQLDirectiveContainer)?.hasAppliedDirective(Keyword.defaultHydration) == true
}

fun GraphQLNamedType.getDefaultHydrationOrNull(): NadelDefaultHydrationDefinition? {
return (this as? GraphQLDirectiveContainer)?.getAppliedDirective(Keyword.defaultHydration)
?.let(::NadelDefaultHydrationDefinition)
}

class NadelDefaultHydrationDefinition(
private val appliedDirective: GraphQLAppliedDirective,
) {
companion object {
val directiveDefinition = parseDefinition<DirectiveDefinition>(
// language=GraphQL
"""
"This allows you to hydrate new values into fields"
directive @defaultHydration(
"The backing level field for the data"
field: String!
"Name of the ID argument on the backing field"
idArgument: String!
"How to identify matching results"
identifiedBy: String! = "id"
"The batch size"
batchSize: Int
"The timeout to use when completing hydration"
timeout: Int! = -1
"The arguments to the hydrated field"
arguments: [NadelHydrationArgument!]! = []
) on OBJECT | INTERFACE
""".trimIndent(),
)
}

val backingField: List<String>
get() = appliedDirective.getArgument(Keyword.field).getValue<String>().split(".")

val identifiedBy: String?
get() = appliedDirective.getArgument(Keyword.identifiedBy).getValue()

val idArgument: String
get() = appliedDirective.getArgument(Keyword.idArgument).getValue()

val batchSize: Int
get() = appliedDirective.getArgument(Keyword.batchSize).getValue()

val arguments: List<NadelHydrationArgumentDefinition>
get() = (appliedDirective.getArgument(Keyword.arguments).argumentValue.value as ArrayValue)
.values
.map {
NadelHydrationArgumentDefinition.from(it as ObjectValue)
}

val timeout: Int
get() = appliedDirective.getArgument(Keyword.timeout).getValue()

internal object Keyword {
const val defaultHydration = "defaultHydration"
const val field = "field"
const val identifiedBy = "identifiedBy"
const val batchSize = "batchSize"
const val timeout = "timeout"
const val arguments = "arguments"
const val idArgument = "idArgument"
}
}
Loading

0 comments on commit d211e72

Please sign in to comment.