1use crate::ast::{Definition, ImportStmt, SchemaName};
2use crate::error::{DuplicateDefinition, InvalidSchemaName, InvalidSyntax, IoError};
3use crate::grammar::{Grammar, Rule};
4use crate::issues::Issues;
5use crate::validate::Validate;
6use crate::warning::{DuplicateImport, NonSnakeCaseSchemaName};
7use pest::Parser;
8use std::fs::File;
9use std::io::Read;
10use std::path::{Path, PathBuf};
11
12#[derive(Debug, Clone)]
13pub struct Schema {
14 name: String,
15 path: PathBuf,
16 source: Option<String>,
17 imports: Vec<ImportStmt>,
18 defs: Vec<Definition>,
19}
20
21impl Schema {
22 pub(crate) fn parse<P>(schema_path: P, issues: &mut Issues) -> Self
23 where
24 P: AsRef<Path>,
25 {
26 let schema_path = schema_path.as_ref();
27
28 let mut schema = Self {
29 name: Self::parse_file_name(schema_path, issues),
30 path: schema_path.to_owned(),
31 source: None,
32 imports: Vec::new(),
33 defs: Vec::new(),
34 };
35
36 let source = {
37 let mut file = match File::open(schema_path) {
38 Ok(file) => file,
39 Err(e) => {
40 issues.add_error(IoError::new(&schema.name, e));
41 return schema;
42 }
43 };
44
45 let mut source = String::new();
46 if let Err(e) = file.read_to_string(&mut source) {
47 issues.add_error(IoError::new(&schema.name, e));
48 return schema;
49 }
50
51 source
52 };
53
54 let pairs = match Grammar::parse(Rule::file, &source) {
55 Ok(pairs) => pairs,
56 Err(e) => {
57 schema.source = Some(source);
58 issues.add_error(InvalidSyntax::new(&schema.name, e));
59 return schema;
60 }
61 };
62
63 for pair in pairs {
64 match pair.as_rule() {
65 Rule::import_stmt => schema.imports.push(ImportStmt::parse(pair)),
66 Rule::def => schema.defs.push(Definition::parse(pair)),
67 Rule::EOI => break,
68 _ => unreachable!(),
69 }
70 }
71
72 schema.source = Some(source);
73 schema
74 }
75
76 fn parse_file_name<P>(path: P, issues: &mut Issues) -> String
77 where
78 P: AsRef<Path>,
79 {
80 let path = path.as_ref();
81
82 let file_stem = match path.file_stem() {
83 Some(file_stem) => file_stem,
84 None => {
85 let schema_name = path.to_string_lossy().into_owned();
86 issues.add_error(InvalidSchemaName::new(&schema_name));
87 return schema_name;
88 }
89 };
90
91 let file_stem_str = match file_stem.to_str() {
92 Some(file_stem_str) => file_stem_str,
93 None => {
94 let schema_name = file_stem.to_string_lossy().into_owned();
95 issues.add_error(InvalidSchemaName::new(&schema_name));
96 return schema_name;
97 }
98 };
99
100 let mut schema_name_pairs = match Grammar::parse(Rule::schema_name, file_stem_str) {
101 Ok(schema_name_pairs) => schema_name_pairs,
102 Err(_) => {
103 issues.add_error(InvalidSchemaName::new(file_stem_str));
104 return file_stem_str.to_owned();
105 }
106 };
107
108 if schema_name_pairs.as_str() != file_stem_str {
109 issues.add_error(InvalidSchemaName::new(file_stem_str));
110 return file_stem_str.to_owned();
111 }
112
113 let schema_name = SchemaName::parse(schema_name_pairs.next().unwrap());
114 schema_name.value().to_owned()
115 }
116
117 pub(crate) fn validate(&self, validate: &mut Validate) {
118 if self.source.is_none() {
119 return;
120 }
121
122 DuplicateDefinition::validate(self, validate);
123 NonSnakeCaseSchemaName::validate(&self.name, validate);
124 DuplicateImport::validate(self, validate);
125
126 for import in &self.imports {
127 import.validate(validate);
128 }
129
130 for def in &self.defs {
131 def.validate(validate);
132 }
133 }
134
135 pub fn name(&self) -> &str {
136 &self.name
137 }
138
139 pub fn path(&self) -> &Path {
140 &self.path
141 }
142
143 pub fn source(&self) -> Option<&str> {
144 self.source.as_deref()
145 }
146
147 pub fn imports(&self) -> &[ImportStmt] {
148 &self.imports
149 }
150
151 pub fn definitions(&self) -> &[Definition] {
152 &self.defs
153 }
154}