Skip to main content

omnigraph_compiler/schema/
ast.rs

1use std::collections::BTreeMap;
2
3use crate::types::PropType;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7pub struct SchemaFile {
8    pub declarations: Vec<SchemaDecl>,
9}
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub enum SchemaDecl {
13    Interface(InterfaceDecl),
14    Node(NodeDecl),
15    Edge(EdgeDecl),
16}
17
18#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
19pub struct InterfaceDecl {
20    pub name: String,
21    pub properties: Vec<PropDecl>,
22}
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
25pub struct NodeDecl {
26    pub name: String,
27    pub annotations: Vec<Annotation>,
28    pub implements: Vec<String>,
29    pub properties: Vec<PropDecl>,
30    pub constraints: Vec<Constraint>,
31}
32
33#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
34pub struct EdgeDecl {
35    pub name: String,
36    pub from_type: String,
37    pub to_type: String,
38    pub cardinality: Cardinality,
39    pub annotations: Vec<Annotation>,
40    pub properties: Vec<PropDecl>,
41    pub constraints: Vec<Constraint>,
42}
43
44#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
45pub struct PropDecl {
46    pub name: String,
47    pub prop_type: PropType,
48    pub annotations: Vec<Annotation>,
49}
50
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
52pub struct Annotation {
53    pub name: String,
54    pub value: Option<String>,
55    /// Keyword arguments, e.g. `model="…"` on `@embed("source", model="…")`.
56    /// Empty is skipped in serialization so existing schemas' IR JSON (and
57    /// hash) stay byte-identical; `BTreeMap` keeps the order deterministic.
58    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
59    pub kwargs: BTreeMap<String, String>,
60}
61
62/// A typed constraint declared in a node or edge body.
63///
64/// Property-level annotations (`@key`, `@unique`, `@index`) are desugared
65/// into these during parsing, so both syntactic positions produce the same
66/// representation.
67#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
68pub enum Constraint {
69    Key(Vec<String>),
70    Unique(Vec<String>),
71    Index(Vec<String>),
72    Range {
73        property: String,
74        min: Option<ConstraintBound>,
75        max: Option<ConstraintBound>,
76    },
77    Check {
78        property: String,
79        pattern: String,
80    },
81}
82
83/// A numeric bound used in `@range` constraints.
84#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
85pub enum ConstraintBound {
86    Integer(i64),
87    Float(f64),
88}
89
90/// Edge cardinality: `@card(min..max)`. Default is `0..*`.
91#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
92pub struct Cardinality {
93    pub min: u32,
94    pub max: Option<u32>,
95}
96
97impl Default for Cardinality {
98    fn default() -> Self {
99        Self { min: 0, max: None }
100    }
101}
102
103impl Cardinality {
104    pub fn is_default(&self) -> bool {
105        self.min == 0 && self.max.is_none()
106    }
107}
108
109pub fn has_annotation(annotations: &[Annotation], name: &str) -> bool {
110    annotations.iter().any(|ann| ann.name == name)
111}
112
113pub fn annotation_value<'a>(annotations: &'a [Annotation], name: &str) -> Option<&'a str> {
114    annotations
115        .iter()
116        .find(|ann| ann.name == name)
117        .and_then(|ann| ann.value.as_deref())
118}