Skip to content

Commit

Permalink
Refactor RemoteArgumentSource to be a sealed class
Browse files Browse the repository at this point in the history
  • Loading branch information
gnawf committed Dec 19, 2023
1 parent a1bb9df commit b540b12
Show file tree
Hide file tree
Showing 9 changed files with 170 additions and 194 deletions.
26 changes: 13 additions & 13 deletions lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
package graphql.nadel.dsl

import graphql.language.Value
import graphql.nadel.util.AnyAstValue

// todo this should be a union or sealed class thing
data class RemoteArgumentSource(
val argumentName: String?, // for OBJECT_FIELD
val pathToField: List<String>?,
val staticValue: Value<*>?,
val sourceType: SourceType,
) {
enum class SourceType {
ObjectField,
FieldArgument,
StaticArgument
}
sealed class RemoteArgumentSource {
data class ObjectField(
val pathToField: List<String>,
) : RemoteArgumentSource()

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

data class StaticArgument(
val staticValue: AnyAstValue,
) : RemoteArgumentSource()
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ import graphql.language.FieldDefinition
import graphql.language.ImplementingTypeDefinition
import graphql.nadel.Service
import graphql.nadel.dsl.FieldMappingDefinition
import graphql.nadel.dsl.RemoteArgumentSource.SourceType.FieldArgument
import graphql.nadel.dsl.RemoteArgumentSource.SourceType.ObjectField
import graphql.nadel.dsl.RemoteArgumentSource.SourceType.StaticArgument
import graphql.nadel.dsl.RemoteArgumentSource
import graphql.nadel.dsl.TypeMappingDefinition
import graphql.nadel.dsl.UnderlyingServiceHydration
import graphql.nadel.engine.blueprint.hydration.NadelBatchHydrationMatchStrategy
Expand Down Expand Up @@ -485,9 +483,9 @@ private class Factory(
actorFieldDef: GraphQLFieldDefinition,
): List<NadelHydrationActorInputDef> {
return hydration.arguments.map { remoteArgDef ->
val valueSource = when (val argSourceType = remoteArgDef.remoteArgumentSource.sourceType) {
FieldArgument -> {
val argumentName = remoteArgDef.remoteArgumentSource.argumentName!!
val valueSource = when (val argSourceType = remoteArgDef.remoteArgumentSource) {
is RemoteArgumentSource.FieldArgument -> {
val argumentName = argSourceType.argumentName
val argumentDef = hydratedFieldDef.getArgument(argumentName)
?: error("No argument '$argumentName' on field ${hydratedFieldParentType.name}.${hydratedFieldDef.name}")
val defaultValue = if (argumentDef.argumentDefaultValue.isLiteral) {
Expand All @@ -500,23 +498,23 @@ private class Factory(
}

NadelHydrationActorInputDef.ValueSource.ArgumentValue(
argumentName = argumentName,
argumentName = argSourceType.argumentName,
argumentDefinition = argumentDef,
defaultValue = defaultValue,
)
}
ObjectField -> {
val pathToField = remoteArgDef.remoteArgumentSource.pathToField!!
is RemoteArgumentSource.ObjectField -> {
val pathToField = argSourceType.pathToField
FieldResultValue(
queryPathToField = NadelQueryPath(pathToField),
fieldDefinition = getUnderlyingType(hydratedFieldParentType)
?.getFieldAt(pathToField)
?: error("No field defined at: ${hydratedFieldParentType.name}.${pathToField.joinToString(".")}"),
)
}
StaticArgument -> {
is RemoteArgumentSource.StaticArgument -> {
NadelHydrationActorInputDef.ValueSource.StaticValue(
value = remoteArgDef.remoteArgumentSource.staticValue!!
value = argSourceType.staticValue,
)
}
}
Expand Down
70 changes: 16 additions & 54 deletions lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import graphql.language.InputObjectTypeDefinition
import graphql.language.ObjectValue
import graphql.language.SDLDefinition
import graphql.language.StringValue
import graphql.language.TypeDefinition
import graphql.language.Value
import graphql.nadel.dsl.FieldMappingDefinition
import graphql.nadel.dsl.RemoteArgumentDefinition
import graphql.nadel.dsl.RemoteArgumentSource
import graphql.nadel.dsl.RemoteArgumentSource.SourceType
import graphql.nadel.dsl.TypeMappingDefinition
import graphql.nadel.dsl.UnderlyingServiceHydration
import graphql.nadel.dsl.WhenConditionDefinition
Expand Down Expand Up @@ -328,37 +328,20 @@ object NadelDirectives {
}

private fun createRemoteArgumentSource(value: Value<*>): RemoteArgumentSource {
if (value is StringValue) {
val values = listFromDottedString(value.value)
return when (values.first()) {
"\$source" -> RemoteArgumentSource(
argumentName = null,
return if (value is StringValue) {
val values = value.value.split('.')

when (values.first()) {
"\$source" -> RemoteArgumentSource.ObjectField(
pathToField = values.subList(1, values.size),
staticValue = null,
sourceType = SourceType.ObjectField,
)

"\$argument" -> RemoteArgumentSource(
"\$argument" -> RemoteArgumentSource.FieldArgument(
argumentName = values.subList(1, values.size).single(),
pathToField = null,
staticValue = null,
sourceType = SourceType.FieldArgument,
)

else -> RemoteArgumentSource(
argumentName = null,
pathToField = null,
staticValue = value,
sourceType = SourceType.StaticArgument,
)
else -> RemoteArgumentSource.StaticArgument(staticValue = value)
}
} else {
return RemoteArgumentSource(
argumentName = null,
pathToField = null,
staticValue = value,
sourceType = SourceType.StaticArgument,
)
RemoteArgumentSource.StaticArgument(staticValue = value)
}
}

Expand All @@ -380,37 +363,20 @@ object NadelDirectives {

val remoteArgumentSource = if (remoteArgFieldValue != null && remoteArgArgValue != null) {
throw IllegalArgumentException("$inputObjectTypeName can not have both $valueFromFieldKey and $valueFromArgKey set")
} else if (remoteArgFieldValue != null) {
createTemplatedRemoteArgumentSource(remoteArgFieldValue, SourceType.ObjectField)
} else if (remoteArgArgValue != null) {
createTemplatedRemoteArgumentSource(remoteArgArgValue, SourceType.FieldArgument)
} else {
throw IllegalArgumentException("$inputObjectTypeName requires one of $valueFromFieldKey or $valueFromArgKey to be set")
if (remoteArgFieldValue != null) {
RemoteArgumentSource.ObjectField(remoteArgFieldValue.removePrefix("\$source.").split('.'))
} else if (remoteArgArgValue != null) {
RemoteArgumentSource.FieldArgument(remoteArgArgValue.removePrefix("\$argument."))
} else {
throw IllegalArgumentException("$inputObjectTypeName requires one of $valueFromFieldKey or $valueFromArgKey to be set")
}
}

RemoteArgumentDefinition(remoteArgName, remoteArgumentSource)
}
}

private fun createTemplatedRemoteArgumentSource(value: String, argumentType: SourceType): RemoteArgumentSource {
// for backwards compat reasons - we will allow them to specify "$source.field.name" and treat it as just "field.name"
val values = value
.removePrefix("\$source.")
.removePrefix("\$argument.")
.split('.')

var argumentName: String? = null
var path: List<String>? = null
var staticValue: Value<*>? = null
when (argumentType) {
SourceType.ObjectField -> path = values
SourceType.FieldArgument -> argumentName = values.single()
SourceType.StaticArgument -> staticValue = StringValue(value)
}

return RemoteArgumentSource(argumentName, path, staticValue, argumentType)
}

fun createFieldMapping(fieldDefinition: GraphQLFieldDefinition): FieldMappingDefinition? {
val directive = fieldDefinition.getAppliedDirective(renamedDirectiveDefinition.name)
?: return null
Expand All @@ -427,10 +393,6 @@ object NadelDirectives {
return TypeMappingDefinition(underlyingName = from, overallName = directivesContainer.name)
}

private fun listFromDottedString(from: String): List<String> {
return from.split('.').toList()
}

private inline fun <reified T : Any> getDirectiveValue(
directive: GraphQLAppliedDirective,
name: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@ package graphql.nadel.validation

import graphql.Scalars
import graphql.nadel.dsl.RemoteArgumentDefinition
import graphql.nadel.dsl.RemoteArgumentSource.SourceType.ObjectField
import graphql.nadel.dsl.RemoteArgumentSource.SourceType.FieldArgument
import graphql.nadel.dsl.RemoteArgumentSource
import graphql.nadel.dsl.UnderlyingServiceHydration
import graphql.nadel.engine.util.isList
import graphql.nadel.engine.util.isNonNull
import graphql.nadel.engine.util.unwrapNonNull
import graphql.nadel.engine.util.unwrapOne
import graphql.scalars.ExtendedScalars
import graphql.schema.*
import graphql.schema.GraphQLEnumType
import graphql.schema.GraphQLFieldDefinition
import graphql.schema.GraphQLInputObjectType
import graphql.schema.GraphQLInputType
import graphql.schema.GraphQLNamedInputType
import graphql.schema.GraphQLObjectType
import graphql.schema.GraphQLScalarType
import graphql.schema.GraphQLType

internal class NadelHydrationArgumentValidation() {
internal class NadelHydrationArgumentValidation {
fun validateHydrationInputArg(
hydrationSourceType: GraphQLType,
actorFieldArgType: GraphQLInputType,
Expand All @@ -28,7 +34,7 @@ internal class NadelHydrationArgumentValidation() {

//could have ID feed into [ID] (as a possible batch hydration case).
// in this case we need to unwrap the list and check types
if (isBatchHydration && (!unwrappedHydrationSourceType.isList && unwrappedActorFieldArgType.isList)) {
return if (isBatchHydration && (!unwrappedHydrationSourceType.isList && unwrappedActorFieldArgType.isList)) {
val error = getHydrationInputErrors(
unwrappedHydrationSourceType,
unwrappedActorFieldArgType.unwrapOne(),
Expand All @@ -38,15 +44,18 @@ internal class NadelHydrationArgumentValidation() {
hydration,
actorFieldName
)

if (error != null) {
return NadelSchemaValidationError.IncompatibleHydrationArgumentType(
NadelSchemaValidationError.IncompatibleHydrationArgumentType(
parent,
overallField,
remoteArg,
hydrationSourceType,
actorFieldArgType,
actorFieldName
)
} else {
null
}
}
//could have [ID] feed into ID (non-batch, ManyToOne case explained in NadelHydrationStrategy.kt)
Expand All @@ -62,14 +71,16 @@ internal class NadelHydrationArgumentValidation() {
actorFieldName
)
if (error != null) {
return NadelSchemaValidationError.IncompatibleHydrationArgumentType(
NadelSchemaValidationError.IncompatibleHydrationArgumentType(
parent,
overallField,
remoteArg,
hydrationSourceType,
actorFieldArgType,
actorFieldName
)
} else {
null
}
}
// Otherwise we can just check the types normally
Expand All @@ -84,7 +95,6 @@ internal class NadelHydrationArgumentValidation() {
actorFieldName
)
}
return null
}

private fun getHydrationInputErrors(
Expand All @@ -96,10 +106,9 @@ internal class NadelHydrationArgumentValidation() {
hydration: UnderlyingServiceHydration,
actorFieldName: String,
): NadelSchemaValidationError? {

//need to check null compatibility
val sourceType = remoteArg.remoteArgumentSource.sourceType
if (sourceType != ObjectField && actorFieldArgType.isNonNull && !hydrationSourceType.isNonNull) {
// need to check null compatibility
val remoteArgumentSource = remoteArg.remoteArgumentSource
if (remoteArgumentSource !is RemoteArgumentSource.ObjectField && actorFieldArgType.isNonNull && !hydrationSourceType.isNonNull) {
// source must be at least as strict as field argument
return NadelSchemaValidationError.IncompatibleHydrationArgumentType(
parent,
Expand Down Expand Up @@ -136,7 +145,7 @@ internal class NadelHydrationArgumentValidation() {
)
}
// object feed into inputObject (i.e. hydrating with a $source object)
else if (sourceType == ObjectField && unwrappedHydrationSourceType is GraphQLObjectType && unwrappedActorFieldArgType is GraphQLInputObjectType) {
else if (remoteArgumentSource is RemoteArgumentSource.ObjectField && unwrappedHydrationSourceType is GraphQLObjectType && unwrappedActorFieldArgType is GraphQLInputObjectType) {
validateInputObjectArg(
unwrappedHydrationSourceType,
unwrappedActorFieldArgType,
Expand All @@ -148,7 +157,7 @@ internal class NadelHydrationArgumentValidation() {
)
}
// inputObject feed into inputObject (i.e. hydrating with an $argument object)
else if (sourceType == FieldArgument && unwrappedHydrationSourceType is GraphQLInputObjectType && unwrappedActorFieldArgType is GraphQLInputObjectType) {
else if (remoteArgumentSource is RemoteArgumentSource.FieldArgument && unwrappedHydrationSourceType is GraphQLInputObjectType && unwrappedActorFieldArgType is GraphQLInputObjectType) {
if (unwrappedHydrationSourceType.name != unwrappedActorFieldArgType.name) {
NadelSchemaValidationError.IncompatibleHydrationArgumentType(
parent,
Expand Down Expand Up @@ -200,11 +209,12 @@ internal class NadelHydrationArgumentValidation() {
): NadelSchemaValidationError? {
val unwrappedHydrationSourceType = hydrationSourceType.unwrapNonNull()
val unwrappedActorFieldArgType = actorFieldArgType.unwrapNonNull()
if (unwrappedHydrationSourceType is GraphQLScalarType && unwrappedActorFieldArgType is GraphQLScalarType) {

return if (unwrappedHydrationSourceType is GraphQLScalarType && unwrappedActorFieldArgType is GraphQLScalarType) {
if (isScalarAssignable(unwrappedHydrationSourceType, unwrappedActorFieldArgType)) {
return null
null
} else {
return NadelSchemaValidationError.IncompatibleHydrationArgumentType(
NadelSchemaValidationError.IncompatibleHydrationArgumentType(
parent,
overallField,
remoteArg,
Expand All @@ -213,8 +223,9 @@ internal class NadelHydrationArgumentValidation() {
actorFieldName
)
}
} else {
null
}
return null
}

private fun isScalarAssignable(typeToAssign: GraphQLNamedInputType, targetType: GraphQLNamedInputType): Boolean {
Expand Down Expand Up @@ -242,8 +253,8 @@ internal class NadelHydrationArgumentValidation() {
hydration: UnderlyingServiceHydration,
actorFieldName: String,
): NadelSchemaValidationError? {
var hydrationSourceFieldInnerType: GraphQLType = hydrationSourceType.unwrapNonNull().unwrapOne()
var actorFieldArgInnerType: GraphQLType = actorFieldArgType.unwrapNonNull().unwrapOne()
val hydrationSourceFieldInnerType: GraphQLType = hydrationSourceType.unwrapNonNull().unwrapOne()
val actorFieldArgInnerType: GraphQLType = actorFieldArgType.unwrapNonNull().unwrapOne()
val errorExists = getHydrationInputErrors(
hydrationSourceFieldInnerType,
actorFieldArgInnerType,
Expand Down Expand Up @@ -311,4 +322,4 @@ internal class NadelHydrationArgumentValidation() {
}
return null
}
}
}
Loading

0 comments on commit b540b12

Please sign in to comment.