Skip to content

Commit 1db31bc

Browse files
committed
ogar-render-askama: render ClassView x FieldMask -> struct + ActionDef methods (transpile-chain LEG 3)
The render end of the transpile chain. New render_class_with_methods(class, mask, actions): a compile-time (askama = ERB analog) transpiler emitting a Rust struct whose FIELDS are the ClassView x FieldMask projection (the bitmask indexes the ObjectView N3 order - attributes then family edges - the basis OgarClassView::render_rows uses) and whose METHODS are the OGAR ActionDef DO-arm, as a struct-of-methods constructor (impl { new(..) + one fn per ActionDef }). Operator rulings (2026-07-04): - Behaviour is Rust methods, NOT SurrealQL DDL. The deprecated SurrealQL-AST adapter (DEFINE EVENT ... WHEN ... THEN ...) is not a target; consistent with SURREAL-AST-AS-ADAPTER.md. Tests assert no DEFINE EVENT / DEFINE TABLE. - on_enter (the Rubicon state mutation) -> method takes &mut self; read actions take &self. New dep lance-graph-contract (FieldMask, branch=main). 6 new tests (mask gates fields; ActionDef->methods; dotted-predicate + PascalCase sanitisers); 50 tests green; workspace check clean; clippy -D warnings clean. End-to-end verified: masked account.move (mask={0,2}) -> struct AccountMove { name, state } + fn new(name, state) + fn action_post(&mut self), CLASS_ID=0x0202. Also fixes a pre-existing clippy::cloned_ref_to_slice_refs (1.95 toolchain) in the list_view test. Ledger: docs/DISCOVERY-MAP.md D-OGAR-RENDER-CLASSVIEW-FIELDMASK-METHODS.
1 parent ffacf21 commit 1db31bc

6 files changed

Lines changed: 456 additions & 5 deletions

File tree

crates/ogar-render-askama/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ description = "Build-time askama codegen harness over the canonical layer — on
77

88
[dependencies]
99
ogar-vocab = { path = "../ogar-vocab" }
10+
lance-graph-contract = { git = "https://github.com/AdaWorldAPI/lance-graph", branch = "main" }
1011
askama = "0.12"

crates/ogar-render-askama/src/artifact_kinds/rust_struct.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ impl ArtifactEmitter for RustStructEmitter {
116116
/// 2024 strict + reserved-future); a `&str` slot from the canonical layer
117117
/// can never need a non-identifier escape since names are sourced from
118118
/// Rails / Odoo identifiers.
119-
fn escape_rust_ident(name: &str) -> String {
119+
pub(crate) fn escape_rust_ident(name: &str) -> String {
120120
const RESERVED: &[&str] = &[
121121
// Rust 2015+ strict keywords:
122122
"as", "break", "const", "continue", "crate", "else", "enum", "extern",
@@ -142,7 +142,7 @@ fn escape_rust_ident(name: &str) -> String {
142142
/// today. Each `op-*` / `rm-*` consumer is free to specialise (e.g.
143143
/// `Decimal` vs `f64` for monetary slots) downstream. The point is the
144144
/// canonical contract round-trips; precision is a per-consumer concern.
145-
fn rails_to_rust_type(t: Option<&str>) -> String {
145+
pub(crate) fn rails_to_rust_type(t: Option<&str>) -> String {
146146
match t {
147147
Some("string") | Some("text") => "String".into(),
148148
Some("integer") | Some("big_integer") | Some("bigint") => "i64".into(),
@@ -155,7 +155,7 @@ fn rails_to_rust_type(t: Option<&str>) -> String {
155155
}
156156
}
157157

158-
fn edge_rust_type(a: &ogar_vocab::Association) -> String {
158+
pub(crate) fn edge_rust_type(a: &ogar_vocab::Association) -> String {
159159
// Coarse: `belongs_to` / `has_one` → `Option<u64>` (FK id),
160160
// `has_many` / `habtm` → `Vec<u64>`. The concrete `op-*` / `rm-*`
161161
// consumer can swap these for typed references downstream.

crates/ogar-render-askama/src/lib.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
pub mod artifact_kinds;
7272
pub mod form_view;
7373
pub mod list_view;
74+
pub mod rust_class;
7475
pub mod spec;
7576

7677
pub use artifact_kinds::{
@@ -80,6 +81,7 @@ pub use artifact_kinds::{
8081
};
8182
pub use form_view::{default_input_kind_for, InputKind};
8283
pub use list_view::{default_kind_for, ColumnKind, RenderColumn, SortOrder};
84+
pub use rust_class::render_class_with_methods;
8385
pub use spec::{ArtifactKind, ArtifactSpec};
8486

8587
use ogar_vocab::Class;
@@ -329,8 +331,15 @@ mod tests {
329331
}],
330332
block: vec![],
331333
};
332-
let src = render_list("By status", 0x0102, "project_work_item", &[col.clone()], &[], &[row])
333-
.unwrap();
334+
let src = render_list(
335+
"By status",
336+
0x0102,
337+
"project_work_item",
338+
std::slice::from_ref(&col),
339+
&[],
340+
&[row],
341+
)
342+
.unwrap();
334343
assert!(src.contains("class=\"group open\""), "{src}");
335344
assert!(src.contains("<span class=\"name\">Open</span>"), "{src}");
336345
assert!(src.contains("<span class=\"badge\">5</span>"), "{src}");

0 commit comments

Comments
 (0)