1use std::borrow::Cow;
11use std::fmt;
12
13use ergo_runtime::common::{doc_anchor_for_rule, ErrorInfo, Phase};
14#[derive(Debug)]
15#[non_exhaustive]
16pub enum InvalidAdapter {
17 InvalidId {
18 id: String,
19 },
20 InvalidVersion {
21 version: String,
22 },
23 InvalidRuntimeCompatibility {
24 version: String,
25 },
26 IncompatibleRuntime {
27 required: String,
28 actual: String,
29 },
30 ProvidesNothing,
31 DuplicateContextKey {
32 name: String,
33 first_index: usize,
34 second_index: usize,
35 },
36 InvalidContextKeyType {
37 name: String,
38 got: String,
39 index: usize,
40 },
41 DuplicateEventKind {
42 name: String,
43 index: usize,
44 },
45 InvalidPayloadSchema {
46 event: String,
47 error: String,
48 index: usize,
49 },
50 NoCaptureFormat,
51 InvalidCaptureField {
52 field: String,
53 index: usize,
54 },
55 MissingWritableFlag {
56 key: String,
57 index: usize,
58 },
59 DuplicateEffectName {
60 name: String,
61 index: usize,
62 },
63 InvalidEffectSchema {
64 effect: String,
65 error: String,
66 index: usize,
67 },
68 WritableWithoutSetContext {
69 keys: Vec<String>,
70 },
71 WritableKeyNotCaptured {
72 key: String,
73 index: usize,
74 },
75 SetContextNotCaptured,
76 WritableKeyRequired {
77 key: String,
78 index: usize,
79 },
80 RequiredEventFieldNotProvided {
81 event: String,
82 field: String,
83 event_index: usize,
84 },
85 RequiredEventFieldTypeMismatch {
86 event: String,
87 field: String,
88 expected: String,
89 got: String,
90 event_index: usize,
91 },
92 EventPayloadSchemaNotObject {
93 event: String,
94 event_index: usize,
95 },
96 UnsupportedEventFieldType {
97 event: String,
98 field: String,
99 detail: String,
100 event_index: usize,
101 },
102}
103
104impl ErrorInfo for InvalidAdapter {
105 fn rule_id(&self) -> &'static str {
106 match self {
107 Self::InvalidId { .. } => "ADP-1",
108 Self::InvalidVersion { .. } => "ADP-2",
109 Self::InvalidRuntimeCompatibility { .. } => "ADP-3",
110 Self::IncompatibleRuntime { .. } => "ADP-3",
111 Self::ProvidesNothing => "ADP-4",
112 Self::DuplicateContextKey { .. } => "ADP-5",
113 Self::InvalidContextKeyType { .. } => "ADP-6",
114 Self::DuplicateEventKind { .. } => "ADP-7",
115 Self::InvalidPayloadSchema { .. } => "ADP-8",
116 Self::NoCaptureFormat => "ADP-9",
117 Self::InvalidCaptureField { .. } => "ADP-10",
118 Self::MissingWritableFlag { .. } => "ADP-11",
119 Self::DuplicateEffectName { .. } => "ADP-12",
120 Self::InvalidEffectSchema { .. } => "ADP-13",
121 Self::WritableWithoutSetContext { .. } => "ADP-14",
122 Self::WritableKeyNotCaptured { .. } => "ADP-15",
123 Self::SetContextNotCaptured => "ADP-16",
124 Self::WritableKeyRequired { .. } => "ADP-17",
125 Self::RequiredEventFieldNotProvided { .. } => "ADP-18",
126 Self::RequiredEventFieldTypeMismatch { .. } => "ADP-18",
127 Self::EventPayloadSchemaNotObject { .. } => "ADP-19",
128 Self::UnsupportedEventFieldType { .. } => "ADP-19",
129 }
130 }
131
132 fn phase(&self) -> Phase {
133 Phase::Registration
134 }
135
136 fn doc_anchor(&self) -> &'static str {
137 doc_anchor_for_rule(self.rule_id())
138 }
139
140 fn summary(&self) -> Cow<'static, str> {
141 match self {
142 Self::InvalidId { id } => Cow::Owned(format!("Invalid adapter ID: '{}'", id)),
143 Self::InvalidVersion { version } => {
144 Cow::Owned(format!("Invalid version: '{}'", version))
145 }
146 Self::InvalidRuntimeCompatibility { version } => Cow::Owned(format!(
147 "Invalid runtime_compatibility: '{}' (must be semver)",
148 version
149 )),
150 Self::IncompatibleRuntime { required, actual } => {
151 Cow::Owned(format!("runtime {} < required {}", actual, required))
152 }
153 Self::ProvidesNothing => Cow::Borrowed("Adapter provides no context keys or events"),
154 Self::DuplicateContextKey { name, .. } => {
155 Cow::Owned(format!("Duplicate context key: '{}'", name))
156 }
157 Self::InvalidContextKeyType { name, got, .. } => {
158 Cow::Owned(format!("Context key '{}' has invalid type '{}'", name, got))
159 }
160 Self::DuplicateEventKind { name, .. } => {
161 Cow::Owned(format!("Duplicate event kind: '{}'", name))
162 }
163 Self::InvalidPayloadSchema { event, error, .. } => Cow::Owned(format!(
164 "Invalid payload schema for event '{}': {}",
165 event, error
166 )),
167 Self::NoCaptureFormat => Cow::Borrowed("Capture format_version is empty or invalid"),
168 Self::InvalidCaptureField { field, .. } => Cow::Owned(format!(
169 "Capture field '{}' is not in CaptureFieldSet",
170 field
171 )),
172 Self::MissingWritableFlag { key, .. } => Cow::Owned(format!(
173 "Context key '{}' is missing the 'writable' field",
174 key
175 )),
176 Self::DuplicateEffectName { name, .. } => {
177 Cow::Owned(format!("Duplicate effect name: '{}'", name))
178 }
179 Self::InvalidEffectSchema { effect, error, .. } => Cow::Owned(format!(
180 "Invalid payload schema for effect '{}': {}",
181 effect, error
182 )),
183 Self::WritableWithoutSetContext { keys } => Cow::Owned(format!(
184 "Writable keys {:?} declared but adapter has no set_context effect",
185 keys
186 )),
187 Self::WritableKeyNotCaptured { key, .. } => Cow::Owned(format!(
188 "Writable key '{}' must be captured for replay determinism",
189 key
190 )),
191 Self::SetContextNotCaptured => {
192 Cow::Borrowed("set_context effect must be captured when writable keys exist")
193 }
194 Self::WritableKeyRequired { key, .. } => {
195 Cow::Owned(format!("Writable key '{}' cannot have required: true", key))
196 }
197 Self::RequiredEventFieldNotProvided { event, field, .. } => Cow::Owned(format!(
198 "Required event field '{}.{}' is not declared in adapter context_keys",
199 event, field
200 )),
201 Self::RequiredEventFieldTypeMismatch {
202 event,
203 field,
204 expected,
205 got,
206 ..
207 } => Cow::Owned(format!(
208 "Required event field '{}.{}' type mismatch: context key is '{}', schema requires '{}'",
209 event, field, got, expected
210 )),
211 Self::EventPayloadSchemaNotObject { event, .. } => Cow::Owned(format!(
212 "Event '{}' payload_schema must be an object schema for context materialization",
213 event
214 )),
215 Self::UnsupportedEventFieldType {
216 event, field, detail, ..
217 } => Cow::Owned(format!(
218 "Event field '{}.{}' uses unsupported materialization type: {}",
219 event, field, detail
220 )),
221 }
222 }
223
224 fn path(&self) -> Option<Cow<'static, str>> {
225 match self {
226 Self::InvalidId { .. } => Some(Cow::Borrowed("$.id")),
227 Self::InvalidVersion { .. } => Some(Cow::Borrowed("$.version")),
228 Self::InvalidRuntimeCompatibility { .. } => {
229 Some(Cow::Borrowed("$.runtime_compatibility"))
230 }
231 Self::IncompatibleRuntime { .. } => Some(Cow::Borrowed("$.runtime_compatibility")),
232 Self::ProvidesNothing => None,
233 Self::DuplicateContextKey { second_index, .. } => {
234 Some(Cow::Owned(format!("$.context_keys[{}].name", second_index)))
235 }
236 Self::InvalidContextKeyType { index, .. } => {
237 Some(Cow::Owned(format!("$.context_keys[{}].type", index)))
238 }
239 Self::DuplicateEventKind { index, .. } => {
240 Some(Cow::Owned(format!("$.event_kinds[{}].name", index)))
241 }
242 Self::InvalidPayloadSchema { index, .. } => Some(Cow::Owned(format!(
243 "$.event_kinds[{}].payload_schema",
244 index
245 ))),
246 Self::NoCaptureFormat => Some(Cow::Borrowed("$.capture.format_version")),
247 Self::InvalidCaptureField { index, .. } => {
248 Some(Cow::Owned(format!("$.capture.fields[{}]", index)))
249 }
250 Self::MissingWritableFlag { index, .. } => {
251 Some(Cow::Owned(format!("$.context_keys[{}].writable", index)))
252 }
253 Self::DuplicateEffectName { index, .. } => {
254 Some(Cow::Owned(format!("$.accepts.effects[{}].name", index)))
255 }
256 Self::InvalidEffectSchema { index, .. } => Some(Cow::Owned(format!(
257 "$.accepts.effects[{}].payload_schema",
258 index
259 ))),
260 Self::WritableWithoutSetContext { .. } => Some(Cow::Borrowed("$.accepts.effects")),
261 Self::WritableKeyNotCaptured { index, .. } => {
262 Some(Cow::Owned(format!("$.context_keys[{}]", index)))
263 }
264 Self::SetContextNotCaptured => Some(Cow::Borrowed("$.capture.fields")),
265 Self::WritableKeyRequired { index, .. } => {
266 Some(Cow::Owned(format!("$.context_keys[{}]", index)))
267 }
268 Self::RequiredEventFieldNotProvided {
269 event_index, field, ..
270 } => Some(Cow::Owned(format!(
271 "$.event_kinds[{}].payload_schema.properties.{}",
272 event_index, field
273 ))),
274 Self::RequiredEventFieldTypeMismatch {
275 event_index, field, ..
276 } => Some(Cow::Owned(format!(
277 "$.event_kinds[{}].payload_schema.properties.{}",
278 event_index, field
279 ))),
280 Self::EventPayloadSchemaNotObject { event_index, .. } => Some(Cow::Owned(format!(
281 "$.event_kinds[{}].payload_schema",
282 event_index
283 ))),
284 Self::UnsupportedEventFieldType {
285 event_index, field, ..
286 } => Some(Cow::Owned(format!(
287 "$.event_kinds[{}].payload_schema.properties.{}",
288 event_index, field
289 ))),
290 }
291 }
292
293 fn fix(&self) -> Option<Cow<'static, str>> {
294 match self {
295 Self::InvalidId { .. } => Some(Cow::Borrowed(
296 "ID must start with lowercase letter, contain only lowercase letters, digits, and underscores (no hyphens)",
297 )),
298 Self::InvalidVersion { .. } => Some(Cow::Borrowed(
299 "Version must be valid semver (e.g., '1.0.0')",
300 )),
301 Self::InvalidRuntimeCompatibility { .. } => Some(Cow::Borrowed(
302 "runtime_compatibility must be valid semver (e.g., '0.1.0')",
303 )),
304 Self::IncompatibleRuntime { required, .. } => {
305 Some(Cow::Owned(format!(
306 "upgrade runtime to {} or higher",
307 required
308 )))
309 }
310 Self::ProvidesNothing => Some(Cow::Borrowed(
311 "Add at least one context_key or event_kind",
312 )),
313 Self::DuplicateContextKey { name, .. } => Some(Cow::Owned(format!(
314 "Rename '{}' to a unique value",
315 name
316 ))),
317 Self::InvalidContextKeyType { got, .. } => Some(Cow::Owned(format!(
318 "Type '{}' is not valid; use Number, Bool, String, or Series",
319 got
320 ))),
321 Self::DuplicateEventKind { name, .. } => Some(Cow::Owned(format!(
322 "Rename event kind '{}' to a unique value",
323 name
324 ))),
325 Self::InvalidPayloadSchema { .. } => Some(Cow::Borrowed(
326 "Provide a valid JSON Schema (Draft 2020-12)",
327 )),
328 Self::NoCaptureFormat => Some(Cow::Borrowed(
329 "Set capture.format_version to a non-empty string",
330 )),
331 Self::InvalidCaptureField { field, .. } => Some(Cow::Owned(format!(
332 "'{}' is not in CaptureFieldSet; valid selectors: event.<kind>, meta.adapter_id, meta.adapter_version, meta.timestamp",
333 field
334 ))),
335 Self::MissingWritableFlag { key, .. } => Some(Cow::Owned(format!(
336 "Add 'writable: true' or 'writable: false' to context key '{}'",
337 key
338 ))),
339 Self::DuplicateEffectName { name, .. } => Some(Cow::Owned(format!(
340 "Rename effect '{}' to a unique value",
341 name
342 ))),
343 Self::InvalidEffectSchema { .. } => Some(Cow::Borrowed(
344 "Provide a valid JSON Schema (Draft 2020-12)",
345 )),
346 Self::WritableWithoutSetContext { .. } => Some(Cow::Borrowed(
347 "Add 'set_context' to accepts.effects when using writable keys",
348 )),
349 Self::WritableKeyNotCaptured { .. } => None,
351 Self::SetContextNotCaptured => None,
353 Self::WritableKeyRequired { key, .. } => Some(Cow::Owned(format!(
354 "Set 'required: false' on writable key '{}'",
355 key
356 ))),
357 Self::RequiredEventFieldNotProvided { field, .. } => Some(Cow::Owned(format!(
358 "Add context key '{}' to context_keys with matching type",
359 field
360 ))),
361 Self::RequiredEventFieldTypeMismatch {
362 field, expected, ..
363 } => Some(Cow::Owned(format!(
364 "Change context key '{}' type to '{}'",
365 field, expected
366 ))),
367 Self::EventPayloadSchemaNotObject { .. } => Some(Cow::Borrowed(
368 "Set event payload_schema.type to 'object' and declare properties",
369 )),
370 Self::UnsupportedEventFieldType { .. } => Some(Cow::Borrowed(
371 "Use field types that map to runtime values: number/integer, boolean, string, or array of numbers",
372 )),
373 }
374 }
375}
376
377impl fmt::Display for InvalidAdapter {
378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379 write!(f, "[{}] {}", self.rule_id(), self.summary())
380 }
381}
382
383impl std::error::Error for InvalidAdapter {}