Expand description
Project-resource ZIP-member surgery (05-02) — the resource family
re-pointed onto project-export zips, closing the Phase 3
cross-phase defect: the /projects/{p}/resources/** REST routes
the family originally targeted DO NOT EXIST on real 8.3 gateways
(openapi-evidenced twice — 575 paths, zero matches — plus the EAM
probe and the gateway-scripting API audit; 05-RESEARCH). The
native steer’s honest endpoint: export/import round-trip. These
helpers are the surgery half.
PURE functions — no crate::client::GatewayApi surface, no I/O
beyond the zip crate itself — so every mapping is unit-testable
without a gateway. The orchestration (export → surgery → import)
lives in actions::resources.
Zip layout of an 8.3 project export (05-RESEARCH, live-extracted):
project.json at the root plus <collection>/resources/<rest>
file members (collections are single-segment module ids —
com.inductiveautomation.perspective, ignition, …). The
user-facing path form — the Phase-3 UX-unchanged contract — is
<collection>/<rest>: the resources/ segment is stripped on the
way OUT and re-inserted on the way IN. A no-slash user path (a
project-root file, e.g. perspective-properties.json) rides a
module named after the path itself: <X> ↔ <X>/resources/<X>
(06-08, live-proven — the only adoptable shape for root-level
files; see [member_path]). project.json is never a resource.
Directory entries (when a writer emits them) are
skipped on list and preserved verbatim on rewrite.
ResourceEntry keeps the Phase-3 list shape (path typed,
passthrough extras) so the CLI’s rendering contract is untouched;
surgery results carry no extras.
Structs§
- Diff
Summary - The four counts — the summary line and the JSON
summaryobject. - Member
Diff - The member-level diff result: counts + one entry per resource
member, sorted by path. The root
project.jsonis EXCLUDED (it is not a resource;project_meta_deltasurfaces it separately). - Member
Diff Entry - One row of the diff: the user path + its status.
- Resource
Entry - One list item — the Phase-3 shape, unchanged:
pathtyped (the human renderer prints one per line), unknown keys round-trip. Surgery-sourced entries carry no extras (the zip member list is the whole truth).
Enums§
- Member
Status - One member’s diff status — B-relative-to-A semantics (the LOCKED
direction):
added= in B only,removed= in A only.
Functions§
- diff_
members - THE compare primitive: B-relative-to-A member statuses over two
export zips —
added= path in B not A,removed= in A not B,changed= both with differing normalized hashes,same= both with equal ones. Entries ride path-sorted (the BTreeMap union iterates sorted). The rootproject.jsonnever appears (themember_hasheswalk skips it). - member_
hashes - User path → FNV-1a digest for every resource member in the export
zip. Members whose basename is
resource.jsonhash their NORMALIZED form (normalize_descriptor); everything else hashes raw bytes.project.json, directory entries, and non-resources-shaped members carry no user path and are skipped (the same walkresource_membersrides) — which is exactly howdiff_membersexcludes the root project.json from resource entries. - normalize_
descriptor - Normalize one
resource.jsondescriptor for comparison: parse, strip the two live-evidenced volatility fields (attributes.lastModificationandattributes.lastModificationSignature— keep every other attribute and all semantic keys), recursively sort object keys, re-serialize compact.Nonefor non-JSON or a non-object root — the caller hashes the raw bytes instead (the descriptor is exotic or corrupt; content honesty over a false equality). - project_
meta_ delta - Compare the root
project.json’s SEMANTIC fields —title,enabled,parentonly — returning one(field, a_value, b_value)triple per differing field (stringified values; absent renders asnull). Missing member or parse failure → empty vec: the diff is about resources, project meta rides separately. - read_
member - THE read primitive: one member’s bytes, verbatim. A missing member is the existing not-found error shape (exit 6) — the REST family’s 404 semantics carried over the surgery transport.
- remove_
member - THE delete primitive: the zip minus the member. A missing member is the existing not-found error shape (exit 6).
- replace_
member - THE put primitive: replace the member’s content — or append it when absent (upsert: created if missing). Every other member, their order, and directory entries ride across untouched.
- resource_
members - THE list primitive: user-facing paths of every resource member in
the export zip, in member order.
project.json, directory entries, and non-resources-shaped members are skipped.