Skip to main content

skilj_codegen/
spec.rs

1//! The declarative shape a `.skilj.toml` file describes - one bounded
2//! context's event/command *shapes* only (fields, DCB tags,
3//! `rest_trigger_allowed`). Deliberately narrow (Codeberg issue #5's
4//! own "narrower cut" - see this crate's own root doc comment and
5//! docs/architecture.md §17): `decide()` bodies, `sensitive_fields`,
6//! scheduling, `#[requires_role]`, and every `Projection` concept are
7//! all real, legitimate parts of the plugin API this format doesn't
8//! cover yet - named here as deliberately deferred, not silently
9//! missing, matching this project's own "don't build ahead of what's
10//! wired" convention. Every struct below carries `#[serde(deny_unknown_fields)]`
11//! so that promise holds on the input side too: a `.skilj.toml` naming
12//! one of those deferred fields (or a typo of a covered one, e.g.
13//! `taggs`) is a real `toml::de::Error` at `generate()` time, not a
14//! silently-ignored key.
15//!
16//! `fields` is an array of `{name, type}` tables, not a TOML map -
17//! deliberately, so field order in the generated struct matches the
18//! order written in the `.skilj.toml` file. A TOML inline/dotted table
19//! (`{ account_id = "string" }`) has no defined ordering once
20//! deserialised into a plain map; an array does.
21
22use serde::Deserialize;
23use std::collections::BTreeMap;
24
25#[derive(Debug, Deserialize)]
26#[serde(deny_unknown_fields)]
27pub struct BoundedContextSpec {
28    pub bounded_context: String,
29    /// `Vec` field name stays plural (idiomatic Rust); the TOML key is
30    /// singular (`[[event_type]]`) since each array-of-tables block
31    /// declares exactly one event type - the same convention `Cargo.toml`
32    /// itself uses (`[[bin]]`/`[[test]]`, not `[[bins]]`/`[[tests]]`).
33    #[serde(default, rename = "event_type")]
34    pub event_types: Vec<EventTypeSpec>,
35    #[serde(default, rename = "command_type")]
36    pub command_types: Vec<CommandTypeSpec>,
37}
38
39#[derive(Debug, Deserialize)]
40#[serde(deny_unknown_fields)]
41pub struct EventTypeSpec {
42    pub name: String,
43    #[serde(default)]
44    pub fields: Vec<FieldSpec>,
45    /// `key -> field` - `TagMapping`'s own two-part shape. A `BTreeMap`,
46    /// not an ordered map: unlike `fields`, tag order has no effect on
47    /// `matching_events`' own union semantics (skilj-tui's own
48    /// `references/dcb-tags.md` in the `skilj-event-modeling` skill has
49    /// the full explanation of what a tag actually does) - alphabetical
50    /// is simply the cheapest deterministic order to emit.
51    #[serde(default)]
52    pub tags: BTreeMap<String, String>,
53}
54
55#[derive(Debug, Deserialize)]
56#[serde(deny_unknown_fields)]
57pub struct CommandTypeSpec {
58    pub name: String,
59    #[serde(default)]
60    pub fields: Vec<FieldSpec>,
61    #[serde(default)]
62    pub tags: BTreeMap<String, String>,
63    #[serde(default)]
64    pub rest_trigger_allowed: bool,
65}
66
67#[derive(Debug, Deserialize)]
68#[serde(deny_unknown_fields)]
69pub struct FieldSpec {
70    pub name: String,
71    #[serde(rename = "type")]
72    pub ty: FieldType,
73}
74
75/// The scalar leaf shapes the plugin API's own schema rules already
76/// require for a `tag_mappings`/`sensitive_fields` target (see the
77/// `skilj` skill's own `references/event-type.md`) - not a general type
78/// system. A payload field this format can't express (a nested object,
79/// an enum, a list) stays a reason to hand-write that one type instead
80/// of describing it here, not a gap to widen this enum for casually.
81#[derive(Debug, Deserialize, Clone, Copy, PartialEq, Eq)]
82#[serde(rename_all = "lowercase")]
83pub enum FieldType {
84    String,
85    I64,
86    Bool,
87}