1use nautilus_schema::{
12 ast::*,
13 visitor::{walk_model, Visitor},
14 Lexer, Parser, Result,
15};
16use std::collections::{HashMap, HashSet};
17
18const SCHEMA: &str = r#"
19datasource db {
20 provider = "postgresql"
21 url = env("DATABASE_URL")
22}
23
24enum Role {
25 USER
26 ADMIN
27 MODERATOR
28}
29
30model User {
31 id Int @id @default(autoincrement())
32 email String @unique
33 username String @unique
34 role Role @default(USER)
35 createdAt DateTime @default(now())
36
37 posts Post[]
38 comments Comment[]
39 profile Profile?
40
41 @@map("users")
42}
43
44model Profile {
45 id Int @id @default(autoincrement())
46 userId Int @unique
47 bio String?
48 avatar String?
49
50 user User @relation(fields: [userId], references: [id], onDelete: Cascade)
51
52 @@map("profiles")
53}
54
55model Post {
56 id Int @id @default(autoincrement())
57 authorId Int
58 title String
59 content String
60 published Boolean @default(false)
61 createdAt DateTime @default(now())
62
63 author User @relation(fields: [authorId], references: [id])
64 comments Comment[]
65
66 @@map("posts")
67 @@index([authorId])
68}
69
70model Comment {
71 id Int @id @default(autoincrement())
72 postId Int
73 authorId Int
74 content String
75 createdAt DateTime @default(now())
76
77 post Post @relation(fields: [postId], references: [id], onDelete: Cascade)
78 author User @relation(fields: [authorId], references: [id])
79
80 @@map("comments")
81 @@index([postId, authorId])
82}
83"#;
84
85#[derive(Default, Debug)]
86struct SchemaStats {
87 models: usize,
88 enums: usize,
89 total_fields: usize,
90 unique_constraints: usize,
91 indexes: usize,
92 relations: usize,
93 optional_fields: usize,
94 array_fields: usize,
95}
96
97impl Visitor for SchemaStats {
98 fn visit_model(&mut self, model: &ModelDecl) -> Result<()> {
99 self.models += 1;
100 self.total_fields += model.fields.len();
101
102 for attr in &model.attributes {
103 if let ModelAttribute::Index { .. } = attr {
104 self.indexes += 1
105 }
106 }
107
108 walk_model(self, model)
109 }
110
111 fn visit_enum(&mut self, _enum_decl: &EnumDecl) -> Result<()> {
112 self.enums += 1;
113 Ok(())
114 }
115
116 fn visit_field(&mut self, field: &FieldDecl) -> Result<()> {
117 if field.is_optional() {
118 self.optional_fields += 1;
119 }
120 if field.is_array() {
121 self.array_fields += 1;
122 }
123
124 for attr in &field.attributes {
125 match attr {
126 FieldAttribute::Unique => self.unique_constraints += 1,
127 FieldAttribute::Relation { .. } => self.relations += 1,
128 _ => {}
129 }
130 }
131
132 Ok(())
133 }
134}
135
136#[derive(Default)]
137struct RelationshipGraph {
138 relationships: HashMap<String, Vec<(String, String, bool)>>,
140}
141
142impl Visitor for RelationshipGraph {
143 fn visit_model(&mut self, model: &ModelDecl) -> Result<()> {
144 let model_name = model.name.value.clone();
145
146 for field in &model.fields {
147 if let FieldType::UserType(related_type) = &field.field_type {
148 if field.has_relation_attribute() || field.is_array() {
149 let is_required = !field.is_optional() && !field.is_array();
150
151 self.relationships
152 .entry(model_name.clone())
153 .or_default()
154 .push((related_type.clone(), field.name.value.clone(), is_required));
155 }
156 }
157 }
158
159 walk_model(self, model)
160 }
161}
162
163impl RelationshipGraph {
164 fn print(&self) {
165 println!("🔗 Relationship Graph:");
166 for (model, relations) in &self.relationships {
167 println!("\n {} has:", model);
168 for (related, field_name, is_required) in relations {
169 let cardinality = if *is_required { "one" } else { "zero or more" };
170 println!(
171 " - {} {} via field '{}'",
172 cardinality, related, field_name
173 );
174 }
175 }
176 }
177}
178
179#[derive(Default)]
180struct DefaultValueCollector {
181 defaults: HashMap<String, String>,
183}
184
185impl Visitor for DefaultValueCollector {
186 fn visit_model(&mut self, model: &ModelDecl) -> Result<()> {
187 let model_name = model.name.value.clone();
188
189 for field in &model.fields {
190 for attr in &field.attributes {
191 if let FieldAttribute::Default(expr, _) = attr {
192 let key = format!("{}.{}", model_name, field.name.value);
193 let value = format!("{:?}", expr);
194 self.defaults.insert(key, value);
195 }
196 }
197 }
198
199 walk_model(self, model)
200 }
201}
202
203struct NamingValidator {
204 errors: Vec<String>,
205}
206
207impl NamingValidator {
208 fn new() -> Self {
209 Self { errors: Vec::new() }
210 }
211}
212
213impl Visitor for NamingValidator {
214 fn visit_model(&mut self, model: &ModelDecl) -> Result<()> {
215 if !model.name.value.chars().next().unwrap().is_uppercase() {
216 self.errors.push(format!(
217 "Model '{}' should start with uppercase",
218 model.name.value
219 ));
220 }
221
222 walk_model(self, model)
223 }
224
225 fn visit_field(&mut self, field: &FieldDecl) -> Result<()> {
226 if !field.name.value.chars().next().unwrap().is_lowercase() {
227 self.errors.push(format!(
228 "Field '{}' should start with lowercase",
229 field.name.value
230 ));
231 }
232
233 Ok(())
234 }
235
236 fn visit_enum(&mut self, enum_decl: &EnumDecl) -> Result<()> {
237 if !enum_decl.name.value.chars().next().unwrap().is_uppercase() {
238 self.errors.push(format!(
239 "Enum '{}' should start with uppercase",
240 enum_decl.name.value
241 ));
242 }
243
244 for variant in &enum_decl.variants {
245 if !variant
246 .name
247 .value
248 .chars()
249 .all(|c| c.is_uppercase() || c == '_')
250 {
251 self.errors.push(format!(
252 "Enum variant '{}' should be UPPERCASE",
253 variant.name.value
254 ));
255 }
256 }
257
258 Ok(())
259 }
260}
261
262#[derive(Default)]
263struct MigrationOrderer {
264 dependencies: HashMap<String, HashSet<String>>,
266}
267
268impl Visitor for MigrationOrderer {
269 fn visit_model(&mut self, model: &ModelDecl) -> Result<()> {
270 let model_name = model.name.value.clone();
271 let mut deps = HashSet::new();
272
273 for field in &model.fields {
274 if let FieldType::UserType(related_type) = &field.field_type {
275 for attr in &field.attributes {
276 if let FieldAttribute::Relation {
277 fields: Some(_), ..
278 } = attr
279 {
280 deps.insert(related_type.clone());
281 }
282 }
283 }
284 }
285
286 self.dependencies.insert(model_name, deps);
287 walk_model(self, model)
288 }
289}
290
291impl MigrationOrderer {
292 fn print(&self) {
293 println!("📦 Migration Order (based on foreign key dependencies):");
294 println!(" Models should be created in this order:\n");
295
296 let mut remaining: HashSet<_> = self.dependencies.keys().cloned().collect();
297 let mut order = Vec::new();
298
299 while !remaining.is_empty() {
300 let can_create: Vec<_> = remaining
301 .iter()
302 .filter(|m| {
303 self.dependencies[*m]
304 .iter()
305 .all(|dep| order.contains(dep) || !remaining.contains(dep))
306 })
307 .cloned()
308 .collect();
309
310 if can_create.is_empty() {
311 println!(" ⚠️ Circular dependency detected!");
312 break;
313 }
314
315 for model in can_create {
316 order.push(model.clone());
317 remaining.remove(&model);
318
319 let deps_list: Vec<_> = self.dependencies[&model].iter().collect();
320 if deps_list.is_empty() {
321 println!(" {}. {} (no dependencies)", order.len(), model);
322 } else {
323 println!(
324 " {}. {} (depends on: {})",
325 order.len(),
326 model,
327 deps_list
328 .iter()
329 .map(|s| s.as_str())
330 .collect::<Vec<_>>()
331 .join(", ")
332 );
333 }
334 }
335 }
336 }
337}
338
339fn main() -> Result<()> {
340 println!("=== Nautilus Schema Visitor Pattern Demo ===\n");
341
342 let mut lexer = Lexer::new(SCHEMA);
343 let mut tokens = Vec::new();
344 loop {
345 let token = lexer.next_token()?;
346 if matches!(token.kind, nautilus_schema::TokenKind::Eof) {
347 tokens.push(token);
348 break;
349 }
350 tokens.push(token);
351 }
352 let schema = Parser::new(&tokens, SCHEMA).parse_schema()?;
353
354 println!(
355 "Parsed schema with {} declarations\n",
356 schema.declarations.len()
357 );
358 let separator = "=".repeat(60);
359 println!("{}", separator);
360
361 println!("\n1️⃣ Schema Statistics\n");
362 let mut stats = SchemaStats::default();
363 stats.visit_schema(&schema)?;
364 println!("{:#?}", stats);
365
366 println!("\n{}", separator);
367
368 println!("\n2️⃣ Relationship Analysis\n");
369 let mut graph = RelationshipGraph::default();
370 graph.visit_schema(&schema)?;
371 graph.print();
372
373 println!("\n{}", separator);
374
375 println!("\n3️⃣ Default Values\n");
376 let mut defaults = DefaultValueCollector::default();
377 defaults.visit_schema(&schema)?;
378 for (field, default) in &defaults.defaults {
379 println!(" {} = {}", field, default);
380 }
381
382 println!("\n{}", separator);
383
384 println!("\n4️⃣ Naming Convention Validation\n");
385 let mut validator = NamingValidator::new();
386 validator.visit_schema(&schema)?;
387 if validator.errors.is_empty() {
388 println!(" ✅ All names follow conventions!");
389 } else {
390 println!(" ⚠️ Found {} naming issues:", validator.errors.len());
391 for error in &validator.errors {
392 println!(" - {}", error);
393 }
394 }
395
396 println!("\n{}", separator);
397
398 println!("\n5️⃣ Migration Planning\n");
399 let mut orderer = MigrationOrderer::default();
400 orderer.visit_schema(&schema)?;
401 orderer.print();
402
403 println!("\n{}", separator);
404 println!("\n✅ Visitor demo completed successfully!");
405
406 Ok(())
407}