GraphQL中的可选但非空字段

时间:2018-11-07 16:49:57

标签: node.js graphql graphql-js apollo-server

在对我们的GraphQL API进行更新时,仅需要模型_id字段,因此下面的SDL语言代码中的!。更新中不必包括其他字段,例如name,但也不能具有null值。当前,从名称字段中排除!可使最终用户不必在更新中传递name,但允许最终用户传递{{1} },这是不允许的。

一个null值使我们知道需要从数据库中删除一个字段。

下面是一个可能导致问题的模型示例-name自定义标量不允许空值,但GraphQL仍允许它们通过:

null

当传入null值时,它会绕过我们的Scalars,因此如果null值不是允许值,我们就不会引发用户输入验证错误。

我知道Name的意思是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 } ,缺少!的意思是该字段可以为空,但据我所知,我们无法指定该字段令人沮丧如果不需要/可选字段,则为字段的确切值。此问题仅在更新时发生。

是否有任何方法可以通过自定义Scalars解决此问题,而不必在每个更新解析器中启动硬编码逻辑,这似乎很麻烦?

应该失败的示例更改

non-nullable

变量

!

UPDATE 9/11/18:供参考,我找不到解决方法,因为使用自定义标量,自定义指令和验证规则存在问题。我已经在GitHub上打开了一个问题:https://github.com/apollographql/apollo-server/issues/1942

1 个答案:

答案 0 :(得分:4)

您真正要寻找的是自定义验证逻辑。您可以在构建架构时通常包含的“默认”集之上添加所需的任何验证规则。这是一个粗略的示例,说明如何添加一个规则,以在将特定类型或标量用作参数时检查空值:

Tab

编辑:仅当您不使用变量时,以上方法才有效,这在大多数情况下不会很有帮助。解决方法是,我能够利用FIELD_DEFINITION指令来实现所需的行为。您可能有多种方法可以解决此问题,但这是一个基本示例:

import "package:flutter/material.dart";
import "package:flutter/services.dart";

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
    Widget build(BuildContext context) {
      SystemChrome.setEnabledSystemUIOverlays([]);
      return MaterialApp(
        home: MyHome()
      );
    }
}

class MyHomeState extends State<MyHome> with TickerProviderStateMixin {
  TabController tabController;
  @override
    void initState() {
      tabController = tabController?? TabController(
        vsync: this,
        length: 2
      );
      super.initState();
    }
  @override
    void dispose() {
      tabController.dispose();
      super.dispose();
    }
  @override
    Widget build(BuildContext context) {
      return Scaffold(
        body: Column(
          children: [
            TabBar(
              controller: tabController,
              tabs: [
                Container(
                  color: Colors.purple,
                  width: 100.0,
                  height: 50.0
                ),
                Container(
                  color: Colors.pink,
                  width: 100.0,
                  height: 50.0
                )
              ]
            ),
            Expanded(
              child: TabBarView(
                controller: tabController,
                children: [
                  MyPageView(),
                  MyPageView(),
                ],
              )
            )
          ]
        )
      );
    }
}
class MyHome extends StatefulWidget {
  @override
    State<StatefulWidget> createState() {
      return MyHomeState();
    }
}

class MyPageViewState extends State<MyPageView> {
  PageController pageController;
  @override
    void initState() {
      pageController = pageController?? PageController();
      super.initState();
    }
    @override
      void dispose() {
        pageController.dispose();
        super.dispose();
      }
  @override
    Widget build(BuildContext context) {
      return PageView(
        controller: pageController,
        children: [
          Container(
            color: Colors.blue
          ),
          Container(
            color: Colors.green
          )
        ]
      );
    }
}
class MyPageView extends StatefulWidget {
  @override
    State<StatefulWidget> createState() {
      return MyPageViewState();
    }
}

然后在您的架构中:

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,
})

假设每次在架构中使用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) } } } 时,“ non null”输入字段都是相同的,则可以将每个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