1use crate::SchemaFile;
2use crate::ast::{Comment, Definition, DocString, Ident, ImportStmt, Prelude};
3use crate::error::{DuplicateDefinition, InvalidSchemaName, InvalidSyntax, IoError};
4use crate::grammar::{Grammar, Rule};
5use crate::issues::Issues;
6use crate::validate::Validate;
7use crate::warning::{BrokenDocLink, DuplicateImport, NonSnakeCaseSchemaName, ReservedSchemaName};
8use pest::Parser;
9
10#[derive(Debug, Clone)]
11pub struct Schema {
12 name: String,
13 path: String,
14 source: Option<String>,
15 comment: Vec<Comment>,
16 doc: Vec<DocString>,
17 imports: Vec<ImportStmt>,
18 defs: Vec<Definition>,
19}
20
21impl Schema {
22 pub(crate) fn parse(file: &SchemaFile, issues: &mut Issues) -> Self {
23 let mut schema = Self {
24 name: file.name().to_owned(),
25 path: file.path().to_owned(),
26 source: None,
27 comment: Vec::new(),
28 doc: Vec::new(),
29 imports: Vec::new(),
30 defs: Vec::new(),
31 };
32
33 let source = match file.source() {
34 Ok(source) => source,
35
36 Err(e) => {
37 issues.add_error(IoError::new(&schema.name, e.to_string()));
38 return schema;
39 }
40 };
41
42 schema.source = Some(source.to_owned());
43
44 let mut pairs = match Grammar::parse(Rule::file, source) {
45 Ok(pairs) => pairs,
46
47 Err(e) => {
48 issues.add_error(InvalidSyntax::new(&schema.name, e));
49 return schema;
50 }
51 };
52
53 let mut prelude = Prelude::schema(&mut pairs);
54
55 for pair in pairs {
56 #[expect(clippy::wildcard_enum_match_arm)]
57 match pair.as_rule() {
58 Rule::import_stmt => schema.imports.push(ImportStmt::parse(pair)),
59 Rule::def => schema.defs.push(Definition::parse(pair)),
60 Rule::EOI => break,
61 _ => unreachable!(),
62 }
63 }
64
65 schema.comment = prelude.take_comment();
66 schema.doc = prelude.take_doc();
67
68 schema
69 }
70
71 pub(crate) fn validate(&self, validate: &mut Validate) {
72 if !Ident::is_valid(&self.name) {
73 validate.add_error(InvalidSchemaName::new(self.name.clone()));
74 }
75
76 if self.source.is_none() {
77 return;
78 }
79
80 BrokenDocLink::validate(&self.doc, validate);
81 DuplicateDefinition::validate(self, validate);
82 DuplicateImport::validate(self, validate);
83 NonSnakeCaseSchemaName::validate(&self.name, validate);
84 ReservedSchemaName::validate(&self.name, validate);
85
86 for import in &self.imports {
87 import.validate(validate);
88 }
89
90 for def in &self.defs {
91 def.validate(validate);
92 }
93 }
94
95 pub fn name(&self) -> &str {
96 &self.name
97 }
98
99 pub fn path(&self) -> &str {
100 &self.path
101 }
102
103 pub fn source(&self) -> Option<&str> {
104 self.source.as_deref()
105 }
106
107 pub fn comment(&self) -> &[Comment] {
108 &self.comment
109 }
110
111 pub fn doc(&self) -> &[DocString] {
112 &self.doc
113 }
114
115 pub fn imports(&self) -> &[ImportStmt] {
116 &self.imports
117 }
118
119 pub fn definitions(&self) -> &[Definition] {
120 &self.defs
121 }
122}