|
| 1 | +package graphql.nadel.validation.hydration |
| 2 | + |
| 3 | +import graphql.nadel.engine.blueprint.hydration.NadelBatchHydrationMatchStrategy |
| 4 | +import graphql.nadel.engine.blueprint.hydration.NadelHydrationArgument |
| 5 | +import graphql.nadel.engine.blueprint.hydration.NadelHydrationCondition |
| 6 | +import graphql.nadel.engine.transform.query.NFUtil |
| 7 | +import graphql.nadel.engine.transform.query.NadelQueryPath |
| 8 | +import graphql.nadel.engine.util.getFieldContainerFor |
| 9 | +import graphql.nadel.engine.util.isNonNull |
| 10 | +import graphql.nadel.engine.util.unwrapAll |
| 11 | +import graphql.nadel.validation.NadelValidationContext |
| 12 | +import graphql.nadel.validation.NadelValidationInterimResult |
| 13 | +import graphql.nadel.validation.NadelValidationInterimResult.Success.Companion.asInterimSuccess |
| 14 | +import graphql.nadel.validation.onError |
| 15 | +import graphql.nadel.validation.onErrorCast |
| 16 | +import graphql.normalized.ExecutableNormalizedField |
| 17 | +import graphql.schema.GraphQLInputObjectType |
| 18 | +import graphql.schema.GraphQLObjectType |
| 19 | + |
| 20 | +/** |
| 21 | + * Forked version of [NadelHydrationSourceFieldValidation] that has new functionality so we can feature flag the code. |
| 22 | + */ |
| 23 | +internal class NadelHydrationSourceFieldValidation2 { |
| 24 | + context(NadelValidationContext, NadelHydrationValidationContext) |
| 25 | + fun getSourceFields( |
| 26 | + arguments: List<NadelHydrationArgument>, |
| 27 | + hydrationCondition: NadelHydrationCondition?, |
| 28 | + ): NadelValidationInterimResult<List<ExecutableNormalizedField>> { |
| 29 | + val argumentSourceFields = arguments.getSourceFields() |
| 30 | + .onError { return it } |
| 31 | + val conditionSourceFields = listOfNotNull(hydrationCondition?.fieldPath).map { makeLeafField(it) } |
| 32 | + |
| 33 | + return (argumentSourceFields + conditionSourceFields) |
| 34 | + .asInterimSuccess() |
| 35 | + } |
| 36 | + |
| 37 | + context(NadelValidationContext, NadelHydrationValidationContext) |
| 38 | + fun getBatchHydrationSourceFields( |
| 39 | + arguments: List<NadelHydrationArgument>, |
| 40 | + matchStrategy: NadelBatchHydrationMatchStrategy, |
| 41 | + hydrationCondition: NadelHydrationCondition?, |
| 42 | + ): NadelValidationInterimResult<List<ExecutableNormalizedField>> { |
| 43 | + val argumentSourceFields = arguments.getSourceFields() |
| 44 | + .onError { return it } |
| 45 | + val conditionSourceFields = listOfNotNull(hydrationCondition?.fieldPath).map { makeLeafField(it) } |
| 46 | + |
| 47 | + return (argumentSourceFields + conditionSourceFields) |
| 48 | + .asInterimSuccess() |
| 49 | + } |
| 50 | + |
| 51 | + context(NadelValidationContext, NadelHydrationValidationContext) |
| 52 | + private fun List<NadelHydrationArgument>.getSourceFields(): NadelValidationInterimResult<List<ExecutableNormalizedField>> { |
| 53 | + return mapNotNull { argument -> |
| 54 | + when (argument.valueSource) { |
| 55 | + is NadelHydrationArgument.ValueSource.ArgumentValue -> null |
| 56 | + is NadelHydrationArgument.ValueSource.FieldResultValue -> |
| 57 | + getSourceFieldQueryPaths(argument, argument.valueSource) |
| 58 | + .onErrorCast { return it } |
| 59 | + is NadelHydrationArgument.ValueSource.StaticValue -> null |
| 60 | + is NadelHydrationArgument.ValueSource.RemainingArguments -> null |
| 61 | + } |
| 62 | + }.asInterimSuccess() |
| 63 | + } |
| 64 | + |
| 65 | + context(NadelValidationContext, NadelHydrationValidationContext) |
| 66 | + private fun getSourceFieldQueryPaths( |
| 67 | + argument: NadelHydrationArgument, |
| 68 | + hydrationValueSource: NadelHydrationArgument.ValueSource.FieldResultValue, |
| 69 | + ): NadelValidationInterimResult<ExecutableNormalizedField> { |
| 70 | + val hydrationSourceType = hydrationValueSource.fieldDefinition.type.unwrapAll() |
| 71 | + |
| 72 | + if (hydrationSourceType is GraphQLObjectType) { |
| 73 | + return createObjectField(argument, hydrationValueSource) |
| 74 | + } |
| 75 | + |
| 76 | + return makeLeafField(hydrationValueSource.queryPathToField) |
| 77 | + .asInterimSuccess() |
| 78 | + } |
| 79 | + |
| 80 | + context(NadelValidationContext, NadelHydrationValidationContext) |
| 81 | + private fun createObjectField( |
| 82 | + argument: NadelHydrationArgument, |
| 83 | + hydrationValueSource: NadelHydrationArgument.ValueSource.FieldResultValue, |
| 84 | + ): NadelValidationInterimResult<ExecutableNormalizedField> { |
| 85 | + val parentObjectType = |
| 86 | + parent.underlying.getFieldContainerFor(hydrationValueSource.queryPathToField.segments) as GraphQLObjectType |
| 87 | + |
| 88 | + // todo: need to handle parents |
| 89 | + // todo: should probably check cardinality here too |
| 90 | + return makeObjectField( |
| 91 | + parentObjectType = parentObjectType, |
| 92 | + fieldName = hydrationValueSource.fieldDefinition.name, |
| 93 | + inputObjectType = argument.backingArgumentDef.type.unwrapAll() as GraphQLInputObjectType, |
| 94 | + outputObjectType = hydrationValueSource.fieldDefinition.type.unwrapAll() as GraphQLObjectType, |
| 95 | + ).asInterimSuccess() |
| 96 | + } |
| 97 | + |
| 98 | + private fun makeObjectField( |
| 99 | + parentObjectType: GraphQLObjectType, |
| 100 | + fieldName: String, |
| 101 | + inputObjectType: GraphQLInputObjectType, |
| 102 | + outputObjectType: GraphQLObjectType, |
| 103 | + ): ExecutableNormalizedField { |
| 104 | + val children = inputObjectType.fields |
| 105 | + .mapNotNull { inputField -> |
| 106 | + val equivalentOutputField = outputObjectType.getField(inputField.name) |
| 107 | + if (equivalentOutputField == null) { |
| 108 | + if (inputField.type.isNonNull) { // i.e. required |
| 109 | + null |
| 110 | + } else { |
| 111 | + error("Required input field is missed") // todo: proper error here |
| 112 | + } |
| 113 | + } else { |
| 114 | + val parentObjectType = parentObjectType.getField(fieldName).type.unwrapAll() as GraphQLObjectType |
| 115 | + if ( |
| 116 | + inputField.type.unwrapAll() is GraphQLInputObjectType |
| 117 | + || equivalentOutputField.type.unwrapAll() is GraphQLObjectType |
| 118 | + ) { |
| 119 | + makeObjectField( |
| 120 | + parentObjectType = parentObjectType, |
| 121 | + fieldName = inputField.name, |
| 122 | + inputObjectType = inputField.type.unwrapAll() as GraphQLInputObjectType, |
| 123 | + outputObjectType = equivalentOutputField.type.unwrapAll() as GraphQLObjectType, |
| 124 | + ) |
| 125 | + } else { |
| 126 | + ExecutableNormalizedField.newNormalizedField() |
| 127 | + .objectTypeNames(listOf(parentObjectType.name)) |
| 128 | + .fieldName(inputField.name) |
| 129 | + .build() |
| 130 | + } |
| 131 | + } |
| 132 | + } |
| 133 | + |
| 134 | + return ExecutableNormalizedField.newNormalizedField() |
| 135 | + .objectTypeNames(listOf(parentObjectType.name)) |
| 136 | + .fieldName(fieldName) |
| 137 | + .children(children) |
| 138 | + .build() |
| 139 | + } |
| 140 | + |
| 141 | + context(NadelValidationContext, NadelHydrationValidationContext) |
| 142 | + private fun makeLeafField( |
| 143 | + path: NadelQueryPath, |
| 144 | + ): ExecutableNormalizedField { |
| 145 | + // todo: should do some validation here?? e.g. arg is a scalar value, type validation? maybe type validation is done elsewhere already |
| 146 | + return NFUtil.createField( |
| 147 | + schema = backingService.underlyingSchema, |
| 148 | + parentType = parent.underlying as GraphQLObjectType, |
| 149 | + queryPathToField = path, |
| 150 | + fieldArguments = emptyMap(), |
| 151 | + fieldChildren = emptyList(), // This must be a leaf node |
| 152 | + ) |
| 153 | + } |
| 154 | +} |
0 commit comments