maksit-certs-ui/assets/docs/PATCH_DELTA_REFERENCE.md
2026-05-03 10:35:34 +02:00

11 KiB
Raw Permalink Blame History

PATCH Delta Handling Backend & Frontend Reference

This document is the single reference for how PATCH payloads (deltas) are structured and interpreted so backend (BE) and frontend (FE) stay consistent. It follows the MaksIT.Core patch contract (same rules as shared deepDelta helpers). MaksIT-CertsUI uses that model with a hostnames collection on account PATCH.

Audience: Backend (C# / ASP.NET) and Frontend (TypeScript / React) developers.


TL;DR (start here)

  • PATCH sends only what changed, not the full resource. Each change is tagged with an operation (set, remove, add item, remove item).
  • Root fields (e.g. description, contact): send new value + operations["fieldName"] = SetField or RemoveField.
  • Collections (e.g. hostnames): do not replace the whole array when the API is “patchable collection” semantics. Send per-item changes: each added item has operations.collectionItemOperation = AddToCollection, each removed item has RemoveFromCollection, and changed items send identity and changed fields.
  • Frontend (Certs WebUI): For Edit Account, use
    deepDelta(formState, backupState, { arrays: { hostnames: { identityKey: 'hostname', idFieldKey: 'hostname' } } })
    so hostname rows are itemized (including “add first hostname”) and stay in sync with the backend.
  • Backend: Use TryGetOperation(Constants.CollectionItemOperation, out var op) on each collection item; never treat root Operations["hostnames"] = SetField as “replace all” if the API follows per-item patch semantics.

1. Core contract (MaksIT.Core)

The following come from MaksIT.Core and must be respected by all consumers.

1.1 PatchOperation enum

Value Integer Meaning
SetField 0 Set or replace a scalar or root-level value
RemoveField 1 Set a field to null
AddToCollection 2 Add an item to a collection (used on collection items, not root)
RemoveFromCollection 3 Remove an item from a collection (used on collection items, not root)
  • Source: MaksIT.Core.Webapi.Models.PatchOperation
  • FE mirror: PatchOperation enum in WebUI (src/MaksIT.WebUI/src/models/PatchOperation.ts) must keep the same numeric values for JSON serialization.

1.2 PatchRequestModelBase

  • Operations: Dictionary<string, PatchOperation>? (C#) / { [key: string]: PatchOperation } (TS).
  • Lookup: Case-insensitive by property name (e.g. "hostnames", "description").
  • Usage:
    • Root level: Operations["propertyName"] describes the operation for that property (e.g. SetField for a changed field, RemoveField for null).
    • Collection items: Each element of a collection property is itself a patch model; it uses a reserved key (see below) to indicate add/remove/update for that item.

1.3 Collection item operation key

For elements inside a collection property (e.g. each item in hostnames), the operation is stored under a fixed key so the backend can distinguish “add/remove this item” from “update fields of this item”.

  • Key name: collectionItemOperation
  • BE: Constants.CollectionItemOperation (same string across MaksIT services when aligned with Core).
  • FE: COLLECTION_ITEM_OPERATION in src/MaksIT.WebUI/src/models/PatchOperation.ts; same string in payloads and in deepDelta. Keep in sync with backend.

Allowed values for collection items: AddToCollection (2), RemoveFromCollection (3). For in-place updates (same item, changed fields), the item typically has an id and no collectionItemOperation, or field-level changes follow your APIs semantics.


2. Backend (BE) rules

2.1 Root-level properties

  • Read request.TryGetOperation(propertyName, out var op).
  • If op == SetField: apply the new value from the request for that property.
  • If op == RemoveField: set the property to null (or clear it) where applicable.
  • If the property is not in Operations but is present with a value, treat as optional direct assignment (or ignore), depending on your API convention; for strict PATCH, prefer requiring operations for changed fields.

2.2 Collection properties (e.g. hostnames)

  • Rule: Collection properties are patched only via per-item operations when the API follows patchable-collection semantics. The backend does not treat root Operations["hostnames"] = SetField as “replace the entire collection” unless explicitly documented for that endpoint.
  • For each item in the collection payload:
    1. Call item.TryGetOperation(Constants.CollectionItemOperation, out var collectionOp) (or the agreed constant).
    2. If collectionOp == AddToCollection: add the item to the collection (merge by id if present, or append).
    3. If collectionOp == RemoveFromCollection: remove the item (by item.Id or by matching key fields such as hostname string).
    4. If no collectionItemOperation but the item is identifiable: treat as in-place update.
    5. If no collectionItemOperation and the item cannot be identified: do not add ambiguous items; the FE must send AddToCollection for new rows when required by your rules.

2.3 Consistency checklist (BE)

  • Use the same CollectionItemOperation key as the FE (see Constants / Core).
  • Do not rely on root-level SetField for patchable collections to mean “replace all”; use per-item add/remove/update only (unless documented otherwise).
  • For add: require TryGetOperation(CollectionItemOperation) == AddToCollection (or equivalent) for new items where applicable.
  • For remove: use RemoveFromCollection and/or identity fields agreed with the FE.

3. Frontend (FE) rules

3.1 Building the delta (deepDelta)

  • Scalar / root fields: Emit the new value and set operations[propertyName] = SetField or RemoveField as appropriate.
  • Primitive arrays: Emit the full array and operations[propertyName] = SetField (full replace).
  • Object arrays that are “patchable collections”: Must always produce itemized deltas when configured with an array policy (identity key / id field):
    • Each added item must have operations.collectionItemOperation = AddToCollection.
    • Each removed item must have operations.collectionItemOperation = RemoveFromCollection.
    • Updated items carry changed fields and identity; see deepDelta implementation in src/MaksIT.WebUI/src/functions/deep/deepDelta.ts.

3.2 Patchable collections identity requirement

For the backend to interpret add/remove/update correctly, each collection item must be identifiable:

  • Existing items: Use id from the server when present.
  • New items: May have no server id; the FE must pass an array policy with identityKey / idFieldKey so the delta stays itemized (e.g. hostname string as stable key for hostnames).

3.3 Shared array policies (this repository)

MaksIT-CertsUI does not ship a shared patchCollectionPolicies.ts; the Edit Account form passes an inline policy for the hostnames collection:

Collection Policy (inline) Used in
hostnames { identityKey: 'hostname', idFieldKey: 'hostname' } EditAccount.tsx

Example:

deepDelta(fromFormState, fromBackupState, {
  arrays: {
    hostnames: {
      identityKey: 'hostname',
      idFieldKey: 'hostname',
    },
  },
})

3.4 Consistency checklist (FE)

  • Use the same PatchOperation numeric values as Core (SetField 0, RemoveField 1, AddToCollection 2, RemoveFromCollection 3).
  • Use COLLECTION_ITEM_OPERATION from PatchOperation.ts (same string as backend Constants.CollectionItemOperation).
  • For account hostnames, pass the hostnames array policy in deepDelta so the delta is itemized, not a blind full-replace of the array.
  • New items must have operations.collectionItemOperation = AddToCollection when the backend expects it.

4. Payload examples

4.1 Root-level SetField (scalar)

{
  "description": "Updated",
  "operations": {
    "description": 0
  }
}

0 = SetField.

4.2 Root-level RemoveField (clear optional field)

{
  "operations": { "someOptionalField": 1 }
}

1 = RemoveField.

4.3 Root-level SetField (primitive array full replace)

{
  "tags": ["a", "b", "c"],
  "operations": {
    "tags": 0
  }
}

4.4 Collection property itemized (add items)

Example shape for new hostname rows (numeric ops match PatchOperation enum):

{
  "hostnames": [
    {
      "hostname": "api.example.com",
      "isDisabled": false,
      "operations": { "collectionItemOperation": 2 }
    }
  ]
}

2 = AddToCollection.

4.5 Collection property remove item

{
  "hostnames": [
    {
      "hostname": "old.example.com",
      "operations": { "collectionItemOperation": 3 }
    }
  ]
}

3 = RemoveFromCollection (identity may be hostname or server id depending on API).

4.6 Collection property in-place update

Item exists; fields change; no collectionItemOperation on the item (or only nested field operations per your model).


5. Quick reference

Aspect Backend Frontend
Operation enum PatchOperation (Core) PatchOperation (same values 03)
Root operations TryGetOperation(propertyName, out op) operations[propertyName] = op
Collection item key Constants.CollectionItemOperation ("collectionItemOperation") COLLECTION_ITEM_OPERATION in payload and deepDelta
New collection item Require AddToCollection on item when applicable Send operations.collectionItemOperation: 2 for new items
Certs account hostnames Per-item patch semantics deepDelta + hostnames array policy in EditAccount.tsx

  • MaksIT.Core: PatchOperation, PatchRequestModelBase (README / XML).

7. Current implementation vs reference (MaksIT-CertsUI)

7.1 Frontend (FE)

Checked: deepDelta usage in src/MaksIT.WebUI/src/forms/EditAccount.tsx for account PATCH; inline hostnames array policy; COLLECTION_ITEM_OPERATION in PatchOperation.ts and usage in deepDelta.ts.

  • Safe: Hostname rows use identityKey / idFieldKey so itemized deltas include AddToCollection / RemoveFromCollection where appropriate.
  • Consistent: Same PatchOperation values and collection-item key string as Core.

FE summary: Follows the shared reference; account PATCH covers account fields and hostnames.

7.2 Backend (BE)

Maintainers: Confirm the account PATCH handler in MaksIT.CertsUI WebAPI applies the same per-item rules (TryGetOperation(CollectionItemOperation, ...)) for hostnames as described in sections 2.2 and 3.

7.3 Gaps and maintenance

Topic Status Note
Shared policies file N/A in Certs Inline policy in EditAccount.tsx for hostnames.
New forms with patchable collections Ongoing When adding a form that patches a collection, pass the correct arrays: { key: policy } to deepDelta.

Last updated: 2026-04-12