关于 jsonapi
jsonapi 是面向 JSON:API 端点开发与审查的编码技能,整理了文档结构、资源、关系、错误、查询参数和 HTTP 状态码规范。
主要功能
- 01响应与资源结构规范
- 02Content-Type 与标识符要求
- 03关系与 include 指引
- 04错误格式与状态码
- 05查询参数校验规则
适用场景
- 01检查端点是否符合 JSON:API
- 02设计关系更新请求
- 03审查 API 错误与查询处理
MCP 相关
该技能建议使用 Context7 MCP 查询最新的 JSON:API 规范。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ prowler-cloud/ prowler --skill jsonapi安装后在 Claude Code 中输入/jsonapi确认已加载。
详细介绍
与 django-drf 搭配使用
此技能侧重于规范合规性。如需了解实现模式(ViewSet、Serializer、Filter),请将 django-drf 技能与此技能搭配使用。
| 技能 | 侧重点 |
|---|---|
jsonapi | 规范要求什么(MUST/MUST NOT 规则) |
django-drf | 如何在 DRF 中实现(代码模式) |
创建或修改端点时,必须同时调用这两项技能。
实现或审查前
创建或修改端点前,始终先根据最新规范进行验证:
选项 1:Context7 MCP(首选)
如果 Context7 MCP 可用,请直接查询 JSON:API 规范:
mcp_context7_resolve-library-id(query="jsonapi specification")
mcp_context7_query-docs(libraryId="<resolved-id>", query="[specific topic: relationships, errors, etc.]")
选项 2:WebFetch(备用)
如果 Context7 不可用,请从官方规范获取内容:
WebFetch(url="https://jsonapi.org/format/", prompt="Extract rules for [specific topic]")
这样可确保符合最新版本的 JSON:API,即使规范更新后也不例外。
关键规则(绝不可违反)
文档结构
- 绝不可在同一响应中同时包含
data和errors - 始终至少包含以下之一:
data、errors、meta - 资源对象中始终使用
type和id(字符串) - 创建资源时绝不可包含
id(由服务端生成)
Content-Type
- 始终使用
Content-Type: application/vnd.api+json - 始终使用
Accept: application/vnd.api+json - 未指定
ext/profile时,绝不可为媒体类型添加参数
资源对象
id始终使用字符串(即使是 UUID)type始终使用小写 kebab-case- 绝不可将
id或type放在attributes中 - 绝不可在
attributes中包含外键——请使用relationships
关系
- 始终至少包含以下之一:
links、data或meta - 始终使用资源关联格式:
{"type": "...", "id": "..."} - 绝不可在关系中使用原始 ID——始终使用关联对象
错误对象
- 始终以数组形式返回错误:
{"errors": [...]} - 始终将
status设为字符串(例如"400",而不是400) - 字段级错误始终包含
source.pointer
HTTP 状态码(必须遵守)
| 操作 | 成功 | 异步 | 冲突 | 未找到 | 禁止 | 错误请求 |
|---|---|---|---|---|---|---|
| GET | 200 | - | - | 404 | 403 | 400 |
| POST | 201 | 202 | 409 | 404 | 403 | 400 |
| PATCH | 200 | 202 | 409 | 404 | 403 | 400 |
| DELETE | 200/204 | 202 | - | 404 | 403 | - |
各状态码的适用场景
| 状态码 | 使用场景 |
|---|---|
200 OK | GET 成功;PATCH 返回响应正文;DELETE 返回响应 |
201 Created | POST 创建了资源(必须包含 Location 标头) |
202 Accepted | 异步操作已启动(返回任务引用) |
204 No Content | DELETE 成功;PATCH 不返回响应正文 |
400 Bad Request | 查询参数无效、请求格式错误、存在未知字段 |
403 Forbidden | 身份验证通过但无权限;拒绝客户端生成的 ID |
404 Not Found | 资源不存在,或被 RLS 隐藏(绝不透露具体原因) |
409 Conflict | ID 重复、类型不匹配、关系冲突 |
415 Unsupported | Content-Type 标头错误 |
文档结构
成功响应(单项)
{
"data": {
"type": "providers",
"id": "550e8400-e29b-41d4-a716-446655440000",
"attributes": {
"alias": "Production",
"connected": true
},
"relationships": {
"tenant": {
"data": {"type": "tenants", "id": "..."}
}
},
"links": {
"self": "/api/v1/providers/550e8400-..."
}
},
"links": {
"self": "/api/v1/providers/550e8400-..."
}
}
成功响应(列表)
{
"data": [
{"type": "providers", "id": "...", "attributes": {...}},
{"type": "providers", "id": "...", "attributes": {...}}
],
"links": {
"self": "/api/v1/providers?page[number]=1",
"first": "/api/v1/providers?page[number]=1",
"last": "/api/v1/providers?page[number]=5",
"prev": null,
"next": "/api/v1/providers?page[number]=2"
},
"meta": {
"pagination": {"count": 100, "pages": 5}
}
}
错误响应
{
"errors": [
{
"status": "400",
"code": "invalid",
"title": "Invalid attribute",
"detail": "UID must be 12 digits for AWS accounts",
"source": {"pointer": "/data/attributes/uid"}
}
]
}
查询参数
| 类别 | 格式 | 示例 |
|---|---|---|
page | page[number]、page[size] | ?page[number]=2&page[size]=25 |
filter | filter[field]、filter[field__op] | ?filter[status]=FAIL |
sort | 以逗号分隔,- 表示降序 | ?sort=-inserted_at,name |
fields | fields[type] | ?fields[providers]=id,alias |
include | 以逗号分隔的路径 | ?include=provider,scan.task |
规则
- 对不支持的查询参数必须返回
400 - 对不支持的
include路径必须返回400 - 对不支持的
sort字段必须返回400 - 指定
fields[type]时绝不可包含额外字段
常见违规(避免)
| 违规情况 | 错误示例 | 正确示例 |
|---|---|---|
| ID 使用整数 | "id": 123 | "id": "123" |
| 类型使用驼峰命名 | "type": "providerGroup" | "type": "provider-groups" |
| 外键放在 attributes 中 | "tenant_id": "..." | "relationships": {"tenant": {...}} |
| errors 不是数组 | {"error": "..."} | {"errors": [{"detail": "..."}]} |
| status 使用数字 | "status": 400 | "status": "400" |
| 同时包含 data 和 errors | {"data": ..., "errors": ...} | 只能包含其中一个 |
| 缺少指针 | {"detail": "Invalid"} | {"detail": "...", "source": {"pointer": "..."}} |
关系更新
一对一关系
PATCH /api/v1/providers/123/relationships/tenant
Content-Type: application/vnd.api+json
{"data": {"type": "tenants", "id": "456"}}
清除关系:{"data": null}
一对多关系
| 操作 | 方法 | 请求体 |
|---|---|---|
| 替换全部 | PATCH | {"data": [{...}, {...}]} |
| 添加成员 | POST | {"data": [{...}]} |
| 移除成员 | DELETE | {"data": [{...}]} |
复合文档(include)
使用 ?include=provider 时:
{
"data": {
"type": "scans",
"id": "...",
"relationships": {
"provider": {
"data": {"type": "providers", "id": "prov-123"}
}
}
},
"included": [
{
"type": "providers",
"id": "prov-123",
"attributes": {"alias": "Production"}
}
]
}
规则
- 每个包含的资源都必须能通过从主要数据开始的关系链访问
- 不得包含孤立资源
- 不得重复资源(相同的 type+id)
规范参考
- 完整规范:https://jsonapi.org/format/
- 实现:DRF 特定模式请使用
django-drfskill - 测试:测试模式请使用
prowler-test-apiskill
评论
支持 Markdown