案例作品集 浏览精选项目

Shopify Plus 升级月费减免+最高抵扣$4800开发费用 - WesWoo专属优惠

指南

Shopify GraphQL 响应兼容验收:null、errors 与枚举变更

发布日期: 编辑复核:2026-10-04

本文面向维护商品读取接口的开发者和发布验收人员。目标是判断一次 GraphQL 响应能否进入下游商品快照:版本是否一致、结果是否完整、字段类型是否符合约定,以及新增枚举会不会被旧代码误解释。

资料核验于 2026年10月4日,查询固定使用 GraphQL Admin API 2026-10。需要已安装并获授权的应用、read_products 商品读取权限,以及开发店中的商品 ID。以下商品 ID、响应和测试都是假设样例;Python 代码已做离线测试,未调用商家 API。这些规则按 API 与授权条件适用,不能凭 Shopify Plus 套餐名推断权限。API 版本规则、Product 权限与字段。

1. 先固定查询,避免验收对象随代码漂移

保存查询文件、变量、请求版本和消费者字段清单。这个例子只读取一个商品,返回别名固定为 catalogItem:

query ProductContract($id: ID!) {
  catalogItem: product(id: $id) {
    id
    title
    status
    updatedAt
  }
}

变量示例是 {"id":"gid://shopify/Product/1001"},其中 ID 必须换成获授权开发店中的真实值。product(id: ID!) 的返回类型是可为空的 Product;输入 ID 必填,不代表结果一定存在。2026-10 product 查询。

需要验证查询时,可在自己的 Shopify CLI 应用项目运行 shopify app dev,按 g 打开该应用的 GraphiQL;它使用当前应用的权限。先在 Docs Explorer 对照字段,再在开发店执行获授权的只读查询。独立安装的 GraphiQL 应用具有自己的权限与数据边界,不能替代当前应用的授权验收。官方 GraphiQL 使用入口。

原始 HTTP 请求入口是 POST https://{store}.myshopify.com/admin/api/2026-10/graphql.json,认证请求使用 X-Shopify-Access-Token。使用官方应用模板或客户端时按其认证流程处理,不把 token 放进浏览器页面、样例响应或验收日志。Admin API 认证与端点。

2. 把 schema 类型翻译成消费者约定

schema 是 API 的类型契约。表中的 ! 表示非空;它约束是否为 null,不代表字符串必然有内容。消费者若要求非空白标题,应另列业务规则,不把业务规则冒充 API 保证。GraphQL 类型与 Non-Null。

对象或字段2026-10 类型本例的验收动作
catalogItem查询 product 的返回值 Product区分键缺失和明确的 null;为空时暂停更新
idID!保留字符串原值,并与请求 ID 比较;不转成整数
titleString!检查字符串类型;空字符串通过类型检查
statusProductStatus!识别当前四个值;未知值暂停并检查版本与映射
updatedAtDateTime!保留原值;本例另要求可解析且带时区的时间字符串

字段类型见 Product 字段定义。ID 按字符串序列化;DateTime 是 ISO 8601 日期时间字符串。本例的带时区解析要求属于消费者策略,并非完整的 ISO 8601 校验器。GraphQL ID、Shopify DateTime。

updatedAt 也可能因订单导致的库存调整而变化,不能把它直接当成“标题被修改”的证据。Product.updatedAt。

3. HTTP 200 后继续检查 errors、data 和实际版本

GraphQL 的成功 HTTP 状态不能替代业务验收:Shopify 可以返回 HTTP 200 和 errors。GraphQL 执行中的字段错误还可能与部分 data 同时出现;执行前的请求错误则可能没有 data。Shopify 错误响应、GraphQL 响应结构。

本例采用适合完整商品快照的保守策略:只要出现非空 errors,这次响应就不能覆盖上一次有效快照。允许部分数据显示的产品可以另订策略,但必须说明哪些字段仍可使用。

观察到的结果本例处理下一步
非 200 或 JSON 无法解析不进入字段验收保留状态码与请求标识,排查传输或服务错误
实际版本与请求版本不同暂停提升为有效快照检查请求 URL 和版本支持状态
存在非空 errors,即使也有商品保留旧快照根据错误码处理授权、节流或服务故障
data 没有 catalogItem 键视为结果形状不符比较查询别名、缓存和解析代码
catalogItem 明确为 null标为暂不可用检查 ID、授权与资源状态,不据此直接删除本地商品
ID、类型、枚举与时间检查均通过返回允许字段组成的快照由业务流程决定何时提交

