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 #[serde(default)]
60 pub pending: bool,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
66pub struct ClauseId(pub String);
67
68impl std::fmt::Display for ClauseId {
69 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70 f.write_str(&self.0)
71 }
72}
73
74#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
76pub enum Keyword {
77 Must,
78 MustNot,
79 Should,
80 ShouldNot,
81 May,
82 Wont,
83 Given,
84 Otherwise,
85 MustAlways,
86 MustBy,
87}
88
89#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
91pub enum Severity {
92 Required,
93 Recommended,
94 Optional,
95 NegativeConfirmation,
96}
97
98impl Keyword {
99 pub fn severity(self) -> Severity {
100 match self {
101 Keyword::Must | Keyword::MustNot | Keyword::MustAlways | Keyword::MustBy => {
102 Severity::Required
103 }
104 Keyword::Should | Keyword::ShouldNot => Severity::Recommended,
105 Keyword::May => Severity::Optional,
106 Keyword::Wont => Severity::NegativeConfirmation,
107 Keyword::Given | Keyword::Otherwise => Severity::Required,
108 }
109 }
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize)]
114pub enum Temporal {
115 Invariant,
117 Deadline(Duration),
119}
120
121#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct SourceLocation {
124 pub file: PathBuf,
125 pub line: usize,
126}
127
128#[derive(Debug, Clone, thiserror::Error)]
130#[error("{file}:{line}: {message}")]
131pub struct ParseError {
132 pub file: PathBuf,
133 pub line: usize,
134 pub message: String,
135}