full replace API は「明示の空 = 全消し・missing/null = 拒否」を validator 層で区別する
コレクションを丸ごと差し替える full replace の更新 API では、「空配列を明示して全消し」と「フィールド未指定・null」を同じ扱いにすると、他フィールドだけ更新するつもりの呼び出しや壊れた payload がデータ全消しになる事故経路が生まれる。契約として「明示の空 = 全消し操作」「missing / null = 不正リクエストとして拒否」を分け、その区別は呼び出し側の注意ではなく validator 層で強制する。
誤解しやすい条件(Go の go-playground/validator)
スライスフィールドの required タグは「nil でないこと」を検証するため、JSON で該当キーが missing または null の場合(unmarshal 後 nil)は 400 になり、キーに空配列を明示した場合(非 nil の空スライス)は通る。つまり required タグ 1 つで「[] は全消しとして許可・missing/null は拒否」の契約をそのまま表現できる。逆に required を付け忘れると missing/null が長さ 0 として素通りし、full replace で全消し保存される。「空を許可したいから required を外す」は誤りで、外すと missing まで許可してしまう。
判断基準
- full replace のコレクション入力には required(nil 拒否)を付け、空配列を明示クリアの操作として文書化する。
- 省略時に既存値維持としたいフィールドは、full replace の対象から外してポインタ型などの部分更新セマンティクスにする。1 つの API 内で「省略 = 維持」のフィールドと「required な full replace」のフィールドは共存できる。
検証
境界テストを 3 点セットで置く: (1) キー missing が 400、(2) 明示 null が 400、(3) 明示の空配列が通って既存データが空で上書きされる(全消しが意図どおり動く)。この 3 点目を省くと、required の付け忘れ(missing で全消し)をテストが検出できない。構造体を直接渡すテストだけでなく、JSON の unmarshal を通した実バインドで missing / null / 空配列を作ることが重要(構造体直渡しでは nil と空の区別を作り分けたつもりになりやすい)。
関連
「省略フィールドを ambient デフォルトで補完すると既存関連を黙って上書きする」という既存の判断とは別軸で、こちらは補完ではなく拒否によって missing の破壊的解釈を塞ぐ側の機構。