Skip to content

Como Fazer: Atualização Parcial com PATCH (JSON Merge Patch) ​

Referência FT: FT326 (NENE2-FT/patchlog) — Atualização parcial JSON Merge Patch (RFC 7396): reset de campo nulo, rejeição de campo imutável, ETag/If-Match, mutação apenas pelo proprietário, 42 testes / 141 assertivas PASS.

Este guia mostra como implementar um endpoint PATCH seguindo a semântica JSON Merge Patch: apenas campos fornecidos são atualizados, null redefine para o padrão e campos imutáveis são rejeitados.

Schema ​

sql
CREATE TABLE documents (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    owner_id   INTEGER NOT NULL,
    title      TEXT    NOT NULL,
    body       TEXT    NOT NULL DEFAULT '',
    status     TEXT    NOT NULL DEFAULT 'draft',
    version    INTEGER NOT NULL DEFAULT 1,
    created_at TEXT    NOT NULL,
    updated_at TEXT    NOT NULL
);

Endpoints ​

MétodoCaminhoDescrição
POST/documentsCriar (requer X-User-Id)
GET/documentsListar
GET/documents/{id}Obter com header ETag
PATCH/documents/{id}Atualização parcial (requer X-User-Id)
DELETE/documents/{id}Excluir (apenas proprietário)

Criar ​

php
POST /documents  X-User-Id: 1
{"title": "Meu Doc", "body": "Conteúdo"}
→ 201  {"id": 1, "owner_id": 1, "title": "Meu Doc", "status": "draft", "version": 1}

// Sem X-User-Id → 401
// title ausente → 422
// title vazio   → 422
// body é opcional → padrão para ""

GET com ETag ​

php
GET /documents/1
→ 200  ETag: "doc-1-1"
{"id": 1, "title": "Meu Doc", "version": 1, ...}

Formato do ETag: "doc-{id}-{version}".

PATCH — Semântica JSON Merge Patch ​

php
// Atualizar apenas title — body inalterado
PATCH /documents/1  X-User-Id: 1
{"title": "Atualizado"}
→ 200  {"title": "Atualizado", "body": "Conteúdo", ...}

// Atualizar apenas body
PATCH /documents/1  X-User-Id: 1
{"body": "Novo conteúdo"}
→ 200  {"title": "Atualizado", "body": "Novo conteúdo", ...}

// {} vazio — sem operação (válido per RFC 7396 §3)
PATCH /documents/1  X-User-Id: 1
{}
→ 200  (documento inalterado)

// null redefine campo para o padrão
PATCH /documents/1  X-User-Id: 1
{"status": null}
→ 200  {"status": "draft"}   // redefinido para o padrão

Campos Imutáveis — Rejeitados ​

Alguns campos nunca devem ser alterados via PATCH:

php
PATCH /documents/1  {"id": 999}         → 422  // imutável
PATCH /documents/1  {"owner_id": 99}    → 422  // imutável
PATCH /documents/1  {"version": 999}    → 422  // imutável
PATCH /documents/1  {"created_at": "…"} → 422  // imutável

Autorização Apenas para Proprietário ​

php
// Usuário 2 tenta fazer patch no documento do usuário 1 → 404 (não 403, para prevenir enumeração)
PATCH /documents/1  X-User-Id: 2  {"title": "Roubado"}  → 404

// Proprietário sempre pode fazer patch no próprio
PATCH /documents/1  X-User-Id: 1  {"title": "Meu"}      → 200

ETag / If-Match ​

php
// PATCH condicional — 412 se a versão mudou
PATCH /documents/1  X-User-Id: 1  If-Match: "doc-1-1"
{"title": "Atualizado"}
→ 200  // se a versão ainda for 1

PATCH /documents/1  X-User-Id: 1  If-Match: "doc-1-1"
{"title": "Desatualizado"}
→ 412  // se a versão agora for 2

Validação de Tipo ​

php
PATCH /documents/1  {"title": 123}   → 422  // int em vez de string
PATCH /documents/1  {"body": [1,2]}  → 422  // array em vez de string

O Que NÃO Fazer ​

Anti-padrãoRisco
Tratar campo ausente igual a nullO chamador não pode limpar um campo; undefined ≠ null no Merge Patch
Permitir patch em owner_idTransferência de propriedade via API sem fluxo de autorização
Retornar 403 para acesso entre proprietáriosRevela existência do documento; retornar 404 em vez disso
Substituir documento inteiro no PATCHSobrescreve campos que o cliente não pretendia alterar
Aceitar campos imutáveis silenciosamente (sem operação)O cliente acredita que alterou id; falha silenciosa causa confusão

Publicado sob a licença MIT.