1use serde::{Deserialize, Serialize};
34use std::collections::HashMap;
35
36#[cfg(feature = "json-schema")]
37use schemars::JsonSchema;
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
45#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
46pub struct ImportDecl {
47 pub specifier: ImportSpecifier,
48 pub from_module: String,
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize)]
52#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
53#[serde(tag = "type")]
54pub enum ImportSpecifier {
55 Named { items: Vec<ImportItem> },
56 Wildcard { alias: String },
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize)]
60#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
61pub struct ImportItem {
62 pub name: String,
63 #[serde(skip_serializing_if = "Option::is_none")]
64 pub alias: Option<String>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize, Default)]
69#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
70pub struct FileMetadata {
71 #[serde(skip_serializing_if = "Option::is_none")]
72 pub namespace: Option<String>,
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub version: Option<String>,
75 #[serde(skip_serializing_if = "Option::is_none")]
76 pub owner: Option<String>,
77 #[serde(skip_serializing_if = "Option::is_none")]
78 pub profile: Option<String>,
79 #[serde(default, skip_serializing_if = "Vec::is_empty")]
80 pub imports: Vec<ImportDecl>,
81}
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
88#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
89pub enum PolicyKind {
90 Constraint,
91 Derivation,
92 Obligation,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
97pub enum PolicyModality {
98 Obligation,
99 Prohibition,
100 Permission,
101}
102
103#[derive(Debug, Clone, Serialize, Deserialize)]
104#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
105pub struct PolicyMetadata {
106 #[serde(skip_serializing_if = "Option::is_none")]
107 pub kind: Option<PolicyKind>,
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub modality: Option<PolicyModality>,
110 #[serde(skip_serializing_if = "Option::is_none")]
111 pub priority: Option<i32>,
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub rationale: Option<String>,
114 #[serde(default, skip_serializing_if = "Vec::is_empty")]
115 pub tags: Vec<String>,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
123#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
124pub enum Severity {
125 Info,
126 Warning,
127 Error,
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize)]
131#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
132pub struct MetricMetadata {
133 #[serde(skip_serializing_if = "Option::is_none")]
135 pub refresh_interval: Option<String>,
136 #[serde(skip_serializing_if = "Option::is_none")]
137 pub unit: Option<String>,
138 #[serde(skip_serializing_if = "Option::is_none")]
140 pub threshold: Option<String>,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub severity: Option<Severity>,
143 #[serde(skip_serializing_if = "Option::is_none")]
144 pub target: Option<String>,
145 #[serde(skip_serializing_if = "Option::is_none")]
146 pub window: Option<String>,
147}
148
149#[derive(Debug, Clone, Serialize, Deserialize)]
154#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
155pub enum TargetFormat {
156 Calm,
157 Kg,
158 Sbvr,
159 Protobuf,
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
163#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
164pub struct MappingRule {
165 pub primitive_type: String,
166 pub primitive_name: String,
167 pub target_type: String,
168 #[serde(default)]
169 pub fields: HashMap<String, serde_json::Value>,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
173#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
174pub struct ProjectionOverride {
175 pub primitive_type: String,
176 pub primitive_name: String,
177 #[serde(default)]
178 pub fields: HashMap<String, serde_json::Value>,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
186#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
187pub struct WindowSpec {
188 pub duration: u64,
189 pub unit: String,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
193#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
194pub enum BinaryOp {
195 And,
196 Or,
197 Equal,
198 NotEqual,
199 GreaterThan,
200 LessThan,
201 GreaterThanOrEqual,
202 LessThanOrEqual,
203 Plus,
204 Minus,
205 Multiply,
206 Divide,
207 Contains,
208 StartsWith,
209 EndsWith,
210 Matches,
211 HasRole,
212 Before,
213 After,
214 During,
215}
216
217#[derive(Debug, Clone, Serialize, Deserialize)]
218#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
219pub enum UnaryOp {
220 Not,
221 Negate,
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
225#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
226pub enum Quantifier {
227 ForAll,
228 Exists,
229 ExistsUnique,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize)]
233#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
234pub enum AggregateFunction {
235 Count,
236 Sum,
237 Min,
238 Max,
239 Avg,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
243#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
244#[serde(tag = "type")]
245pub enum Expression {
246 Literal {
247 value: serde_json::Value,
248 },
249 QuantityLiteral {
250 value: String,
252 unit: String,
253 },
254 TimeLiteral {
255 timestamp: String,
256 },
257 IntervalLiteral {
258 start: String,
259 end: String,
260 },
261 Variable {
262 name: String,
263 },
264 GroupBy {
265 variable: String,
266 collection: Box<Expression>,
267 #[serde(skip_serializing_if = "Option::is_none")]
268 filter: Option<Box<Expression>>,
269 key: Box<Expression>,
270 condition: Box<Expression>,
271 },
272 Binary {
273 op: BinaryOp,
274 left: Box<Expression>,
275 right: Box<Expression>,
276 },
277 Unary {
278 op: UnaryOp,
279 operand: Box<Expression>,
280 },
281 Cast {
282 operand: Box<Expression>,
283 target_type: String,
284 },
285 Quantifier {
286 quantifier: Quantifier,
287 variable: String,
288 collection: Box<Expression>,
289 condition: Box<Expression>,
290 },
291 MemberAccess {
292 object: String,
293 member: String,
294 },
295 Aggregation {
296 function: AggregateFunction,
297 collection: Box<Expression>,
298 #[serde(skip_serializing_if = "Option::is_none")]
299 field: Option<String>,
300 #[serde(skip_serializing_if = "Option::is_none")]
301 filter: Option<Box<Expression>>,
302 },
303 AggregationComprehension {
304 function: AggregateFunction,
305 variable: String,
306 collection: Box<Expression>,
307 #[serde(skip_serializing_if = "Option::is_none")]
308 window: Option<WindowSpec>,
309 predicate: Box<Expression>,
310 projection: Box<Expression>,
311 #[serde(skip_serializing_if = "Option::is_none")]
312 target_unit: Option<String>,
313 },
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize)]
322#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
323pub struct SpannedAstNode {
324 pub node: AstNode,
325 pub line: usize,
327 pub column: usize,
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
333#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
334#[serde(tag = "type")]
335pub enum AstNode {
336 Export { declaration: Box<SpannedAstNode> },
338
339 Entity {
341 name: String,
342 #[serde(skip_serializing_if = "Option::is_none")]
343 version: Option<String>,
344 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
345 annotations: HashMap<String, serde_json::Value>,
346 #[serde(skip_serializing_if = "Option::is_none")]
347 domain: Option<String>,
348 },
349
350 Resource {
352 name: String,
353 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
354 annotations: HashMap<String, serde_json::Value>,
355 #[serde(skip_serializing_if = "Option::is_none")]
356 unit_name: Option<String>,
357 #[serde(skip_serializing_if = "Option::is_none")]
358 domain: Option<String>,
359 },
360
361 Flow {
363 resource_name: String,
364 #[serde(default, skip_serializing_if = "HashMap::is_empty")]
365 annotations: HashMap<String, serde_json::Value>,
366 from_entity: String,
367 to_entity: String,
368 #[serde(skip_serializing_if = "Option::is_none")]
369 quantity: Option<i32>,
370 },
371
372 Pattern { name: String, regex: String },
374
375 Role {
377 name: String,
378 #[serde(skip_serializing_if = "Option::is_none")]
379 domain: Option<String>,
380 },
381
382 Relation {
384 name: String,
385 subject_role: String,
386 predicate: String,
387 object_role: String,
388 #[serde(skip_serializing_if = "Option::is_none")]
389 via_flow: Option<String>,
390 },
391
392 Dimension { name: String },
394
395 UnitDeclaration {
397 symbol: String,
398 dimension: String,
399 factor: String,
401 base_unit: String,
402 },
403
404 Policy {
406 name: String,
407 #[serde(skip_serializing_if = "Option::is_none")]
408 version: Option<String>,
409 metadata: PolicyMetadata,
410 expression: Expression,
411 },
412
413 Instance {
415 name: String,
416 entity_type: String,
417 #[serde(default)]
418 fields: HashMap<String, Expression>,
419 },
420
421 ConceptChange {
423 name: String,
424 from_version: String,
425 to_version: String,
426 migration_policy: String,
427 breaking_change: bool,
428 },
429
430 Metric {
432 name: String,
433 expression: Expression,
434 metadata: MetricMetadata,
435 },
436
437 MappingDecl {
439 name: String,
440 target: TargetFormat,
441 rules: Vec<MappingRule>,
442 },
443
444 ProjectionDecl {
446 name: String,
447 target: TargetFormat,
448 overrides: Vec<ProjectionOverride>,
449 },
450}
451
452#[derive(Debug, Clone, Serialize, Deserialize)]
458#[cfg_attr(feature = "json-schema", derive(JsonSchema))]
459pub struct Ast {
460 pub metadata: FileMetadata,
461 pub declarations: Vec<SpannedAstNode>,
462}
463
464#[cfg(all(test, feature = "json-schema"))]
469mod tests {
470 use super::*;
471 use schemars::schema_for;
472
473 #[test]
474 #[ignore] fn generate_ast_schema() {
476 let schema = schema_for!(Ast);
477 let json = serde_json::to_string_pretty(&schema).expect("Failed to serialize schema");
478
479 let schema_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
481 .parent()
482 .unwrap()
483 .join("schemas")
484 .join("ast-v3.schema.json");
485
486 std::fs::write(&schema_path, &json).expect("Failed to write schema file");
487
488 println!("Schema written to: {}", schema_path.display());
489 println!("\n{}", json);
490 }
491}