Changes and change requests¶
ONE Record updates logistics objects with api:Change documents: lists of
ADD and DELETE operations on triples, sent with PATCH
/logistics-objects/{id} and processed by the holder as a change request. The
Change namespace models those documents, computes them, and applies them.
The Change model¶
Change\Change carries the target object, the revision it was written
against, the operations, an optional description, the
notifyRequestStatusChange flag and linked verification requests.
Change::fromJsonLd() reads a partner's PATCH body and raises
ChangeException (a 400) for anything malformed; toJsonLd() writes the
shape IATA's examples use (api:o as a one-element list, api:hasRevision
as a typed xsd:positiveInteger).
An operation object is a (datatype, value) pair. An XSD datatype means a
literal; any other datatype is the class of the node the value names: a
logistics object URI, an embedded object id (internal:…), or a blank node
label (_:b0) for an embedded object the change introduces.
Revisions¶
api:hasRevision is the revision of the logistics object the change was
written against, which is the revision the requester last read. The holder
applies the change only while that is still the current revision, and the
result becomes revision + 1. This is what NE:ONE enforces (spec question 14).
Computing a change¶
ChangeBuilder::diff($from, $to, $revision) returns the change that turns one
version of an object into another, or null when they are the same graph.
- Plain values and references become DELETE/ADD pairs (replace is delete plus add, as the spec says).
- Embedded objects are matched by content. An unchanged one produces nothing. When a slot holds exactly one old and one new object of the same type, the old one is edited in place through its embedded id (spec example C3). Otherwise the old object is deleted together with its triples (example C4) and the new one added as a blank node with its triples (example C2).
cargo:eventsis never part of a change; the spec forbids patching events.- A change of type is refused; a logistics object keeps its class.
Hosts use this when republishing: resolve the new graph, diff against the stored version, apply as the holder. Partners use it to request corrections.
Embedded objects are handled in two phases. First a correspondence between
the old and the new embedded nodes is built over the whole graphs: nodes with
identical content pair up first, in every slot, then a slot left with exactly
one old and one new node of the same type pairs them for an in-place edit.
Only then are operations emitted from that correspondence. So an embedded
node reached through several links (one cargo:Value used as both width and
height, say) is one node in the change: it is edited or introduced once and
linked as often as needed, a branch that did not change is never edited to
serve another branch, splitting or merging shared nodes yields exactly the
target's sharing, and a node's own triples are deleted only when no link to it
survives. Content matching uses the comparer, so
JsonLd\ComparisonBudgetExceeded can be raised for pathologically symmetric
structures (see JSON-LD).
Applying a change¶
ChangeApplier::apply($current, $currentRevision, $change) implements the
spec's rules for the holder:
api:hasLogisticsObjectmust be the object being changed.- The revision must match.
- No operation may touch
cargo:eventsor the object's type. - Subjects must be the object, one of its embedded objects, or a blank node that an ADD in the same change introduces.
- Deletes run first and must match an existing value (numbers compared by
value, so
20.0deletes2.0E1and anxsd:intdeletes anxsd:integer); then adds, which must not duplicate an existing value. - The whole resulting graph, root and every embedded node, must satisfy the
ontology:
Model\GraphValidator, the same validator that judges an object created over HTTP and a posted event. An embedded node must declare a class the ontology knows and that is not a logistics object class; every property must exist, be accepted by the node's classes, be of the right kind, and a literal must fit the range (XSD derivation included) and its datatype's grammar; a node under an object property must be of the range or a subclass when the graph knows its classes. - Every added value is checked against the ontology: the property must be accepted by the subject's class, literals go to datatype properties and nodes to object properties, and booleans, numbers and date-times must be well-formed.
- New embedded objects receive stable
internal:ids and the class named by the operation's datatype; embedded objects left unreachable are removed with their triples.
The result is the new object and the list of changed properties (for
api:hasChangedProperty in notifications). Any failure raises
ChangeRejected with api:Error objects and leaves the object untouched; the
server records them on the change request as REQUEST_FAILED.