GraphQL.js 17 的变量放进 fragment 后,Int 被误判不能传给 Int!
参数有默认值时,同一变量内联使用能通过,放进fragment却报错。核对17.0.2位置默认值收集,简单选择可先内联,保留null与省略的区别。
相同的 GraphQL 字段,直接写在 operation 中可以通过校验,放进 fragment 后却报 Variable "$h" of type "Int" used in position expecting type "Int!"。若参数本身定义了默认值,需要检查 GraphQL.js 17.0.2 的 fragment 变量用途收集路径。
例如 schema 中字段为 url(height: Int! = 10): String!,operation 声明 $h: Int,fragment 中通过 url(height: $h) 使用它。这个参数位置有默认值,本应参与 nullable 变量能否用于该位置的判断;正式 17.0.2 在收集 fragment 内用途时,却把位置默认值记录成 undefined。
参数默认值与变量默认值属于不同位置
VariablesInAllowedPositionRule 在看到非空参数配可空变量时,会检查变量是否有非空默认值,或参数位置是否有默认值。普通 operation 内的字段用途保留了后者;fragment 分支为引入片段变量而处理用途时,没有为这里引用的 operation 变量保留同一信息。
因此,先确认错误只在移入 fragment 后出现,再核对 schema 实际的参数定义。若参数没有默认值,或变量真的可以把 null 传到不允许空值的位置,原来的校验错误可能完全合理。不能把所有 Int 与 Int! 的错误都当成这次回归。
这里的默认值也不意味着显式 null 会被替换为 10。省略变量与传入 null 是两种请求,不能在客户端把两者随意合并。
受影响的简单选择可以先移回 operation
如果 fragment 只是同一类型下这一段简单字段,没有额外类型条件或指令,可以把这段选择移回 operation,让它走保留位置默认值的校验路径。下面给出完整的最小 schema 与 operation 定义,未执行请求或调用业务 resolver:
import { buildSchema, parse, validate } from 'graphql';
const schema = buildSchema(`
type Query { img: Img }
type Img { url(height: Int! = 10): String! }
`);
const document = parse(`
query Q($h: Int) {
img { url(height: $h) }
}
`);
const errors = validate(schema, document);
在应用中保留原有的字段、别名和选择范围。带类型条件、include/skip 等指令或嵌套 fragment 的文档,展开时还要保留对应语义,不能机械复制几行后把条件删掉。使用持久化查询或生成代码时,也应通过正常流程更新相应文档标识与产物。
不要为了让校验通过,移除 VariablesInAllowedPositionRule、把服务器参数改成可空,或关闭整套文档校验。这些改动会放宽其他请求的契约。
给 operation 变量直接加默认值,有时也能绕过这个判断,但会改变该变量在其他用途中的缺省行为。尤其一个变量被多个不同默认值的参数共用时,不能认为它与保留参数默认值完全等价。
校验通过之后,还要核请求语义
当前正式 17.0.2 仍保留 fragment 分支的上述写法;修复提议或其他分支中的变更不等于已进入应用安装的正式包。更新后应重新比较原 fragment 文档与等义的内联文档,而不是永久保留一份被改过的校验器。
覆盖省略 h、合法整数及显式 null 三种变量输入,同时区分静态文档校验与执行阶段的变量、参数强制转换。省略时应保留 height 的默认行为,合法整数仍按原值传递,显式 null 不能悄悄变成默认值。返回的字段选择也要保持一致,不能只看 errors 数组为空。


