1use indexmap::IndexMap;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub struct RegistryRef {
9 pub id: String,
11 #[serde(default, skip_serializing_if = "Option::is_none")]
13 pub uri: Option<String>,
14}
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
18#[serde(rename_all = "lowercase")]
19pub enum RegistryPublicationStatus {
20 Draft,
22 Experimental,
24 Standard,
26 Deprecated,
28 Obsolete,
30}
31
32impl RegistryPublicationStatus {
33 #[must_use]
35 pub fn as_str(self) -> &'static str {
36 match self {
37 Self::Draft => "draft",
38 Self::Experimental => "experimental",
39 Self::Standard => "standard",
40 Self::Deprecated => "deprecated",
41 Self::Obsolete => "obsolete",
42 }
43 }
44}
45
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "lowercase")]
49pub enum RegistryEntryStatus {
50 Draft,
52 Experimental,
54 Candidate,
56 Standard,
58 Deprecated,
60 Obsolete,
62}
63
64impl RegistryEntryStatus {
65 #[must_use]
67 pub fn as_str(self) -> &'static str {
68 match self {
69 Self::Draft => "draft",
70 Self::Experimental => "experimental",
71 Self::Candidate => "candidate",
72 Self::Standard => "standard",
73 Self::Deprecated => "deprecated",
74 Self::Obsolete => "obsolete",
75 }
76 }
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
81#[serde(rename_all = "camelCase")]
82pub enum RegistryCategory {
83 SemanticAction,
85 Function,
87 Operator,
89 Rule,
91 LogicalType,
93 Diagnostic,
95 ExtensionNamespace,
97 Profile,
99 Capability,
101}
102
103impl RegistryCategory {
104 #[must_use]
106 pub fn as_str(self) -> &'static str {
107 match self {
108 Self::SemanticAction => "semanticAction",
109 Self::Function => "function",
110 Self::Operator => "operator",
111 Self::Rule => "rule",
112 Self::LogicalType => "logicalType",
113 Self::Diagnostic => "diagnostic",
114 Self::ExtensionNamespace => "extensionNamespace",
115 Self::Profile => "profile",
116 Self::Capability => "capability",
117 }
118 }
119}
120
121#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
123#[serde(rename_all = "lowercase")]
124pub enum ExtensionCompatibility {
125 Mandatory,
127 Optional,
129}
130
131impl ExtensionCompatibility {
132 #[must_use]
134 pub fn as_str(self) -> &'static str {
135 match self {
136 Self::Mandatory => "mandatory",
137 Self::Optional => "optional",
138 }
139 }
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
144#[serde(rename_all = "camelCase")]
145pub struct RegistryEntry {
146 #[serde(default)]
150 pub id: String,
151 pub name: String,
153 pub category: RegistryCategory,
155 pub version: String,
157 pub status: RegistryEntryStatus,
159 #[serde(default, skip_serializing_if = "Option::is_none")]
161 pub compatibility: Option<ExtensionCompatibility>,
162 #[serde(default, skip_serializing_if = "Option::is_none")]
164 pub definition: Option<String>,
165 #[serde(default, skip_serializing_if = "Vec::is_empty")]
167 pub references: Vec<String>,
168 #[serde(default = "default_supported")]
170 pub supported: bool,
171}
172
173fn default_supported() -> bool {
174 true
175}
176
177#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
179#[serde(rename_all = "camelCase")]
180pub struct RegistryDocument {
181 pub id: String,
183 pub version: String,
185 pub governing_specification: String,
187 pub publication_status: RegistryPublicationStatus,
189 #[serde(default)]
191 pub entries: IndexMap<String, RegistryEntry>,
192}
193
194impl RegistryDocument {
195 #[must_use]
197 pub fn get(&self, id: &str) -> Option<&RegistryEntry> {
198 self.entries.get(id)
199 }
200
201 #[must_use]
203 pub fn get_category(&self, id: &str, category: RegistryCategory) -> Option<&RegistryEntry> {
204 self.entries
205 .values()
206 .find(|entry| entry.id == id && entry.category == category)
207 }
208
209 pub fn insert(&mut self, entry: RegistryEntry) {
211 self.entries.insert(entry.id.clone(), entry);
212 }
213
214 pub fn merge(
220 &mut self,
221 other: &RegistryDocument,
222 ) -> Result<(), crate::diagnostics::DiagnosticReport> {
223 for (id, entry) in &other.entries {
224 if id.starts_with("dtcs:") {
225 if self.entries.contains_key(id) {
226 continue;
227 }
228 let mut report = crate::diagnostics::DiagnosticReport::new();
229 report.push(
230 crate::diagnostics::Diagnostic::new(
231 crate::diagnostics::codes::INVALID_REGISTRY,
232 crate::diagnostics::Severity::Error,
233 crate::diagnostics::DiagnosticStage::Validation,
234 crate::diagnostics::DiagnosticCategory::Structure,
235 format!(
236 "registry merge rejected novel standard entry '{id}'; vendor catalogs cannot extend the dtcs: namespace"
237 ),
238 )
239 .with_object_ref(format!("entries.{id}"))
240 .with_remediation(
241 "Use vendor namespaces for custom identifiers; only builtin dtcs: entries are authoritative",
242 ),
243 );
244 return Err(report);
245 }
246 self.entries.insert(id.clone(), entry.clone());
247 }
248 Ok(())
249 }
250
251 pub(crate) fn merge_trusted(&mut self, other: &RegistryDocument) {
255 for (id, entry) in &other.entries {
256 if id.starts_with("dtcs:") && self.entries.contains_key(id) {
257 if self
258 .entries
259 .get(id)
260 .is_some_and(|existing| existing.category != entry.category)
261 {
262 self.entries
263 .insert(format!("{}:{id}", entry.category.as_str()), entry.clone());
264 }
265 continue;
266 }
267 self.entries.insert(id.clone(), entry.clone());
268 }
269 }
270
271 #[must_use]
273 pub fn list(&self) -> Vec<&RegistryEntry> {
274 self.entries.values().collect()
275 }
276}