Skip to main content

type_bridge_toml_transpiler/
lib.rs

1//! TOML schema DSL transpiler for type-bridge.
2//!
3//! Converts a TOML schema document into a canonical TypeQL `define` block.
4//! This crate has no PyO3, no `type-bridge-*` dependencies, and no runtime
5//! dependency beyond `toml`, `serde`, `indexmap`, and `thiserror`.
6
7#![deny(missing_docs)]
8
9#[cfg(doctest)]
10#[doc = include_str!("../README.md")]
11pub mod readme_doctests {}
12
13mod emit;
14mod model;
15mod validate;
16
17use std::fmt;
18
19use thiserror::Error;
20
21/// Kind of schema type involved in a dangling-sub or missing-role diagnostic.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum TypeKind {
24    /// An attribute type.
25    Attribute,
26    /// An entity type.
27    Entity,
28    /// A relation type.
29    Relation,
30}
31
32impl fmt::Display for TypeKind {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        match self {
35            TypeKind::Attribute => write!(f, "attribute"),
36            TypeKind::Entity => write!(f, "entity"),
37            TypeKind::Relation => write!(f, "relation"),
38        }
39    }
40}
41
42/// Errors that can occur during TOML-to-TypeQL transpilation.
43#[derive(Debug, Error)]
44pub enum TranspileError {
45    /// The TOML document could not be parsed or the schema structure did not
46    /// match the expected shape (e.g. unknown key, missing required field).
47    #[error("TOML parse or deserialisation error: {0}")]
48    Toml(#[from] toml::de::Error),
49
50    /// An attribute declares a `value` type that is not one of the 12 known
51    /// TypeDB value type keywords.
52    #[error(
53        "attribute `{type_name}` has unknown value type `{value}` \
54         (expected one of: datetime-tz, datetime, boolean, decimal, duration, \
55         integer, string, double, date, long, bool, int)"
56    )]
57    UnknownValueType {
58        /// The attribute type containing the invalid value declaration.
59        type_name: String,
60        /// The unsupported value-type spelling.
61        value: String,
62    },
63
64    /// A struct field declares a `type` that is not one of the 12 known
65    /// TypeDB value type keywords.
66    #[error(
67        "struct `{struct_name}` field `{field}` has unknown value type `{value}` \
68         (expected one of: datetime-tz, datetime, boolean, decimal, duration, \
69         integer, string, double, date, long, bool, int)"
70    )]
71    UnknownStructFieldType {
72        /// The struct containing the invalid field declaration.
73        struct_name: String,
74        /// The field whose value type is invalid.
75        field: String,
76        /// The unsupported value-type spelling.
77        value: String,
78    },
79
80    /// An attribute sets both `value` and `sub`; exactly one must be present.
81    #[error("attribute `{attr}` sets both `value` and `sub`; specify exactly one")]
82    AttributeValueSubConflict {
83        /// The attribute that declares both `value` and `sub`.
84        attr: String,
85    },
86
87    /// An attribute sets neither `value` nor `sub`; exactly one must be present.
88    #[error("attribute `{attr}` sets neither `value` nor `sub`; specify exactly one")]
89    AttributeMissingValueSub {
90        /// The attribute missing both `value` and `sub`.
91        attr: String,
92    },
93
94    /// A type declares `sub = "<parent>"` but the parent is not defined in the
95    /// same section of the schema.
96    #[error(
97        "{kind} `{type_name}` declares `sub = \"{parent}\"` but `{parent}` \
98         is not defined in the schema"
99    )]
100    DanglingSubParent {
101        /// The kind of schema type containing the invalid `sub` declaration.
102        kind: TypeKind,
103        /// The type whose parent cannot be resolved.
104        type_name: String,
105        /// The unresolved parent type name.
106        parent: String,
107    },
108
109    /// An entity or relation `plays` entry references a relation that is not
110    /// defined in the schema.
111    #[error(
112        "`{player}` plays `{relation}:{role}` but relation `{relation}` \
113         is not defined in the schema"
114    )]
115    MissingRoleRelation {
116        /// The entity or relation that declares the `plays` capability.
117        player: String,
118        /// The unresolved relation type name.
119        relation: String,
120        /// The role name qualified by the missing relation.
121        role: String,
122    },
123
124    /// An entity or relation `plays` entry references a role name that is not
125    /// declared on the specified relation.
126    #[error(
127        "`{player}` plays `{relation}:{role}` but relation `{relation}` \
128         has no role named `{role}`"
129    )]
130    MissingRole {
131        /// The entity or relation that declares the `plays` capability.
132        player: String,
133        /// The resolved relation type name.
134        relation: String,
135        /// The role not declared by the relation.
136        role: String,
137    },
138
139    /// A struct declares no fields; structs require at least one field.
140    #[error("struct `{struct_name}` declares no fields; structs require at least one")]
141    EmptyStruct {
142        /// The struct that declares no fields.
143        struct_name: String,
144    },
145
146    /// A function body has no `return` clause; a function body must reach
147    /// `return ...;`.
148    #[error(
149        "function `{function}` body has no `return` clause; \
150         a function body must reach `return ...;`"
151    )]
152    MalformedFunctionBody {
153        /// The function whose body has no return clause.
154        function: String,
155    },
156
157    /// A role or owns clause has `distinct = true` but `ordered = false`.
158    /// `@distinct` is only valid on a list form (`relates name[]` / `owns attr[]`).
159    #[error(
160        "{kind} `{type_name}` has `distinct = true` on `{item}` without `ordered = true`; \
161         @distinct requires the list form"
162    )]
163    DistinctWithoutOrdered {
164        /// The kind of schema type containing the capability.
165        kind: TypeKind,
166        /// The type that owns or relates the invalid item.
167        type_name: String,
168        /// The role or owned attribute marked distinct without list ordering.
169        item: String,
170    },
171}
172
173/// Parse `toml_text` as a type-bridge schema document and emit a canonical
174/// TypeQL `define` block.
175///
176/// # Errors
177///
178/// Returns [`TranspileError::Toml`] when the input is not valid TOML or does
179/// not conform to the schema model (unknown keys, missing required fields).
180/// Returns a semantic [`TranspileError`] variant when the schema is structurally
181/// valid TOML but contains a semantic error (unknown value type, dangling sub
182/// parent, missing role, empty struct, malformed function body, etc.).
183pub fn toml_to_typeql(toml_text: &str) -> Result<String, TranspileError> {
184    let schema: model::TomlSchema = toml::from_str(toml_text)?;
185    validate::validate(&schema)?;
186    Ok(emit::emit(&schema))
187}