All Downloads are FREE. Search and download functionalities are using the official Maven repository.

graphql.nadel.validation.NadelInputValidation.kt Maven / Gradle / Ivy

Go to download

Nadel is a Java library that combines multiple GrahpQL services together into one API.

There is a newer version: 2024-11-20T03-31-21-302962b7
Show newest version
package graphql.nadel.validation

import graphql.nadel.engine.util.isList
import graphql.nadel.engine.util.isNonNull
import graphql.nadel.engine.util.isNotWrapped
import graphql.nadel.engine.util.isWrapped
import graphql.nadel.engine.util.strictAssociateBy
import graphql.nadel.engine.util.unwrapNonNull
import graphql.nadel.engine.util.unwrapOne
import graphql.nadel.validation.NadelSchemaValidationError.IncompatibleArgumentInputType
import graphql.nadel.validation.NadelSchemaValidationError.IncompatibleFieldInputType
import graphql.nadel.validation.NadelSchemaValidationError.MissingUnderlyingInputField
import graphql.nadel.validation.util.NadelSchemaUtil
import graphql.schema.GraphQLArgument
import graphql.schema.GraphQLFieldDefinition
import graphql.schema.GraphQLInputObjectField
import graphql.schema.GraphQLInputObjectType
import graphql.schema.GraphQLInputType
import graphql.schema.GraphQLType
import graphql.schema.GraphQLUnmodifiedType

internal class NadelInputValidation {
    fun validate(
        schemaElement: NadelServiceSchemaElement,
    ): List {
        return if (schemaElement.overall is GraphQLInputObjectType && schemaElement.underlying is GraphQLInputObjectType) {
            validate(
                parent = schemaElement,
                overallFields = schemaElement.overall.fields,
                underlyingFields = schemaElement.underlying.fields,
            )
        } else {
            emptyList()
        }
    }

    private fun validate(
        parent: NadelServiceSchemaElement,
        overallFields: List,
        underlyingFields: List,
    ): List {
        val underlyingFieldsByName = underlyingFields.strictAssociateBy { it.name }

        return overallFields.flatMap { overallField ->
            validate(parent, overallField, underlyingFieldsByName)
        }
    }

    private fun validate(
        parent: NadelServiceSchemaElement,
        overallInputField: GraphQLInputObjectField,
        underlyingFieldsByName: Map,
    ): List {
        val underlyingInputField = underlyingFieldsByName[overallInputField.name]
        return if (underlyingInputField == null) {
            listOf(
                MissingUnderlyingInputField(parent, overallInputField),
            )
        } else {
            validate(parent, overallInputField, underlyingInputField)
        }
    }

    private fun validate(
        parent: NadelServiceSchemaElement,
        overallInputField: GraphQLInputObjectField,
        underlyingInputField: GraphQLInputObjectField,
    ): List {
        return if (!isInputTypeValid(overallInputField.type, underlyingInputField.type)) {
            listOf(IncompatibleFieldInputType(parent, overallInputField, underlyingInputField))
        } else {
            listOf()
        }
    }

    fun validate(
        parent: NadelServiceSchemaElement,
        overallField: GraphQLFieldDefinition,
        overallInputArgument: GraphQLArgument,
        underlyingInputArgument: GraphQLArgument,
    ): List {
        return if (!isInputTypeValid(overallInputArgument.type, underlyingInputArgument.type)) {
            listOf(IncompatibleArgumentInputType(parent, overallField, overallInputArgument, underlyingInputArgument))
        } else {
            listOf()
        }
    }

    /**
     * It checks whether the type name and type wrappings e.g. [graphql.schema.GraphQLNonNull] make sense.
     * Same as [NadelTypeValidation.isOutputTypeValid] but with the logic for acceptable nullability logic flipped
     * i.e. we allow the overall input type to be non-nullable and the underlying input type to be nullable
     */
    private fun isInputTypeValid(
        overallType: GraphQLInputType,
        underlyingType: GraphQLInputType,
    ): Boolean {
        var overall: GraphQLType = overallType
        var underlying: GraphQLType = underlyingType

        while (overall.isWrapped && underlying.isWrapped) {
            if (!underlying.isNonNull && overall.isNonNull) {
                // Overall type is allowed to have stricter restrictions
                overall = overall.unwrapOne()
            } else if ((overall.isList && underlying.isList) || (overall.isNonNull && underlying.isNonNull)) {
                overall = overall.unwrapOne()
                underlying = underlying.unwrapOne()
            } else {
                return false
            }
        }

        if (overall.isNotWrapped && underlying.isNotWrapped) {
            return isInputTypeNameValid(
                overallType = overall as GraphQLUnmodifiedType,
                underlyingType = underlying as GraphQLUnmodifiedType,
            )
        } else if (overall.isWrapped && underlying.isNotWrapped) {
            if (overall.isNonNull && overall.unwrapNonNull().isNotWrapped) {
                return isInputTypeNameValid(
                    overallType = overall.unwrapNonNull() as GraphQLUnmodifiedType,
                    underlyingType = underlying as GraphQLUnmodifiedType,
                )
            }
            return false
        } else {
            return false
        }
    }

    private fun isInputTypeNameValid(
        overallType: GraphQLUnmodifiedType,
        underlyingType: GraphQLUnmodifiedType,
    ): Boolean {
        return NadelSchemaUtil.getUnderlyingName(overallType) == underlyingType.name
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy