1use std::path::PathBuf;
2use std::time::Duration;
3
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct Spec {
9 pub name: String,
10 pub metadata: Metadata,
11 pub sections: Vec<Section>,
12 pub source_path: PathBuf,
13}
14
15#[derive(Debug, Clone, Default, Serialize, Deserialize)]
17pub struct Metadata {
18 pub context: Option<String>,
19 pub sources: Vec<String>,
20 pub schemas: Vec<String>,
21 pub requires: Vec<SpecRef>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
26pub struct SpecRef {
27 pub label: String,
28 pub path: PathBuf,
29 pub anchor: Option<String>,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct Section {
35 pub title: String,
36 pub depth: u8,
37 pub prose: String,
38 pub clauses: Vec<Clause>,
39 pub subsections: Vec<Section>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct Clause {
45 pub id: ClauseId,
46 pub keyword: Keyword,
47 pub severity: Severity,
48 pub text: String,
49 pub condition: Option<String>,
50 pub otherwise: Vec<Clause>,
51 pub temporal: Option<Temporal>,
52 pub hints: Vec<String>,
53 pub source_location: SourceLocation,
54 pub content_hash: String,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
60pub struct ClauseId(pub String);
61
62impl std::fmt::Display for ClauseId {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 f.write_str(&self.0)
65 }
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
70pub enum Keyword {
71 Must,
72 MustNot,
73 Should,
74 ShouldNot,
75 May,
76 Wont,
77 Given,
78 Otherwise,
79 MustAlways,
80 MustBy,
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
85pub enum Severity {
86 Required,
87 Recommended,
88 Optional,
89 NegativeConfirmation,
90}
91
92impl Keyword {
93 pub fn severity(self) -> Severity {
94 match self {
95 Keyword::Must | Keyword::MustNot | Keyword::MustAlways | Keyword::MustBy => {
96 Severity::Required
97 }
98 Keyword::Should | Keyword::ShouldNot => Severity::Recommended,
99 Keyword::May => Severity::Optional,
100 Keyword::Wont => Severity::NegativeConfirmation,
101 Keyword::Given | Keyword::Otherwise => Severity::Required,
102 }
103 }
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
108pub enum Temporal {
109 Invariant,
111 Deadline(Duration),
113}
114
115#[derive(Debug, Clone, Serialize, Deserialize)]
117pub struct SourceLocation {
118 pub file: PathBuf,
119 pub line: usize,
120}
121
122#[derive(Debug, Clone, thiserror::Error)]
124#[error("{file}:{line}: {message}")]
125pub struct ParseError {
126 pub file: PathBuf,
127 pub line: usize,
128 pub message: String,
129}