1pub mod ast;
8mod canonical;
9pub mod diagnostic;
10pub mod generator;
11pub mod lexer;
12mod parse_limits;
13pub mod parser;
14pub mod schema;
15
16pub use ast::{Block, Document, Value};
17pub use canonical::{
18 canonical_bytes, canonical_bytes_with_schema, canonical_digest, canonical_digest_with_schema,
19 CanonicalError, CANONICAL_DIGEST_ALGORITHM,
20};
21pub use diagnostic::{DiagnosticCode, DiagnosticReport, ParseError};
22pub use generator::{generate, generate_from_map, Generator, GeneratorConfig};
23pub use lexer::{Lexer, Location, Span, Token, TokenWithSpan};
24pub use parse_limits::{ParseLimits, DEFAULT_PARSE_LIMITS};
25pub use parser::{collect_diagnostics, collect_diagnostics_with_limits, parse, parse_with_limits};
26pub use schema::{
27 validate_document, validate_document_with_limits, AttributeSchema, BlockSchema, CallSchema,
28 Cardinality, ObjectSchema, Schema, SchemaDefinitionError, SchemaDiagnostic,
29 SchemaDiagnosticCode, SchemaReport, ValueSchema,
30};
31
32pub fn parse_acl(input: &str) -> Result<Document, ParseError> {
55 parse(input)
56}
57
58pub fn generate_acl(doc: &Document) -> String {
82 generate(doc)
83}
84
85pub mod builder {
87 use crate::ast::{Block, Document, Value};
88 use std::collections::HashMap;
89
90 pub struct DocumentBuilder {
92 blocks: Vec<Block>,
93 }
94
95 impl DocumentBuilder {
96 pub fn new() -> Self {
97 Self { blocks: Vec::new() }
98 }
99
100 pub fn block(mut self, block: Block) -> Self {
102 self.blocks.push(block);
103 self
104 }
105
106 pub fn kv_block(mut self, name: &str, key: &str, value: Value) -> Self {
108 let mut attrs = HashMap::new();
109 attrs.insert(key.to_string(), value);
110 self.blocks.push(Block {
111 name: name.to_string(),
112 labels: Vec::new(),
113 blocks: Vec::new(),
114 attributes: attrs,
115 });
116 self
117 }
118
119 pub fn build(self) -> Document {
121 Document {
122 blocks: self.blocks,
123 }
124 }
125 }
126
127 impl Default for DocumentBuilder {
128 fn default() -> Self {
129 Self::new()
130 }
131 }
132
133 pub struct BlockBuilder {
135 name: String,
136 labels: Vec<String>,
137 blocks: Vec<Block>,
138 attributes: HashMap<String, Value>,
139 }
140
141 impl BlockBuilder {
142 pub fn new(name: &str) -> Self {
143 Self {
144 name: name.to_string(),
145 labels: Vec::new(),
146 blocks: Vec::new(),
147 attributes: HashMap::new(),
148 }
149 }
150
151 pub fn label(mut self, label: &str) -> Self {
152 self.labels.push(label.to_string());
153 self
154 }
155
156 pub fn attr(mut self, key: &str, value: Value) -> Self {
157 self.attributes.insert(key.to_string(), value);
158 self
159 }
160
161 pub fn nested_block(mut self, block: Block) -> Self {
162 self.blocks.push(block);
163 self
164 }
165
166 pub fn build(self) -> Block {
167 Block {
168 name: self.name,
169 labels: self.labels,
170 blocks: self.blocks,
171 attributes: self.attributes,
172 }
173 }
174 }
175
176 pub fn string(s: &str) -> Value {
178 Value::String(s.to_string())
179 }
180
181 pub fn number(n: f64) -> Value {
182 Value::Number(n)
183 }
184
185 pub fn integer(n: i64) -> Value {
186 Value::Number(n as f64)
187 }
188
189 pub fn boolean(b: bool) -> Value {
190 Value::Bool(b)
191 }
192
193 pub fn null() -> Value {
194 Value::Null
195 }
196
197 pub fn list(items: Vec<Value>) -> Value {
198 Value::List(items)
199 }
200
201 pub fn call(name: &str, args: Vec<Value>) -> Value {
202 Value::Call(name.to_string(), args)
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_parse_acl_function() {
212 let input = r#"
213 name = "test"
214 count = 42
215 "#;
216 let doc = parse_acl(input).unwrap();
217 assert_eq!(doc.blocks.len(), 2);
218 }
219
220 #[test]
221 fn test_generate_acl_function() {
222 let doc = Document {
223 blocks: vec![Block {
224 name: "test".to_string(),
225 labels: vec![],
226 blocks: vec![],
227 attributes: vec![("key".to_string(), Value::String("value".to_string()))]
228 .into_iter()
229 .collect(),
230 }],
231 };
232 let output = generate_acl(&doc);
233 assert!(output.contains("test"));
234 assert!(output.contains("value"));
235 }
236
237 #[test]
238 fn test_builder_document_builder() {
239 let doc = builder::DocumentBuilder::new()
240 .kv_block("config", "key", builder::string("value"))
241 .build();
242
243 assert_eq!(doc.blocks.len(), 1);
244 assert_eq!(doc.blocks[0].name, "config");
245 }
246
247 #[test]
248 fn test_builder_document_builder_with_block() {
249 let block = builder::BlockBuilder::new("provider")
250 .attr("name", builder::string("openai"))
251 .build();
252
253 let doc = builder::DocumentBuilder::new().block(block).build();
254
255 assert_eq!(doc.blocks.len(), 1);
256 assert_eq!(doc.blocks[0].name, "provider");
257 }
258
259 #[test]
260 fn test_builder_document_default() {
261 let doc = builder::DocumentBuilder::default().build();
262 assert!(doc.blocks.is_empty());
263 }
264
265 #[test]
266 fn test_builder_block_builder() {
267 let block = builder::BlockBuilder::new("provider")
268 .label("openai")
269 .attr("api_key", builder::string("sk-xxx"))
270 .attr("enabled", builder::boolean(true))
271 .build();
272
273 assert_eq!(block.name, "provider");
274 assert_eq!(block.labels, vec!["openai"]);
275 assert_eq!(
276 block
277 .attributes
278 .get("api_key")
279 .map(|v| v.to_string())
280 .unwrap(),
281 "sk-xxx"
282 );
283 }
284
285 #[test]
286 fn test_builder_block_builder_nested() {
287 let inner = builder::BlockBuilder::new("model")
288 .attr("name", builder::string("gpt-4"))
289 .build();
290
291 let block = builder::BlockBuilder::new("provider")
292 .label("openai")
293 .nested_block(inner)
294 .build();
295
296 assert_eq!(block.blocks.len(), 1);
297 assert_eq!(block.blocks[0].name, "model");
298 }
299
300 #[test]
301 fn test_builder_value_helpers() {
302 assert_eq!(builder::string("test"), Value::String("test".to_string()));
303 assert_eq!(builder::number(3.14), Value::Number(3.14));
304 assert_eq!(builder::integer(42), Value::Number(42.0));
305 assert_eq!(builder::boolean(true), Value::Bool(true));
306 assert_eq!(builder::boolean(false), Value::Bool(false));
307 assert_eq!(builder::null(), Value::Null);
308 assert_eq!(
309 builder::list(vec![Value::Number(1.0)]),
310 Value::List(vec![Value::Number(1.0)])
311 );
312 }
313
314 #[test]
315 fn test_re_exports() {
316 let _ = Document::default();
318 let _ = Block {
319 name: "test".to_string(),
320 labels: vec![],
321 blocks: vec![],
322 attributes: std::collections::HashMap::new(),
323 };
324 let _ = Value::String("test".to_string());
325 let _ = Generator::new();
326 let _ = GeneratorConfig::default();
327 let _ = Lexer::new("");
328 let _ = Location {
329 line: 1,
330 column: 1,
331 offset: 0,
332 };
333 let _ = Span::default();
334 let _ = Token::Eof;
335 let _ = TokenWithSpan::new(Token::Eof, Location::default(), Location::default());
336 let _ = ParseError::new(
337 DiagnosticCode::UnexpectedToken,
338 "test",
339 Span::point(Location {
340 line: 1,
341 column: 1,
342 offset: 0,
343 }),
344 );
345 }
346
347 #[test]
348 fn test_lexer_re_exports() {
349 let mut lexer = Lexer::new("test = 42");
350 let tokens = lexer.tokenize();
351 assert!(!tokens.is_empty());
352 }
353
354 #[test]
355 fn test_token_with_span() {
356 let loc = Location {
357 line: 1,
358 column: 1,
359 offset: 0,
360 };
361 let token = TokenWithSpan::new(Token::Ident("test".to_string()), loc, loc);
362 assert!(matches!(token.token, Token::Ident(_)));
363 }
364}