Skip to content

Commit

Permalink
Build blueprint from validation (#612)
Browse files Browse the repository at this point in the history
* Validation to blueprint

* Fix build
  • Loading branch information
gnawf authored Nov 13, 2024
1 parent eb7b6b2 commit 4c8f4c5
Show file tree
Hide file tree
Showing 55 changed files with 5,796 additions and 6,864 deletions.
2 changes: 1 addition & 1 deletion lib/src/main/java/graphql/nadel/NadelSchemas.kt
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import graphql.schema.idl.WiringFactory
import java.io.Reader
import graphql.schema.idl.ScalarInfo.GRAPHQL_SPECIFICATION_SCALARS as graphQLSpecScalars

data class NadelSchemas constructor(
data class NadelSchemas(
val engineSchema: GraphQLSchema,
val services: List<Service>,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import graphql.nadel.util.getObjectField
/**
* Argument belonging to [NadelHydrationDefinition.arguments]
*/
class NadelHydrationArgumentDefinition(
private val argumentObject: ObjectValue,
) {
sealed class NadelHydrationArgumentDefinition {
companion object {
val inputValueDefinition = parseDefinition<InputObjectTypeDefinition>(
// language=GraphQL
Expand All @@ -24,57 +22,60 @@ class NadelHydrationArgumentDefinition(
}
""".trimIndent(),
)

fun from(argumentObject: ObjectValue): NadelHydrationArgumentDefinition {
val name = (argumentObject.getObjectField(Keyword.name).value as StringValue).value
val astValue = argumentObject.getObjectField(Keyword.value).value

return if (astValue is StringValue && astValue.value.startsWith("$")) {
val command = astValue.value.substringBefore(".")
val values = astValue.value.substringAfter(".").split('.')

when (command) {
"\$source" -> ObjectField(
name = name,
pathToField = values,
)
"\$argument" -> FieldArgument(
name = name,
argumentName = values.single(),
)
else -> StaticArgument(
name = name,
staticValue = astValue,
)
}
} else {
StaticArgument(
name = name,
staticValue = astValue,
)
}
}
}

/**
* Name of the backing field's argument.
*/
val name: String
get() = (argumentObject.getObjectField(Keyword.name).value as StringValue).value
abstract val name: String

/**
* Value to support to the backing field's argument at runtime.
*/
val value: ValueSource
get() = ValueSource.from(argumentObject.getObjectField(Keyword.value).value)
data class ObjectField(
override val name: String,
val pathToField: List<String>,
) : NadelHydrationArgumentDefinition()

data class FieldArgument(
override val name: String,
val argumentName: String,
) : NadelHydrationArgumentDefinition()

data class StaticArgument(
override val name: String,
val staticValue: AnyAstValue,
) : NadelHydrationArgumentDefinition()

internal object Keyword {
const val name = "name"
const val value = "value"
}

sealed class ValueSource {
data class ObjectField(
val pathToField: List<String>,
) : ValueSource()

data class FieldArgument(
val argumentName: String,
) : ValueSource()

data class StaticArgument(
val staticValue: AnyAstValue,
) : ValueSource()

companion object {
fun from(astValue: AnyAstValue): ValueSource {
return if (astValue is StringValue && astValue.value.startsWith("$")) {
val command = astValue.value.substringBefore(".")
val values = astValue.value.substringAfter(".").split('.')

when (command) {
"\$source" -> ObjectField(
pathToField = values,
)
"\$argument" -> FieldArgument(
argumentName = values.single(),
)
else -> StaticArgument(staticValue = astValue)
}
} else {
StaticArgument(staticValue = astValue)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class NadelHydrationDefinition(
val isIndexed: Boolean
get() = appliedDirective.getArgument(Keyword.indexed).getValue()

@Deprecated(message = "Not used and should be deleted")
val isBatched: Boolean
get() = appliedDirective.getArgument(Keyword.batched)?.getValue<Boolean>() == true

Expand All @@ -76,7 +77,7 @@ class NadelHydrationDefinition(
get() = (appliedDirective.getArgument(Keyword.arguments).argumentValue.value as ArrayValue)
.values
.map {
NadelHydrationArgumentDefinition(it as ObjectValue)
NadelHydrationArgumentDefinition.from(it as ObjectValue)
}

val condition: NadelHydrationConditionDefinition?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import graphql.nadel.engine.util.AnyImplementingTypeDefinition
import graphql.nadel.engine.util.AnyNamedNode
import graphql.nadel.engine.util.emptyOrSingle
import graphql.nadel.engine.util.getFieldAt
import graphql.nadel.engine.util.getFieldContainerAt
import graphql.nadel.engine.util.getFieldContainerFor
import graphql.nadel.engine.util.getFieldsAlong
import graphql.nadel.engine.util.getOperationType
import graphql.nadel.engine.util.isExtensionDef
Expand Down Expand Up @@ -192,7 +192,9 @@ private class Factory(
when (val renamedDefinition = field.getRenamedOrNull()) {
null -> {
field.getHydrationDefinitions()
.map { makeHydrationFieldInstruction(type, field, it) } + makePartitionInstruction(type, field)
.map {
makeHydrationFieldInstruction(type, field, it)
} + makePartitionInstruction(type, field)
}
else -> when (renamedDefinition.from.size) {
1 -> listOf(makeRenameInstruction(type, field, renamedDefinition))
Expand Down Expand Up @@ -223,7 +225,7 @@ private class Factory(
hydration: NadelHydrationDefinition,
): NadelFieldInstruction {
val pathToBackingField = hydration.backingField
val backingFieldContainer = engineSchema.queryType.getFieldContainerAt(pathToBackingField)!!
val backingFieldContainer = engineSchema.queryType.getFieldContainerFor(pathToBackingField)!!
val backingFieldDef = engineSchema.queryType.getFieldAt(pathToBackingField)!!
val hydrationBackingService = coordinatesToService[makeFieldCoordinates(backingFieldContainer, backingFieldDef)]!!

Expand Down Expand Up @@ -536,10 +538,10 @@ private class Factory(
virtualFieldDef: GraphQLFieldDefinition,
backingFieldDef: GraphQLFieldDefinition,
): List<NadelHydrationArgument> {
return hydration.arguments.map { remoteArgDef ->
val valueSource = when (val argSourceType = remoteArgDef.value) {
is NadelHydrationArgumentDefinition.ValueSource.FieldArgument -> {
val argumentName = argSourceType.argumentName
return hydration.arguments.map { hydrationArgument ->
val valueSource = when (hydrationArgument) {
is NadelHydrationArgumentDefinition.FieldArgument -> {
val argumentName = hydrationArgument.argumentName
val argumentDef = virtualFieldDef.getArgument(argumentName)
?: error("No argument '$argumentName' on field ${virtualFieldParentType.name}.${virtualFieldDef.name}")
val defaultValue = if (argumentDef.argumentDefaultValue.isLiteral) {
Expand All @@ -552,37 +554,37 @@ private class Factory(
}

NadelHydrationArgument.ValueSource.ArgumentValue(
argumentName = argSourceType.argumentName,
argumentName = hydrationArgument.argumentName,
argumentDefinition = argumentDef,
defaultValue = defaultValue,
)
}
is NadelHydrationArgumentDefinition.ValueSource.ObjectField -> {
is NadelHydrationArgumentDefinition.ObjectField -> {
// Ugh code still uses underlying schema, we need to pull these up to the overall schema
val typeToLookAt = if (virtualFieldParentType.isVirtualType()) {
virtualFieldParentType
} else {
getUnderlyingType(virtualFieldParentType, virtualFieldDef)
}

val pathToField = argSourceType.pathToField
val pathToField = hydrationArgument.pathToField
NadelHydrationArgument.ValueSource.FieldResultValue(
queryPathToField = NadelQueryPath(pathToField),
fieldDefinition = typeToLookAt
?.getFieldAt(pathToField)
?: error("No field defined at: ${virtualFieldParentType.name}.${pathToField.joinToString(".")}"),
)
}
is NadelHydrationArgumentDefinition.ValueSource.StaticArgument -> {
is NadelHydrationArgumentDefinition.StaticArgument -> {
NadelHydrationArgument.ValueSource.StaticValue(
value = argSourceType.staticValue,
value = hydrationArgument.staticValue,
)
}
}

NadelHydrationArgument(
name = remoteArgDef.name,
backingArgumentDef = backingFieldDef.getArgument(remoteArgDef.name),
name = hydrationArgument.name,
backingArgumentDef = backingFieldDef.getArgument(hydrationArgument.name),
valueSource = valueSource,
)
} + listOfNotNull(getRemainingHydrationArgumentsOrNull(virtualFieldDef, backingFieldDef))
Expand Down
9 changes: 5 additions & 4 deletions lib/src/main/java/graphql/nadel/engine/util/GraphQLUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,13 @@ private fun GraphQLFieldsContainer.getFieldAt(
/**
* Can find field containers via a field path syntax
*/
fun GraphQLFieldsContainer.getFieldContainerAt(
fun GraphQLFieldsContainer.getFieldContainerFor(
pathToField: List<String>,
): GraphQLFieldsContainer? {
return getFieldContainerAt(pathToField, pathIndex = 0)
return getFieldContainerFor(pathToField, pathIndex = 0)
}

private fun GraphQLFieldsContainer.getFieldContainerAt(
private fun GraphQLFieldsContainer.getFieldContainerFor(
pathToField: List<String>,
pathIndex: Int,
): GraphQLFieldsContainer? {
Expand All @@ -183,7 +183,7 @@ private fun GraphQLFieldsContainer.getFieldContainerAt(
} else {
val possibleFieldContainer = field.type.unwrapAll()
if (possibleFieldContainer is GraphQLFieldsContainer) {
possibleFieldContainer.getFieldContainerAt(pathToField, pathIndex + 1)
possibleFieldContainer.getFieldContainerFor(pathToField, pathIndex + 1)
} else {
null
}
Expand Down Expand Up @@ -309,6 +309,7 @@ fun GraphQLType.unwrapNonNull(): GraphQLType {

val GraphQLType.isList: Boolean get() = GraphQLTypeUtil.isList(this)
val GraphQLType.isNonNull: Boolean get() = GraphQLTypeUtil.isNonNull(this)
val GraphQLType.isNullable: Boolean get() = !isNonNull
val GraphQLType.isWrapped: Boolean get() = GraphQLTypeUtil.isWrapped(this)
val GraphQLType.isNotWrapped: Boolean get() = GraphQLTypeUtil.isNotWrapped(this)

Expand Down
46 changes: 46 additions & 0 deletions lib/src/main/java/graphql/nadel/util/IteratorUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package graphql.nadel.util

/**
* Very similar to [String.split] but the [splitFunction] determines when to split the input.
*/
internal fun <E> Sequence<E>.splitBy(
splitFunction: (E) -> Boolean,
): Sequence<List<E>> {
return sequence {
var currentSplit = mutableListOf<E>()

for (e in this@splitBy) {
if (splitFunction(e)) {
yield(currentSplit)
currentSplit = mutableListOf()
} else {
currentSplit.add(e)
}
}

yield(currentSplit)
}
}

/**
* Very similar to [String.split] but the [splitFunction] determines when to split the input.
*/
internal fun <E> Iterable<E>.splitBy(
splitFunction: (E) -> Boolean,
): List<List<E>> {
val splits = mutableListOf<List<E>>()
var currentSplit = mutableListOf<E>()

for (e in this) {
if (splitFunction(e)) {
splits.add(currentSplit)
currentSplit = mutableListOf()
} else {
currentSplit.add(e)
}
}

splits.add(currentSplit)

return splits
}
21 changes: 10 additions & 11 deletions lib/src/main/java/graphql/nadel/validation/NadelEnumValidation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,34 @@ import graphql.schema.GraphQLEnumType
import graphql.schema.GraphQLEnumValueDefinition

internal class NadelEnumValidation {
context(NadelValidationContext)
fun validate(
schemaElement: NadelServiceSchemaElement,
): List<NadelSchemaValidationError> {
): NadelSchemaValidationResult {
return if (schemaElement.overall is GraphQLEnumType && schemaElement.underlying is GraphQLEnumType) {
validate(
parent = schemaElement,
overallValues = schemaElement.overall.values,
underlyingValues = schemaElement.underlying.values,
)
} else {
emptyList()
ok()
}
}

context(NadelValidationContext)
private fun validate(
parent: NadelServiceSchemaElement,
overallValues: List<GraphQLEnumValueDefinition>,
underlyingValues: List<GraphQLEnumValueDefinition>,
): List<NadelSchemaValidationError> {
): NadelSchemaValidationResult {
val underlyingValuesByName = underlyingValues.strictAssociateBy { it.name }

return overallValues.mapNotNull { overallValue ->
val underlyingValue = underlyingValuesByName[overallValue.name]

if (underlyingValue == null) {
MissingUnderlyingEnumValue(parent, overallValue)
} else {
null
}
overallValues.forEach { overallValue ->
underlyingValuesByName[overallValue.name]
?: return MissingUnderlyingEnumValue(parent, overallValue)
}

return ok()
}
}
Loading

0 comments on commit 4c8f4c5

Please sign in to comment.