1#![doc = include_str!("../README.md")]
2#![warn(
3 elided_lifetimes_in_paths,
4 explicit_outlives_requirements,
5 missing_debug_implementations,
6 missing_docs,
7 noop_method_call,
8 single_use_lifetimes,
9 trivial_casts,
10 trivial_numeric_casts,
11 unreachable_pub,
12 unsafe_code,
13 unused_crate_dependencies,
14 unused_qualifications
15)]
16#![warn(clippy::pedantic, clippy::cargo)]
17
18#[cfg(feature = "parse-knuffel")]
19use knuffel::{Decode, DecodeScalar};
20
21mod schema_schema;
22pub use schema_schema::SCHEMA_SCHEMA;
23
24pub(crate) trait BuildFromRef {
25 fn ref_to(query: impl Into<String>) -> Self;
26}
27
28fn get_id_from_ref(r#ref: &str) -> Option<&str> {
29 r#ref
30 .strip_prefix(r#"[id=""#)
31 .and_then(|r#ref| r#ref.strip_suffix(r#""]"#))
32}
33
34#[derive(Debug, PartialEq, Eq, Default)]
36#[cfg_attr(feature = "parse-knuffel", derive(Decode))]
37pub struct Schema {
38 #[cfg_attr(feature = "parse-knuffel", knuffel(child))]
42 pub document: Document,
43}
44
45impl Schema {
46 #[must_use]
52 pub fn resolve_node_ref(&self, r#ref: &str) -> Option<&Node> {
53 let id = get_id_from_ref(r#ref).expect("invalid ref");
54 self.document
55 .nodes
56 .iter()
57 .find_map(|node| node.find_node_by_id(id))
58 }
59
60 #[must_use]
66 pub fn resolve_prop_ref(&self, r#ref: &str) -> Option<&Prop> {
67 let id = get_id_from_ref(r#ref).expect("invalid ref");
68 self.document
69 .nodes
70 .iter()
71 .find_map(|node| node.find_prop_by_id(id))
72 }
73
74 #[must_use]
80 pub fn resolve_value_ref(&self, r#ref: &str) -> Option<&Value> {
81 let id = get_id_from_ref(r#ref).expect("invalid ref");
82 self.document
83 .nodes
84 .iter()
85 .find_map(|node| node.find_value_by_id(id))
86 }
87
88 #[must_use]
94 pub fn resolve_children_ref(&self, r#ref: &str) -> Option<&Children> {
95 let id = get_id_from_ref(r#ref).expect("invalid ref");
96 self.document
97 .nodes
98 .iter()
99 .find_map(|node| node.find_children_by_id(id))
100 }
101}
102
103#[cfg(feature = "parse-knuffel")]
104impl Schema {
105 pub fn parse(
111 schema_kdl: &str,
112 ) -> Result<Self, knuffel::Error<impl knuffel::traits::ErrorSpan>> {
113 knuffel::parse("<Schema::parse argument>", schema_kdl)
114 }
115}
116
117#[derive(Debug, PartialEq, Eq, Default)]
119#[cfg_attr(feature = "parse-knuffel", derive(Decode))]
120pub struct Document {
121 #[cfg_attr(feature = "parse-knuffel", knuffel(child))]
123 pub info: Info,
124 #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "node")))]
126 pub nodes: Vec<Node>,
127}
128
129#[derive(Debug, PartialEq, Eq, Default)]
131#[cfg_attr(feature = "parse-knuffel", derive(Decode))]
132pub struct Info {
133 #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "title")))]
135 pub title: Vec<TextValue>,
136 #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "description")))]
138 pub description: Vec<TextValue>,
139 #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "author")))]
141 pub authors: Vec<Person>,
142 #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "contributor")))]
144 pub contributors: Vec<Person>,
145 #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "link")))]
147 pub links: Vec<Link>,
148 #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "license")))]
150 pub licenses: Vec<License>,
151 #[cfg_attr(feature = "parse-knuffel", knuffel(child))]
153 pub published: Option<Date>,
154 #[cfg_attr(feature = "parse-knuffel", knuffel(child))]
156 pub modified: Option<Date>,
157}
158
159#[derive(Debug, PartialEq, Eq)]
161#[cfg_attr(feature = "parse-knuffel", derive(Decode))]
162pub struct TextValue {
163 #[cfg_attr(feature = "parse-knuffel", knuffel(argument))]
165 pub text: String,
166 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
168 pub lang: Option<String>,
169}
170
171#[derive(Debug, PartialEq, Eq)]
173#[cfg_attr(feature = "parse-knuffel", derive(Decode))]
174pub struct Person {
175 #[cfg_attr(feature = "parse-knuffel", knuffel(argument))]
177 pub name: String,
178 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
180 pub orcid: Option<String>,
181 #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "link")))]
183 pub links: Vec<Link>,
184}
185
186#[derive(Debug, PartialEq, Eq)]
188#[cfg_attr(feature = "parse-knuffel", derive(Decode))]
189pub struct Link {
190 #[cfg_attr(feature = "parse-knuffel", knuffel(argument))]
192 pub iri: String,
193 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
195 pub rel: Option<String>,
196 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
198 pub lang: Option<String>,
199}
200
201#[derive(Debug, PartialEq, Eq)]
203#[cfg_attr(feature = "parse-knuffel", derive(Decode))]
204pub struct License {
205 #[cfg_attr(feature = "parse-knuffel", knuffel(argument))]
207 pub name: String,
208 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
210 pub spdx: Option<String>,
211 #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "link")))]
213 pub link: Vec<Link>,
214}
215
216#[derive(Debug, PartialEq, Eq)]
218#[cfg_attr(feature = "parse-knuffel", derive(Decode))]
219pub struct Date {
220 #[cfg_attr(feature = "parse-knuffel", knuffel(argument))]
222 pub date: String,
223 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
225 pub time: Option<String>,
226}
227
228#[derive(Debug, PartialEq, Eq, Default)]
230#[cfg_attr(feature = "parse-knuffel", derive(Decode))]
231pub struct Node {
232 #[cfg_attr(feature = "parse-knuffel", knuffel(argument))]
234 pub name: Option<String>,
235 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
237 pub id: Option<String>,
238 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
240 pub description: Option<String>,
241 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
243 pub ref_: Option<String>,
244 #[cfg_attr(feature = "parse-knuffel", knuffel(child, unwrap(argument)))]
246 pub min: Option<usize>,
247 #[cfg_attr(feature = "parse-knuffel", knuffel(child, unwrap(argument)))]
249 pub max: Option<usize>,
250 #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "prop")))]
252 pub props: Vec<Prop>,
253 #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "value")))]
255 pub values: Vec<Value>,
256 #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "children")))]
258 pub children: Vec<Children>,
259}
260
261impl Node {
262 fn find_node_by_id(&self, id: &str) -> Option<&Node> {
263 if self.id.as_deref() == Some(id) {
264 Some(self)
265 } else {
266 self.children
267 .iter()
268 .find_map(|children| children.find_node_by_id(id))
269 }
270 }
271
272 fn find_prop_by_id(&self, id: &str) -> Option<&Prop> {
273 self.props
274 .iter()
275 .find_map(|prop| prop.find_prop_by_id(id))
276 .or_else(|| {
277 self.children
278 .iter()
279 .find_map(|children| children.find_prop_by_id(id))
280 })
281 }
282
283 fn find_value_by_id(&self, id: &str) -> Option<&Value> {
284 self.values
285 .iter()
286 .find_map(|value| value.find_value_by_id(id))
287 .or_else(|| {
288 self.children
289 .iter()
290 .find_map(|children| children.find_value_by_id(id))
291 })
292 }
293
294 fn find_children_by_id(&self, id: &str) -> Option<&Children> {
295 self.children
296 .iter()
297 .find_map(|children| children.find_children_by_id(id))
298 }
299}
300
301impl BuildFromRef for Node {
302 fn ref_to(query: impl Into<String>) -> Self {
303 Self {
304 ref_: Some(query.into()),
305 ..Self::default()
306 }
307 }
308}
309
310#[derive(Debug, PartialEq, Eq, Default)]
312#[cfg_attr(feature = "parse-knuffel", derive(Decode))]
313pub struct Prop {
314 #[cfg_attr(feature = "parse-knuffel", knuffel(argument))]
316 pub key: Option<String>,
317 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
319 pub id: Option<String>,
320 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
322 pub description: Option<String>,
323 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
325 pub ref_: Option<String>,
326 #[cfg_attr(feature = "parse-knuffel", knuffel(child))]
328 pub required: bool,
329 #[cfg_attr(feature = "parse-knuffel", knuffel(children))]
331 pub validations: Vec<Validation>,
332}
333
334impl Prop {
335 fn find_prop_by_id(&self, id: &str) -> Option<&Prop> {
336 if self.id.as_deref() == Some(id) {
337 Some(self)
338 } else {
339 None
340 }
341 }
342}
343
344impl BuildFromRef for Prop {
345 fn ref_to(query: impl Into<String>) -> Self {
346 Self {
347 ref_: Some(query.into()),
348 ..Self::default()
349 }
350 }
351}
352
353#[derive(Debug, PartialEq, Eq, Default)]
355#[cfg_attr(feature = "parse-knuffel", derive(Decode))]
356pub struct Value {
357 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
359 pub id: Option<String>,
360 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
362 pub description: Option<String>,
363 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
365 pub ref_: Option<String>,
366 #[cfg_attr(feature = "parse-knuffel", knuffel(child, unwrap(argument)))]
368 pub min: Option<usize>,
369 #[cfg_attr(feature = "parse-knuffel", knuffel(child, unwrap(argument)))]
371 pub max: Option<usize>,
372 #[cfg_attr(feature = "parse-knuffel", knuffel(children))]
374 pub validations: Vec<Validation>,
375}
376
377impl Value {
378 fn find_value_by_id(&self, id: &str) -> Option<&Value> {
379 if self.id.as_deref() == Some(id) {
380 Some(self)
381 } else {
382 None
383 }
384 }
385}
386
387impl BuildFromRef for Value {
388 fn ref_to(query: impl Into<String>) -> Self {
389 Self {
390 ref_: Some(query.into()),
391 ..Self::default()
392 }
393 }
394}
395
396#[derive(Debug, PartialEq, Eq, Default)]
398#[cfg_attr(feature = "parse-knuffel", derive(Decode))]
399pub struct Children {
400 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
402 pub id: Option<String>,
403 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
405 pub description: Option<String>,
406 #[cfg_attr(feature = "parse-knuffel", knuffel(property))]
408 pub ref_: Option<String>,
409 #[cfg_attr(feature = "parse-knuffel", knuffel(children(name = "node")))]
411 pub nodes: Vec<Node>,
412}
413
414impl Children {
415 fn find_node_by_id(&self, id: &str) -> Option<&Node> {
416 self.nodes.iter().find_map(|node| node.find_node_by_id(id))
417 }
418
419 fn find_prop_by_id(&self, id: &str) -> Option<&Prop> {
420 self.nodes.iter().find_map(|node| node.find_prop_by_id(id))
421 }
422
423 fn find_value_by_id(&self, id: &str) -> Option<&Value> {
424 self.nodes.iter().find_map(|node| node.find_value_by_id(id))
425 }
426
427 fn find_children_by_id(&self, id: &str) -> Option<&Children> {
428 if self.id.as_deref() == Some(id) {
429 Some(self)
430 } else {
431 self.nodes
432 .iter()
433 .find_map(|node| node.find_children_by_id(id))
434 }
435 }
436}
437
438impl BuildFromRef for Children {
439 fn ref_to(query: impl Into<String>) -> Self {
440 Self {
441 ref_: Some(query.into()),
442 ..Self::default()
443 }
444 }
445}
446
447#[derive(Debug, PartialEq, Eq)]
449#[cfg_attr(feature = "parse-knuffel", derive(Decode))]
450pub enum Validation {
451 Type(#[cfg_attr(feature = "parse-knuffel", knuffel(argument))] String),
453 Enum(#[cfg_attr(feature = "parse-knuffel", knuffel(arguments))] Vec<String>),
455 Pattern(#[cfg_attr(feature = "parse-knuffel", knuffel(argument))] String),
457 Format(#[cfg_attr(feature = "parse-knuffel", knuffel(arguments))] Vec<Format>),
459}
460
461#[derive(Clone, Debug, PartialEq, Eq)]
463#[cfg_attr(feature = "parse-knuffel", derive(DecodeScalar))]
464pub enum Format {
465 DateTime,
467 Date,
469 Time,
471 Duration,
473 Decimal,
475 Currency,
477 Country2,
479 Country3,
481 CountrySubdivision,
483 Email,
485 IdnEmail,
487 Hostname,
489 IdnHostname,
491 Ipv4,
493 Ipv6,
495 Url,
497 UrlReference,
499 Irl,
501 IrlReference,
503 UrlTemplate,
505 Uuid,
507 Regex,
509 Base64,
511 KdlQuery,
513}