ocs_doc_api: Phase 3 attributes + nested block traversal

- SetAttribute op + GetAttributes/GetBlockEntities queries (append-only).
- backend set_attribute (add/update Insert attribute), attributes (tag,value pairs),
  block_entities (read-only traversal via BlockRecord.entity_handles -> id+kind+bounds).
- facade Entity::set_attribute/attributes, Document::block_entities.
- spec Insert methods; api_reference + binding_schema regenerated; mocks + baseline.
- Query enum: drop Copy (GetBlockEntities carries String).
- Test phase3_attributes_and_block_traversal -> 608 host lib + 3 consistency green.
This commit is contained in:
Sebastian committed 2026-09-06 10:02:23 +02:00
1 parent 2dba0158ca
commit 67655add72
15 files changed
+267 -14

No files matched your search

+20
View File
@@ -128,6 +128,14 @@ impl Document {
}
}
/// Read-only traversal of a block definition's entities (ids + kinds + bounds).
pub fn block_entities(&self, block_name: &str) -> ApiResult<Vec<EntityView>> {
match self.session.one_query(Query::GetBlockEntities { block_name: block_name.to_string() })? {
QueryResult::BlockEntities(v) => Ok(v),
_ => Err(ApiError::Transport("unexpected block-entities result".into())),
}
}
/// Batch of read-only queries in ONE round-trip (safe: no mutation/undo).
/// The closure records queries on a [`QueryBatch`]; the results are returned
/// in the same order as a [`QueryResults`] view the caller destructures.
@@ -289,6 +297,18 @@ impl Entity {
_ => Err(ApiError::Transport("unexpected entity result".into())),
}
}
/// This insert's attributes as (tag, value) pairs. Insert-only.
pub fn attributes(&self) -> ApiResult<Vec<(String, String)>> {
match self.session.one_query(Query::GetAttributes { id: self.id })? {
QueryResult::Attributes(v) => Ok(v),
_ => Err(ApiError::Transport("unexpected attributes result".into())),
}
}
/// Set an attribute `value` for `tag` on this insert (adds if absent). One undo step.
pub fn set_attribute(&self, tag: &str, value: &str) -> ApiResult<()> {
self.session.apply_op(Operation::SetAttribute { id: self.id, tag: tag.to_string(), value: value.to_string() })?;
Ok(())
}
pub fn as_solid(&self) -> Option<Solid> {
// Typed downcast is validated by the view's kind.
matches!(self.view().ok()?.kind.as_str(), "Solid3D").then(|| Solid::new(self.session.clone(), self.id))