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}