Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 51 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions crates/op-canon/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,11 @@ serde_json.workspace = true
# `class_ids` so this port shares the single source of truth with
# `redmine-canon` — the constants cannot drift across ports.
ogar-vocab = { git = "https://github.com/AdaWorldAPI/OGAR", branch = "main" }
# ClassView impl over the canonical concepts — re-exported as
# `op_canon::class_view::OgarClassView`. Same one-source-of-truth
# pattern as `class_ids`: the projection bridge lives in OGAR; this port
# re-exports it. Northstar plan §3 C2 (sibling of redmine-rs#6).
ogar-class-view = { git = "https://github.com/AdaWorldAPI/OGAR", branch = "main" }
# Run-time ClassView trait + FieldMask + RenderRow. Re-exported so
# downstream `op-*` crates have one import for the projection contract.
lance-graph-contract = { git = "https://github.com/AdaWorldAPI/lance-graph", branch = "main" }
92 changes: 92 additions & 0 deletions crates/op-canon/src/class_view.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//! **Canonical ClassView projection** — re-exported from
//! [`ogar_class_view::OgarClassView`], the single source of truth.
//!
//! Pattern mirror of [`crate::class_ids`]: the bridge from
//! [`ogar_vocab::Class`] onto [`lance_graph_contract::ClassView`] lives
//! in OGAR; this port re-exports it so the projection contract is
//! shared with `redmine-canon`, and downstream `op-*` crates have one
//! import for the run-time projection layer.
//!
//! ```
//! use op_canon::class_view::{OgarClassView, ClassView, FieldMask};
//!
//! let view = OgarClassView::new();
//! let class_id = op_canon::class_ids::PROJECT_WORK_ITEM;
//! let mask = FieldMask::EMPTY.with(0).with(1);
//! let rows = view.render_rows(class_id, mask);
//! # let _ = rows;
//! ```
//!
//! Northstar plan §3, C2. The codebook is minted once in
//! [`AdaWorldAPI/OGAR`](https://github.com/AdaWorldAPI/OGAR); the
//! [`OgarClassView`] adapter that lifts every promoted concept onto the
//! `lance_graph_contract::ClassView` trait lives there too. This port
//! re-exports both, so an OpenProject consumer holding `op_canon`
//! reaches the projection trait + the impl + the constants through one
//! import path.

pub use lance_graph_contract::class_view::{
ClassId, ClassProjection, ClassView, FieldMask, RenderRow,
};
pub use lance_graph_contract::ontology::{DisplayTemplate, FieldRef, ObjectView};
pub use ogar_class_view::OgarClassView;

#[cfg(test)]
mod tests {
use super::*;
use crate::Snapshot;

#[test]
fn re_export_loads_the_canonical_class_view() {
// Sanity: the re-export pulled OgarClassView into scope and it
// initialises cleanly (no panic).
let view = OgarClassView::new();
for id in [
crate::class_ids::PROJECT_WORK_ITEM,
crate::class_ids::BILLABLE_WORK_ENTRY,
crate::class_ids::PROJECT_ROLE,
] {
let n = view.field_count(id);
assert!(
n > 0,
"expected non-empty field set for class id 0x{id:04X}"
);
}
}

#[test]
fn render_rows_skips_off_bits_through_the_re_export() {
// The five-line glue from the Northstar plan §2.3 — exercised
// through op-canon's re-export. Symmetric with redmine-canon's
// copy of this test (redmine-rs PR #6).
let view = OgarClassView::new();
let id = crate::class_ids::PROJECT_WORK_ITEM;

let empty = view.render_rows(id, FieldMask::EMPTY);
assert!(empty.is_empty());

let only_first = FieldMask::EMPTY.with(0);
let rows = view.render_rows(id, only_first);
assert_eq!(rows.len(), 1);
}

#[test]
fn snapshot_ids_resolve_through_the_re_exported_view() {
// End-to-end pin: every concept the OpenProject snapshot
// promotes resolves to a non-empty field set via the re-exported
// ClassView. Companion of the `snapshot_concepts_match_re_exported_constants`
// class_ids test.
let view = OgarClassView::new();
let s = Snapshot::load();
for c in &s.concepts {
let id = c.class_id_u16();
let n = view.field_count(id);
assert!(
n > 0,
"snapshot concept {} (id 0x{:04X}) has no ClassView fields",
c.canonical_concept,
id,
);
}
}
}
1 change: 1 addition & 0 deletions crates/op-canon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
#![warn(missing_docs)]

pub mod class_ids;
pub mod class_view;

use serde::Deserialize;

Expand Down