[译] GraphQL - 学习 - 验证

 阅读约 11 分钟

原文地址: Validation

通过使用类型系统,可以预先确定一个 GraphQL 查询是否有效。这允许服务器和客户机在创建无效查询时有效地通知开发人员,而不必依赖于运行时检查。

对于我们的星球大战示例,文件 starwarsvalidtest.js 包含大量的查询,这些查询演示了各种各样的失效,并且是一个可以运行来执行引用实现验证器的测试文件。

首先,让我们使用一个复杂的有效查询。这是一个嵌套的查询,类似于上一节的示例,但是将复制的字段分解为一个片段:

{
  hero {
    ...NameAndAppearances
    friends {
      ...NameAndAppearances
      friends {
        ...NameAndAppearances
      }
    }
  }
}

fragment NameAndAppearances on Character {
  name
  appearsIn
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "appearsIn": [
        "NEWHOPE",
        "EMPIRE",
        "JEDI"
      ],
      "friends": [
        {
          "name": "Luke Skywalker",
          "appearsIn": [
            "NEWHOPE",
            "EMPIRE",
            "JEDI"
          ],
          "friends": [
            {
              "name": "Han Solo",
              "appearsIn": [
                "NEWHOPE",
                "EMPIRE",
                "JEDI"
              ]
            },
            {
              "name": "Leia Organa",
              "appearsIn": [
                "NEWHOPE",
                "EMPIRE",
                "JEDI"
              ]
            },
            ...
          ]
        },
        {
          "name": "Han Solo",
          "appearsIn": [
            "NEWHOPE",
            "EMPIRE",
            "JEDI"
          ],
          "friends": [
            {
              "name": "Luke Skywalker",
              "appearsIn": [
                "NEWHOPE",
                "EMPIRE",
                "JEDI"
              ]
            },
            ...
          ]
        },
        {
          "name": "Leia Organa",
          "appearsIn": [
            "NEWHOPE",
            "EMPIRE",
            "JEDI"
          ],
          "friends": [
            {
              "name": "Luke Skywalker",
              "appearsIn": [
                "NEWHOPE",
                "EMPIRE",
                "JEDI"
              ]
            },
            ...
            {
              "name": "R2-D2",
              "appearsIn": [
                "NEWHOPE",
                "EMPIRE",
                "JEDI"
              ]
            }
          ]
        }
      ]
    }
  }
}

这个查询是有效的。让我们看看一些无效的查询。

片段不能引用自身或创建一个循环,因为这可能导致一个无限循环!下面是相同的查询,但是没有明确的三层嵌套:

{
  hero {
    ...NameAndAppearancesAndFriends
  }
}

fragment NameAndAppearancesAndFriends on Character {
  name
  appearsIn
  friends {
    ...NameAndAppearancesAndFriends
  }
}

结果

{
  "errors": [
    {
      "message": "Cannot spread fragment \"NameAndAppearancesAndFriends\" within itself.",
      "locations": [
        {
          "line": 11,
          "column": 5
        }
      ]
    }
  ]
}

当我们查询字段时,我们必须查询给定类型中存在的字段。因此,当 hero 返回一个 Character 时,我们必须查询 Character 的字段。该类型没有一个 favoriteSpaceship,因此这个查询是无效的:

# INVALID: favoriteSpaceship does not exist on Character
{
  hero {
    favoriteSpaceship
  }
}
{
  "errors": [
    {
      "message": "Cannot query field \"favoriteSpaceship\" on type \"Character\".",
      "locations": [
        {
          "line": 4,
          "column": 5
        }
      ]
    }
  ]
}

每当我们查询一个字段,它返回的不是标量或枚举时,我们需要指定要从该字段返回的数据。英雄返回一个 Character,我们一直在请求 nameappearsIn 之类的字段;如果我们省略了这个,这个查询就不会有效:

# INVALID: hero is not a scalar, so fields are needed
{
  hero
}
{
  "errors": [
    {
      "message": "Field \"hero\" of type \"Character\" must have a selection of subfields. Did you mean \"hero { ... }\"?",
      "locations": [
        {
          "line": 3,
          "column": 3
        }
      ]
    }
  ]
}

类似地,如果字段是标量,在它上查询额外的字段是没有意义的,这样做会使查询无效:

# INVALID: name is a scalar, so fields are not permitted
{
  hero {
    name {
      firstCharacterOfName
    }
  }
}
{
  "errors": [
    {
      "message": "Field \"name\" must not have a selection since type \"String!\" has no subfields.",
      "locations": [
        {
          "line": 4,
          "column": 10
        }
      ]
    }
  ]
}

之前,有人注意到,查询只能查询所涉及类型的字段;当我们查询返回一个 Characterhero 时,我们只能查询存在于字符的字段。但是,如果我们想要查询 R2-D2s 的主函数,会发生什么呢?

# INVALID: primaryFunction does not exist on Character
{
  hero {
    name
    primaryFunction
  }
}
{
  "errors": [
    {
      "message": "Cannot query field \"primaryFunction\" on type \"Character\". Did you mean to use an inline fragment on \"Droid\"?",
      "locations": [
        {
          "line": 5,
          "column": 5
        }
      ]
    }
  ]
}

这个查询是无效的,因为 primaryFunction 不是一个 Character 的字段。我们希望通过某种方式表明,如果 CharacterDroid,我们希望获取 primaryFunction,否则将忽略该字段。我们可以使用前面介绍的片段来实现这一点。通过在 Droid 上设置一个片段并包含它,我们可以确保只查询定义的 primaryFunction

{
  hero {
    name
    ...DroidFields
  }
}

fragment DroidFields on Droid {
  primaryFunction
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}

这个查询是有效的,但是有点冗长;当我们多次使用它们时,命名的片段是很有价值的,但是我们只使用一次。我们可以使用一个内联片段,而不是使用一个命名的片段;这仍然允许我们指出我们正在查询的类型,但是没有指定一个单独的片段:

{
  hero {
    name
    ... on Droid {
      primaryFunction
    }
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2",
      "primaryFunction": "Astromech"
    }
  }
}

这仅仅触及了验证系统的表面;这里有许多验证规则,以确保 GraphQL 查询具有语义上的意义。规范在 验证 部分中详细讨论了这个主题,以及 GraphQL 中的验证目录。js 包含实现符合规范的 GraphQL 验证器的代码。

阅读 1k更新于 2017-12-27

推荐阅读
酸柠檬
用户专栏

柠檬是苦的. 酸柠檬更不知道是什么味道这就是程序的味道个中滋味, 无法对外人道深思回味, 终得豁然

17 人关注
44 篇文章
专栏主页
目录