skip to content

In a SCIM 2.0 PatchOp your service receives, what do op and path mean, and when is path required?

level: middleimportance: must knowfreq 42%

answer

  1. three operations, not four
  2. one of them cannot guess its target
  3. the bracket selects, the name does not
  4. operations apply in order
  5. remove requires path

basics

~20 s

A PatchOp carries an ordered Operations array; each entry has an op of add, replace or remove, an optional value, and a path selecting what it targets. Path is optional for add and replace, which then merge value at the resource root, and required for remove.

solid answer

~40 s

A PATCH body is a `urn:ietf:params:scim:api:messages:2.0:PatchOp` document holding an ordered `Operations` array. Each operation has an `op` of `add`, `replace` or `remove`, usually a `value`, and optionally a `path`. Omitting `path` on `add` or `replace` is legal: `value` is then an object whose attributes are merged onto the resource root. `remove` must carry a `path`, because there is nothing else to say what disappears. The part people miss is the **value path**: a `path` may carry a filter in brackets that selects one element of a multi-valued attribute, as in `members[value eq "..."]`, which is how a single person is taken out of a group without rewriting the whole membership. Operations apply in order, so a later one sees the earlier one's result.

code

json · 14 lines
json
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [
    {
      "op": "add",
      "path": "members",
      "value": [{ "value": "2819c223-7f76-453a-919d-413861904646" }]
    },
    {
      "op": "remove",
      "path": "members[value eq \"f1a0b7c4-2d19-4f0a-8c31-0b6d4e9a7c55\"]"
    }
  ]
}

go deeper

for a junior

Recall the shape: a PatchOp holds a list called Operations, each with an op that is add, replace or remove, usually a value, and sometimes a path. Remember that remove is the one that must say what it targets.

for a middle

Explain why path is optional for add and replace — value can be an object whose member names are the attributes — and mandatory for remove. Then explain a value path in brackets and what it selects inside a multi-valued attribute.

for a senior

Demonstrate that operations apply in order inside one transaction, and that a value path must become a targeted predicate against storage rather than a read-modify-write of the whole membership, which would restore the lost update PATCH exists to prevent.

for a principal

Frame the choice of how much of the path grammar you implement as a product commitment: every selector you accept is one you must support for years against clients you cannot change, and every one you refuse narrows which customers can integrate.

## Why this endpoint has a PATCH at all A provisioning client could keep your copy of a person current with full replacements, and the simple ones do. But a dental group's radiography viewer holds groups with hundreds of members, and the change that actually arrives is *one hygienist joined the Wednesday rota* or *this radiographer is no longer active*. Sending the whole membership back to express that is a lost-update machine: two administrators acting a second apart each send a full list computed before the other's change, and one of them silently wins. PATCH exists so that a change can be expressed as a change. The document is a `urn:ietf:params:scim:api:messages:2.0:PatchOp`, and its only interesting member is an ordered `Operations` array. Each element is a small object: - **`op`** — one of `add`, `replace` or `remove`. There are three, and there is no fourth. - **`path`** — an attribute path selecting the target. Optional for two of the three operations, required for the third. - **`value`** — what to write. Absent on `remove`, present on the other two, and its shape depends on whether `path` is there. ## The three operations, and what each one means to your storage | op | path | value | what your handler does | |---|---|---|---| | `add` | optional | required | sets the attribute if unset; for a multi-valued attribute, adds an element to the existing set rather than replacing it | | `replace` | optional | required | overwrites the targeted attribute, or the attributes named in `value` when no path is given | | `remove` | **required** | absent | deletes the targeted attribute or, with a value path, the elements it selects | The asymmetry is the whole point of the question. `add` and `replace` can go without a `path` because `value` can then be an object whose member names *are* the paths: `{"op": "replace", "value": {"active": false, "title": "Senior Radiographer"}}` is two attribute writes in one operation, merged onto the resource root. `remove` has no such option — with no `path` there is nothing in the document naming what should disappear, so it is required and a `remove` without one is a malformed request you reject rather than guess at. ## Value paths: addressing one element of a multi-valued attribute A `path` is not only a dotted attribute name. It may carry a filter in square brackets that selects elements of a multi-valued attribute, and it may continue past the bracket into a sub-attribute: 1. `members` — the whole multi-valued attribute. 2. `members[value eq "2819c223-..."]` — exactly the element whose `value` sub-attribute holds that resource identifier. 3. `emails[type eq "work"].value` — the `value` sub-attribute of whichever email is the work one. Form 2 is how a person leaves a group without the client ever sending the membership list. Form 1 with `op` `remove` empties the group. A handler that ignores the bracket and treats `members[...]` as `members` will therefore delete every member of a practice's group on what was meant to be one departure — a defect that passes every unit test written with a single-member fixture. Ordering matters too. `Operations` is a list, not a set, and operations apply in sequence against the running state, so an `add` followed by a `remove` of the same element leaves nothing behind and the reverse leaves it there. Apply them in the order given, inside one transaction, and fail the whole document rather than half of it — a partially applied PATCH leaves the client believing a state you do not hold. ## Where deactivation shows up The change a candidate is usually asked about arrives here. When a radiographer leaves the practice group, many provisioning clients do not delete anything: they send `{"op": "replace", "path": "active", "value": false}`. That single boolean is a *departure* expressed as a write on this endpoint, and your handler must recognise it as such. What it then triggers — whether the account is disabled or deleted, what happens to the images filed under it, which sessions and credentials must end — is a policy decision owned elsewhere in the product. This endpoint's job is narrower and absolute: receive the operation, apply it correctly, and hand the resulting state change to whatever owns the consequence. ## The reject list - A `remove` with no `path` — malformed, and guessing is worse than refusing. - A `path` your implementation cannot parse — say so rather than applying a nearby interpretation. - An `op` you do not recognise — there are three, and a fourth is a client bug you should surface, not absorb. - A write to an attribute you hold as read-only, such as `id` — the client does not own it.

  • Why is a PATCH preferred over a full replacement for group membership, given both can express the same end state?
    Because a full replacement carries the whole membership computed at the client before it was sent. Two administrators changing a practice's rota seconds apart each send a complete list that omits the other's change, and the later write silently discards the earlier one. A PATCH expresses one delta against whatever the current state is, so concurrent deltas compose instead of overwriting.
  • Two operations in one PatchOp touch the same attribute. What must your handler guarantee?
    Ordered application and all-or-nothing. `Operations` is a list and each operation sees the previous one's result, so applying them out of order produces a different resource. Apply them in sequence inside one transaction and fail the whole document if any one fails; a half-applied PATCH leaves the provisioning client recording a state you do not hold, with nothing to reconcile it against.
  • A PatchOp arrives whose path selects a multi-valued element, but the practice's group has hundreds of members. What is the implementation trap?
    Loading the whole multi-valued attribute to apply one element change, and writing it all back. That reintroduces exactly the lost update PATCH exists to avoid, on top of the memory cost. Resolve the value path to a predicate and apply it as a targeted delete or insert on the membership relation, so concurrent operations on different members do not collide.

saying these in an interview costs you the question

  • Thinks every operation requires a path
  • Treats members[value eq ...] as the whole members attribute
  • Applies operations in any order because they are 'independent'
  • Invents a fourth op such as move or set
  • Applies half a PatchOp and returns success
  • Accepts a client's write to the read-only id