记录响应头 X-Shopify-API-Version 的实际值;HTTP 头名应由调用层按大小写不敏感方式读取。Shopify 会在请求版本失去支持时回退到最早仍受支持的版本,所以“URL 写了版本”与“实际按该版本执行”需要分别取证。API 版本与响应头。

4. 用一个小函数落实快照入口

下面的 Python 3.11+ 函数只处理已解析的响应,不发请求。调用层负责获取状态码、实际版本头和 JSON;解析失败时停止调用。返回值由状态与快照组成,失败时快照为 None。它只输出允许的四个字段,额外 JSON 字段不会自动流向下游。

from datetime import datetime

VERSION = "2026-10"
STATUSES = {"ACTIVE", "ARCHIVED", "DRAFT", "UNLISTED"}
FIELDS = ("id", "title", "status", "updatedAt")


def accept_product(http_status, actual_version, payload, requested_id):
    if http_status != 200:
        return "HTTP_FAILURE", None
    if actual_version != VERSION:
        return "VERSION_MISMATCH", None
    if not isinstance(payload, dict):
        return "BAD_ENVELOPE", None
    if "errors" in payload:
        errors = payload["errors"]
        if not isinstance(errors, list):
            return "BAD_ENVELOPE", None
        if errors:
            return "GRAPHQL_ERRORS", None
    data = payload.get("data")
    if not isinstance(data, dict) or "catalogItem" not in data:
        return "MISSING_RESULT", None
    item = data["catalogItem"]
    if item is None:
        return "UNAVAILABLE", None
    if not isinstance(item, dict) or any(k not in item for k in FIELDS):
        return "BAD_FIELDS", None
    if any(not isinstance(item[k], str) for k in FIELDS):
        return "BAD_TYPES", None
    if item["id"] != requested_id:
        return "ID_MISMATCH", None
    if item["status"] not in STATUSES:
        return "UNKNOWN_STATUS", None
    value = item["updatedAt"]
    try:
        stamp = datetime.fromisoformat(value.replace("Z", "+00:00"))
        if "T" not in value or stamp.tzinfo is None:
            return "BAD_TIMESTAMP", None
    except ValueError:
        return "BAD_TIMESTAMP", None
    return "ACCEPT", {k: item[k] for k in FIELDS}

例如,假设 body 是一个完整的合成响应:

body = {"data": {"catalogItem": {
    "id": "gid://shopify/Product/1001",
    "title": "Example shirt",
    "status": "UNLISTED",
    "updatedAt": "2026-10-04T00:00:00Z"
}}}
state, snapshot = accept_product(
    200, "2026-10", body, "gid://shopify/Product/1001"
)
assert state == "ACCEPT"
body["errors"] = [{"message": "synthetic field error"}]
assert accept_product(200, "2026-10", body,
                      "gid://shopify/Product/1001")[0] == "GRAPHQL_ERRORS"

离线测试覆盖了正常值、空标题、额外字段、结果为空、键缺失、部分数据带错误、错误类型、ID 不一致、未知枚举、非法或无时区时间,以及版本和 HTTP 状态不符。这些测试证明示例函数按约定分流,不证明任何商家授权或真实查询已经成功。

5. 用 UNLISTED 检验枚举升级,保留失败材料

2026-10 的 ProductStatus 包含 ACTIVE、ARCHIVED、DRAFT、UNLISTED。UNLISTED 自 2025-10 起可见,在更老版本中会被转换为 ACTIVE。这是一个可用于兼容验收的真实版本差异:旧消费者若只认识前三个值,升级后会拒绝新值;把未知值默认映射成 ACTIVE 则可能掩盖商品可见性变化。ProductStatus 具体定义。

为四个已知值分别准备 fixture,也保留一个合成未知值来检查暂停路径。不要从 ACTIVE 推断商品已发布到某个销售渠道;官方定义明确区分状态与渠道发布。

升级验收交付物应包括:旧与新版本的查询、所选字段类型和枚举差异、脱敏 fixture、函数与 fixture 的版本、每个失败状态的负责人,以及恢复上一个消费者版本的办法。仅当新 schema 对照完成、离线反例通过、获授权开发店的实际响应也通过同一函数时,才完成该环境的兼容验收。未执行的开发店步骤继续标为待执行。

大批量读取还需处理分页、成本和中断恢复,可继续阅读 GraphQL 性能与数据查询验收。这里的单对象响应门禁可放在每次读取后的消费者入口,不能替代整批任务的完成核对。

浏览 WESWOO 案例作品集