Необязательные, но не обнуляемые поля в GraphQL
В обновлении к нашему API GraphQL только модели _id
поле обязательно для заполнения !
в приведенном ниже коде языка SDL. Другие поля, такие как name
не должны быть включены в обновление, но также не могут иметь null
значение. В настоящее время, за исключением !
из поля имени позволяет конечному пользователю не передавать name
в обновлении, но это позволяет им передать null
значение для name
в, что не может быть разрешено.
null
Значение позволяет нам знать, что поле должно быть удалено из базы данных.
Ниже приведен пример модели, в которой это может вызвать проблемы - Name
Пользовательский скаляр не допускает нулевых значений, но GraphQL все еще позволяет им:
type language {
_id: ObjectId
iso: Language_ISO
auto_translate: Boolean
name: Name
updated_at: Date_time
created_at: Date_time
}
input language_create {
iso: Language_ISO!
auto_translate: Boolean
name: Name!
}
input language_update {
_id: ObjectId!
iso: Language_ISO!
auto_translate: Boolean
name: Name
}
Когда в него передается нулевое значение, оно обходит наши Скаляры, поэтому мы не можем выдать ошибку проверки пользовательского ввода, если нулевое значение не является допустимым.
Я знаю что !
средства non-nullable
и что отсутствие !
означает, что поле обнуляемо, однако, это огорчает, что, насколько я вижу, мы не можем указать точные значения для поля, если поле не является обязательным / необязательным. Эта проблема возникает только при обновлении.
Есть ли способы обойти эту проблему с помощью пользовательских скаляров, не прибегая к жесткой кодировке в каждом преобразователе обновлений, что кажется громоздким?
Пример мутации, которая должна провалиться
mutation tests_language_create( $input: language_update! ) { language_update( input: $input ) { name }}
переменные
input: {
_id: "1234",
name: null
}
ОБНОВЛЕНИЕ 9/11/18: для справки, я не могу найти способ обойти это, поскольку есть проблемы с использованием пользовательских скаляров, пользовательских директив и правил проверки. Я открыл вопрос о GitHub здесь: https://github.com/apollographql/apollo-server/issues/1942
1 ответ
То, что вы действительно ищете, это пользовательская логика проверки. Вы можете добавить любые необходимые правила проверки поверх набора "по умолчанию", который обычно включается при построении схемы. Вот грубый пример того, как добавить правило, которое проверяет нулевые значения для определенных типов или скаляров, когда они используются в качестве аргументов:
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
}