То, что вы действительно ищете, - это пользовательская логика проверки. Вы можете добавить любые необходимые правила проверки поверх набора «по умолчанию», который обычно включается при построении схемы. Вот грубый пример того, как добавить правило, которое проверяет нулевые значения для определенных типов или скаляров, когда они используются в качестве аргументов:
const { specifiedRules } = require('graphql/validation')
const { GraphQLError } = require('graphql/error')
const typesToValidate = ['Foo', 'Bar']
// This returns a "Visitor" whose properties get called for
// each node in the document that matches the property's name
function CustomInputFieldsNonNull(context) {
return {
Argument(node) {
const argDef = context.getArgument();
const checkType = typesToValidate.includes(argDef.astNode.type.name.value)
if (checkType && node.value.kind === 'NullValue') {
context.reportError(
new GraphQLError(
`Type ${argDef.astNode.type.name.value} cannot be null`,
node,
),
)
}
},
}
}
// We're going to override the validation rules, so we want to grab
// the existing set of rules and just add on to it
const validationRules = specifiedRules.concat(CustomInputFieldsNonNull)
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules,
})
РЕДАКТИРОВАТЬ : вышеописанное работает, только если вы не используете переменные, что в большинстве случаев не очень полезно. В качестве обходного пути я смог использовать директиву FIELD_DEFINITION для достижения желаемого поведения. Вероятно, есть несколько способов подойти к этому, но вот базовый пример:
class NonNullInputDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field
const { args: { paths } } = this
field.resolve = async function (...resolverArgs) {
const fieldArgs = resolverArgs[1]
for (const path of paths) {
if (_.get(fieldArgs, path) === null) {
throw new Error(`${path} cannot be null`)
}
}
return resolve.apply(this, resolverArgs)
}
}
}
Тогда в вашей схеме:
directive @nonNullInput(paths: [String!]!) on FIELD_DEFINITION
input FooInput {
foo: String
bar: String
}
type Query {
foo (input: FooInput!): String @nonNullInput(paths: ["input.foo"])
}
Предполагая, что «ненулевые» поля ввода одинаковы каждый раз, когда input
используется в схеме, вы можете сопоставить имя каждого input
с массивом имен полей, которые должны быть проверены. Так что вы можете сделать что-то вроде этого:
const nonNullFieldMap = {
FooInput: ['foo'],
}
class NonNullInputDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field
const visitedTypeArgs = this.visitedType.args
field.resolve = async function (...resolverArgs) {
const fieldArgs = resolverArgs[1]
visitedTypeArgs.forEach(arg => {
const argType = arg.type.toString().replace("!", "")
const nonNullFields = nonNullFieldMap[argType]
nonNullFields.forEach(nonNullField => {
const path = `${arg.name}.${nonNullField}`
if (_.get(fieldArgs, path) === null) {
throw new Error(`${path} cannot be null`)
}
})
})
return resolve.apply(this, resolverArgs)
}
}
}
А потом в вашей схеме:
directive @nonNullInput on FIELD_DEFINITION
type Query {
foo (input: FooInput!): String @nonNullInput
}