Add ocs_doc_api: object-oriented CAD entity API (v2)

New crate crates/ocs_doc_api: object-centric facade (DocApi -> Document ->
collections -> typed handles), transport-agnostic wire layer (DocApiEnvelope =
single op | read-only query batch), per-op autocommit (no write transactions),
bulk ops with upfront-validation all-or-nothing, structured ApiError, and
build-time codegen (spec/entities.toml -> ops/query enums, api_reference.md,
binding_schema.json).

ocs_plugin_api: append-only PluginRequest::DocApiRequest / PluginResponse::
DocApiResponse (serde_bytes payload) + defaulted HostApi::doc_api_dispatch.

Host: DocApiBackend impl for HostSession (src/app/doc_api.rs) mapping to scene
primitives (solid_models lift-on-miss, register_prepared_solid_model, meshes
metrics), entity conversions (doc_api_convert.rs), HostSession accessors +
doc_api_dispatch override.

Tests: 592 host lib (incl. 7 DocApi integration tests: create/boolean/query,
unknown-id, transform-many all-or-nothing, query-batch cap, 2D/geometric/DWG
roundtrips), 8 crate unit tests. Examples + README/ARCHITECTURE/IMPLEMENTATION docs.
This commit is contained in:
Sebastian committed 2026-09-06 00:15:27 +02:00
1 parent 9129c85f97
commit e41086b058
44 files changed
+4837 -12

No files matched your search

+12
View File
@@ -552,6 +552,18 @@ pub trait HostApi {
let _ = tab_id;
None
}
/// DocApi v2 (`ocs_doc_api`): dispatch one bincode-serialized `DocApiEnvelope`
/// (one write op OR a read-only query batch) and return the bincode-serialized
/// `Receipt` (or the bincode-serialized `ApiError` on failure). Opaque bytes:
/// this crate does not depend on `ocs_doc_api`, it only routes the envelope.
///
/// Added for the DocApi v2 adapter. The default returns `Err` ("not supported");
/// the in-process host (`HostSession`) overrides it to call the DocApi executor.
fn doc_api_dispatch(&mut self, tab_id: u64, bytes: &[u8]) -> Result<Vec<u8>, String> {
let _ = (tab_id, bytes);
Err("DocApi v2 not supported by this host".to_string())
}
}
/// Simplified, read-only entity kind exposed by [`DocumentReader`].
+10
View File
@@ -138,6 +138,12 @@ pub enum PluginRequest {
GetTabId,
/// V5: ask the host for the filesystem path of the document in `tab_id`.
DocumentPath { tab_id: u64 },
/// DocApi v2 (`ocs_doc_api`): carry one bincode-serialized `DocApiEnvelope`
/// (one write op OR a read-only query batch). Appended at the end to keep
/// existing discriminants stable. The host routes this to the DocApi executor.
/// `serde_bytes` keeps the payload a single length-prefixed bulk copy (the
/// outer frame would otherwise walk it byte-by-byte).
DocApiRequest { tab_id: u64, #[serde(with = "serde_bytes")] bytes: Vec<u8> },
}
/// Responses the host sends back for `PluginRequest`.
@@ -164,6 +170,10 @@ pub enum PluginResponse {
TabId(u64),
/// V5: filesystem path of the document in the requested tab, if any.
DocumentPath(Option<std::ffi::OsString>),
/// DocApi v2 (`ocs_doc_api`): the bincode-serialized `Receipt` answering a
/// `PluginRequest::DocApiRequest`. Appended at the end (discriminant stability).
/// `serde_bytes` keeps the payload a single length-prefixed bulk copy.
DocApiResponse { #[serde(with = "serde_bytes")] bytes: Vec<u8> },
}
/// Messages sent from the host to the plugin runner.
+4
View File
@@ -77,5 +77,9 @@ pub fn handle_plugin_request(
DocumentPath { tab_id } => PluginResponse::DocumentPath(
host.document_path(tab_id).map(|path| path.into_os_string()),
),
DocApiRequest { tab_id, bytes } => match host.doc_api_dispatch(tab_id, &bytes) {
Ok(bytes) => PluginResponse::DocApiResponse { bytes },
Err(msg) => PluginResponse::Error(msg),
},
}
}