Documentation
¶
Overview ¶
Package editor is the record-editing service surface: the JSON patch shape the API accepts, its validation against the editorial predicate whitelist, and the conversion to bibframe editorial patches. The typed WorkDoc mapper and operation lists layer on top in later tasks; this is the quad-level floor they compile down to.
Index ¶
Constants ¶
const ResourceItems = "items"
ResourceItems addresses every bf:Item in the grain at once. It is the only resource an op list can name that is not a single node: item ids are minted per grain, so a selection cannot name them, but "every copy shelved in Stacks" is exactly what a batch relocation means.
Variables ¶
var DefaultPredicateAllowlist = []string{
"http://id.loc.gov/ontologies/bibframe/",
"http://id.loc.gov/ontologies/bflc/",
"https://github.com/freeeve/libcat/ns#",
"http://www.w3.org/2004/02/skos/core#",
"http://www.w3.org/2000/01/rdf-schema#label",
}
DefaultPredicateAllowlist is the editorial predicate policy when a deployment configures none: BIBFRAME descriptive predicates, libcat's lcat: extension markers, and SKOS term descriptions. A fully open editorial graph would let one bad request poison projector output.
Functions ¶
func ApplyOps ¶
func ApplyOps(m *Mapper, grainNQ []byte, workID string, ops []Op, labels LabelResolver) ([]byte, error)
ApplyOps edits a grain through the profile mapper, translating field operations into the editorial write shapes of ARCHITECTURE §5:
- Adding a value asserts it editorially (direct fields on the resource node; chained fields on a deterministic skolem IRI, since editorial statements cannot reference the feed's blank structure nodes).
- Removing an editorial value retracts its quad.
- Any edit that suppresses feed values (remove/set/clear touching them) claims the field with an lcat:overrides marker and re-asserts the
surviving values editorially -- the semantics, so the feed
stays untouched and revert is always possible.
Label companions: when labels is non-nil, every IRI the patch asserts on a prefLabel-annotated field also gets its vocabulary labels written into the grain's authority:<scheme> graph, so the grain stays self-describing (Duplicates compare, exports, the projection's label index) without a vocab lookup at read time -- feed parity with ingest.enrichmentQuads.
Returns the re-canonicalized grain.
Types ¶
type Diff ¶
Diff describes the exact grain change a patch would make (the dry-run / diff-preview payload): canonical N-Quads lines added and removed.
func ComputeDiff ¶
ComputeDiff applies the patch to a copy of the grain and reports the line-level delta between the canonical forms.
func DiffLines ¶
DiffLines reports the canonical N-Quads lines added and removed between two grains -- the diff-preview payload for ops and patches alike.
func (Diff) Empty ¶ added in v0.100.0
Empty reports that the patch changes nothing. A save whose diff is empty must not write, republish, or audit: the grain store is content-addressed so the write is a no-op anyway, but the audit trail would gain a RECORD_EDIT row naming an etag identical to its predecessor's, indistinguishable from a real edit.
type FieldValue ¶
type FieldValue struct {
V string `json:"v"`
Lang string `json:"lang,omitempty"`
// Datatype preserves a literal's explicit datatype IRI.
Datatype string `json:"datatype,omitempty"`
// IRI marks an entity-valued field (V is the IRI).
IRI bool `json:"iri,omitempty"`
// Prov is the named graph the value came from ("feed:overdrive",
// "editorial:", "enrichment:locsh").
Prov string `json:"prov"`
// Overridden marks feed values shadowed by an lcat:overrides marker
//.
Overridden bool `json:"overridden,omitempty"`
// Annotation is the field's display-only qualifier resolved from the
// value's structure node (e.g. a heading's bf:source label, a
// contribution's bf:role label). Its quads stay in passthrough; ToGrain
// ignores it.
Annotation string `json:"annotation,omitempty"`
// Primary marks a chained value whose structure head is typed
// bflc:PrimaryContribution -- the author sorts before the
// narrator. Display-only; ToGrain ignores it.
Primary bool `json:"primary,omitempty"`
// Node is the value's subject term in N-Quads syntax -- the resource
// node for direct fields, the intermediate node for chained fields.
// Reconstruction metadata; clients treat it as opaque.
Node string `json:"node"`
}
FieldValue is one value of a profile field, with provenance for the editor's badges and the reconstruction metadata (Node) that keeps the grain <-> doc mapping lossless.
type LabelResolver ¶
LabelResolver resolves a controlled-term IRI to its vocabulary scheme and language-tagged prefLabels ("" key = untagged); ok is false when no loaded vocabulary knows the IRI. vocab.Index.LabelResolver adapts the index; nil disables label companions.
type Mapper ¶
type Mapper struct {
// WorkProfile shapes the Work fields; InstanceProfile the Instances'.
WorkProfile *profiles.Profile
InstanceProfile *profiles.Profile
}
Mapper materializes grains through a profile pair.
func (*Mapper) ToDoc ¶
ToDoc decomposes a grain into the typed document for one of its Works. Every quad is either claimed by exactly one profile field (rendered as a FieldValue) or preserved verbatim in Passthrough. Feed values whose (subject, predicate) carries an editorial lcat:overrides marker come back flagged Overridden -- shadowed in projection, shown to the editor for the hover-reveal / revert affordance.
type Op ¶
type Op struct {
// Resource is "work", an Instance id, or ResourceItems.
Resource string `json:"resource"`
// Path names the profile field, or an item field for ResourceItems.
Path string `json:"path"`
// Action: "add" one value, "remove" one matching value, "set" the whole
// value set, "clear" every value.
Action string `json:"action"`
Value *OpValue `json:"value,omitempty"` // add / remove
Values []OpValue `json:"values,omitempty"` // set
// Where guards a ResourceItems op: only items whose current value at Path
// is exactly this string are edited. Meaningless on the other resources.
Where *string `json:"where,omitempty"`
}
Op is one field-level edit. The SPA emits ordered op lists; the same shape backs drafts, macros, and batch templates (the plan's everything-is-an-operation-list rule).
type OpValue ¶
type OpValue struct {
V string `json:"v"`
Lang string `json:"lang,omitempty"`
IRI bool `json:"iri,omitempty"`
}
OpValue is one value in an operation.
type Patch ¶
type Patch struct {
Add []Statement `json:"add,omitempty"`
Remove []Statement `json:"remove,omitempty"`
}
Patch is the request body of a record edit: statements to add to and remove from the editorial graph.
func (Patch) BoundTo ¶ added in v0.92.0
BoundTo checks that every Work-node subject in p names workID -- the single-record form of the rule. A patch aimed at one work has no business asserting statements about another work's node, and a grain that describes a Work it does not contain is invisible to every reader that resolves from the Work id inward.
Subjects that are not Work nodes (an Instance node, a skolem child, an absolute IRI) pass through: those are the shapes a single-record editorial patch legitimately mints, and unlike the batch case there is nothing to rebind them to.
func (Patch) RebindWork ¶ added in v0.92.0
RebindWork returns p with every subject rebound to workID's Work node, so applying it to that work states something about that work. Call Rebindable first; a subject that is not a Work node is left alone here rather than silently rewritten.
func (Patch) Rebindable ¶ added in v0.92.0
Rebindable reports whether p can be applied to a work other than the one its subjects name -- the question a batch edit has to answer before it writes anything.
A batch patch carries one literal subject. Applied verbatim to every selected work, it writes quads describing the first work into every other work's grain, and the dry run agrees with the corruption because it diffs the same verbatim patch. The subject has to be rebound per work, which is only meaningful for a Work node: an Instance id or a skolem child names a node in one grain and nothing at all in another. A grain-local object has the same problem. Both are refused rather than guessed at.
func (Patch) ToBibframe ¶
ToBibframe converts the wire patch to a bibframe editorial patch.
type ResourceDoc ¶
type ResourceDoc struct {
ID string `json:"id"`
Fields map[string][]FieldValue `json:"fields"`
}
ResourceDoc is one resource's field view.
type Statement ¶
Statement is the JSON wire form of one editorial statement. Subjects are IRIs (typically "#<id>Work" / "#<id>Instance" fragments); objects are IRIs or literals -- never blank nodes, per the editorial-graph constraint.
type Term ¶
type Term struct {
// Kind is "iri" or "literal".
Kind string `json:"kind"`
Value string `json:"value"`
Lang string `json:"lang,omitempty"`
// Datatype is the literal datatype IRI, when not a plain/lang string.
Datatype string `json:"datatype,omitempty"`
}
Term is the JSON wire form of an RDF term.
type WorkDoc ¶
type WorkDoc struct {
WorkID string `json:"workId"`
// ProfileID shapes the Work fields; InstanceProfileID the Instances' --
// exposed so the editor can drive each form from the deployment's profile
// rather than a hardcoded field list. Empty when no
// instance profile is configured.
ProfileID string `json:"profileId"`
InstanceProfileID string `json:"instanceProfileId,omitempty"`
Work ResourceDoc `json:"work"`
Instances []ResourceDoc `json:"instances"`
// Passthrough holds the unclaimed statements as raw N-Quads lines.
Passthrough []string `json:"passthrough"`
}
WorkDoc is the typed editing document one grain materializes into: the Work's profile fields, its Instances' fields, and a passthrough of every statement no field claims -- so doc -> grain reproduces the input byte-for-byte when nothing is edited.