Skip to main content

hidane_rules/
ast.rs

1//! The compiled ruleset: what the evaluator walks.
2//!
3//! Every node keeps its [`Span`]. A ruleset only exists when its file compiled, so nothing in
4//! it is an error placeholder.
5
6use std::collections::HashMap;
7
8/// Where a node is: byte offsets into its file (end exclusive) and the line and column
9/// (1-based, in code points) of its start, as the official emulator prints them
10/// (`evaluation error at L5:22`).
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
12pub struct Span {
13    pub start: usize,
14    pub end: usize,
15    pub line: u32,
16    pub column: u32,
17}
18
19/// `rules_version`: `'1'` (or none) and `'2'` differ in how recursive wildcards match.
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Version {
22    V1,
23    V2,
24}
25
26#[derive(Debug, Clone, PartialEq)]
27pub struct Ruleset {
28    pub version: Version,
29    /// The name of the file it was compiled from, as the request gave it.
30    pub file_name: Option<String>,
31    /// Functions declared at the top level, outside any service.
32    pub functions: Vec<Function>,
33    pub services: Vec<Service>,
34    /// The opening parentheses around each parenthesized expression, outermost first, by the
35    /// expression's byte range. The AST leaves parentheses out; the coverage report and the
36    /// positions of evaluation errors count an expression from its parentheses.
37    pub parens: HashMap<(usize, usize), Vec<Span>>,
38}
39
40impl Ruleset {
41    /// The service named `name` (`cloud.firestore`), if declared.
42    pub fn service(&self, name: &str) -> Option<&Service> {
43        self.services.iter().find(|s| s.name == name)
44    }
45}
46
47#[derive(Debug, Clone, PartialEq)]
48pub struct Service {
49    /// The dotted name, `cloud.firestore`.
50    pub name: String,
51    pub name_span: Span,
52    pub body: Body,
53    pub span: Span,
54}
55
56/// What a service or a `match` contains, each kind in source order.
57#[derive(Debug, Clone, PartialEq, Default)]
58pub struct Body {
59    pub functions: Vec<Function>,
60    pub matches: Vec<Match>,
61    pub allows: Vec<Allow>,
62}
63
64/// `match /path/{var}/{rest=**} { … }`.
65#[derive(Debug, Clone, PartialEq)]
66pub struct Match {
67    pub path: Vec<Segment>,
68    pub body: Body,
69    /// The `match` keyword.
70    pub span: Span,
71}
72
73#[derive(Debug, Clone, PartialEq)]
74pub struct Segment {
75    pub kind: SegmentKind,
76    /// From the slash to the end of the segment.
77    pub span: Span,
78    /// The variable's name, or the whole segment for a literal.
79    pub name_span: Span,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
83pub enum SegmentKind {
84    /// `/users`.
85    Literal(String),
86    /// `/{user}` or `/{user=*}`: one segment.
87    Wildcard(String),
88    /// `/{rest=**}`: any number of segments.
89    Recursive(String),
90}
91
92impl SegmentKind {
93    /// The variable the segment binds, if any.
94    pub fn variable(&self) -> Option<&str> {
95        match self {
96            Self::Literal(_) => None,
97            Self::Wildcard(name) | Self::Recursive(name) => Some(name),
98        }
99    }
100}
101
102/// `allow read, write: if condition;`.
103#[derive(Debug, Clone, PartialEq)]
104pub struct Allow {
105    pub methods: Vec<MethodName>,
106    /// `None` for `allow read;`, which always allows.
107    pub condition: Option<Expr>,
108    /// The `allow` keyword.
109    pub span: Span,
110}
111
112#[derive(Debug, Clone, PartialEq)]
113pub struct MethodName {
114    pub name: String,
115    /// `None` when the name is not a method (the compiler warns about it).
116    pub method: Option<Method>,
117    pub span: Span,
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121pub enum Method {
122    Read,
123    Write,
124    Get,
125    List,
126    Create,
127    Update,
128    Delete,
129}
130
131impl Method {
132    pub fn from_name(name: &str) -> Option<Self> {
133        Some(match name {
134            "read" => Self::Read,
135            "write" => Self::Write,
136            "get" => Self::Get,
137            "list" => Self::List,
138            "create" => Self::Create,
139            "update" => Self::Update,
140            "delete" => Self::Delete,
141            _ => return None,
142        })
143    }
144
145    /// Whether `self` covers the request method `request` (`read` covers `get` and `list`).
146    pub fn covers(self, request: Self) -> bool {
147        self == request
148            || matches!(
149                (self, request),
150                (Self::Read, Self::Get | Self::List)
151                    | (Self::Write, Self::Create | Self::Update | Self::Delete)
152            )
153    }
154}
155
156/// `function name(params) { let x = …; return expr; }`.
157#[derive(Debug, Clone, PartialEq)]
158pub struct Function {
159    pub name: Ident,
160    pub params: Vec<Ident>,
161    pub lets: Vec<Let>,
162    pub result: Expr,
163    /// The `function` keyword.
164    pub span: Span,
165}
166
167#[derive(Debug, Clone, PartialEq)]
168pub struct Let {
169    pub name: Ident,
170    pub value: Expr,
171    /// The `let` keyword.
172    pub span: Span,
173}
174
175#[derive(Debug, Clone, PartialEq, Eq)]
176pub struct Ident {
177    pub name: String,
178    pub span: Span,
179}
180
181#[derive(Debug, Clone, PartialEq)]
182pub struct Expr {
183    pub kind: ExprKind,
184    /// From the first token of the expression to the end of its last.
185    pub span: Span,
186}
187
188#[derive(Debug, Clone, PartialEq)]
189pub enum ExprKind {
190    Null,
191    Bool(bool),
192    Int(i64),
193    /// An integer literal beyond `i64`: it compiles, and fails when evaluated.
194    IntOverflow(String),
195    Float(f64),
196    String(String),
197    Bytes(Vec<u8>),
198    /// A variable: `request`, a wildcard, a parameter or a `let`.
199    Ident(String),
200    List(Vec<Expr>),
201    /// `{k: v, …}`, in source order.
202    Map(Vec<(Expr, Expr)>),
203    /// A path literal: `/databases/$(database)/documents`.
204    Path(Vec<PathPart>),
205    /// `object.field`.
206    Member {
207        object: Box<Expr>,
208        field: Ident,
209    },
210    /// `object[index]`.
211    Index {
212        object: Box<Expr>,
213        index: Box<Expr>,
214    },
215    /// `object[start:end]`.
216    Slice {
217        object: Box<Expr>,
218        start: Box<Expr>,
219        end: Box<Expr>,
220    },
221    /// `f(args)`, `math.abs(args)` or `receiver.f(args)`.
222    Call {
223        callee: Callee,
224        args: Vec<Expr>,
225    },
226    Unary {
227        op: UnaryOp,
228        operand: Box<Expr>,
229    },
230    Binary {
231        op: BinaryOp,
232        /// The operator token.
233        op_span: Span,
234        left: Box<Expr>,
235        right: Box<Expr>,
236    },
237    /// `value is type`.
238    Is {
239        value: Box<Expr>,
240        /// The `is` token.
241        keyword: Span,
242        type_name: Ident,
243    },
244    /// `condition ? then : otherwise`.
245    Ternary {
246        condition: Box<Expr>,
247        /// The `?` token.
248        question: Span,
249        then: Box<Expr>,
250        otherwise: Box<Expr>,
251    },
252}
253
254#[derive(Debug, Clone, PartialEq)]
255pub enum Callee {
256    /// `name(…)`: a function of the ruleset or a global (`get`, `int`, …).
257    Global(Ident),
258    /// `namespace.name(…)`: `math.abs`, `duration.value`, …
259    Namespaced { namespace: Ident, name: Ident },
260    /// `receiver.name(…)`.
261    Method { receiver: Box<Expr>, name: Ident },
262}
263
264#[derive(Debug, Clone, PartialEq)]
265pub enum PathPart {
266    /// A literal segment without its slash.
267    Segment(String),
268    /// `$(expr)`.
269    Expr(Expr),
270}
271
272#[derive(Debug, Clone, Copy, PartialEq, Eq)]
273pub enum UnaryOp {
274    Not,
275    Neg,
276}
277
278#[derive(Debug, Clone, Copy, PartialEq, Eq)]
279pub enum BinaryOp {
280    Or,
281    And,
282    Eq,
283    Ne,
284    Lt,
285    Le,
286    Gt,
287    Ge,
288    In,
289    Add,
290    Sub,
291    Mul,
292    Div,
293    Mod,
294}