1use schemars::{JsonSchema, generate::SchemaSettings};
7use serde_json::Value;
8
9use crate::{
10 ActionCall, ActionDefinition, ActionExecution, ActionOutcome, ActionProtection, ActionResult,
11 AssetRef, ClearElementArguments, ClearElementResult, CoordinateFallbackReason,
12 DeviceCapabilitiesResult, DeviceConnectResult, DeviceDisconnectResult, DeviceExecuteParams,
13 DeviceExecuteResult, DeviceId, DeviceInfo, DeviceObserveResult, DeviceSelectParams,
14 DeviceSelectResult, DevicesListResult, ElementSelector, ElementTarget, ErrorInfo, EventId,
15 EventSequence, EventStreamCursor, EventStreamEndpoint, EventStreamEpoch,
16 EventStreamOriginPolicy, EventSubscriptionId, EventsClearResult, EventsListParams,
17 EventsListResult, EventsStreamEventNotification, EventsStreamEventParams,
18 EventsStreamOpenParams, EventsStreamOpenResult, EventsStreamTerminalNotification,
19 EventsStreamTerminalParams, EventsStreamTermination, EventsSubscribeParams,
20 EventsSubscribeResult, FeatureOffer, FeatureSelection, FindElementArguments, FindElementResult,
21 HelloParams, HelloResult, JsonRpcVersion, ManualActionArguments, ManualActionStep,
22 ManualRecording, MediaFrame, MediaStreamCaptureParams, MediaStreamCaptureResult,
23 MediaStreamEndParams, MediaStreamEndResult, MediaStreamId, MediaStreamInfo, MediaStreamKind,
24 MediaStreamStartParams, MediaStreamStartResult, Observation, PeerInfo, Platform,
25 ProtocolIncompatibilityReason, ProtocolOffer, ProtocolRange, ProtocolSelection,
26 ProtocolVersion, RecordedActionCall, RequestCancelParams, RequestCancelResult,
27 RequestCancelStatus, RequestTimeoutMs, RpcError, RpcId, RpcParams, RpcRequest, RpcResponse,
28 RpcServerMessage, RpcServerNotification, ScreenshotOmissionReason, SessionCurrentResult,
29 SessionEndParams, SessionEndResult, SessionExport, SessionExportParams, SessionExportResult,
30 SessionId, SessionInfo, SessionOutcome, SessionStartResult, SessionState, SessionTargetParams,
31 SessionsListResult, SetElementValueArguments, SetElementValueResult, SystemDescribeResult,
32 TapElementArguments, TapElementResult, TestEvent, TestEventPayload, TextMatch, TextMatchMode,
33 TransportInfo, UiContextKind, UiContextRef, UiContextSelector, UiNode, UiNodeRef, UiRect,
34 UiSnapshot, UiSnapshotGetParams, UiSnapshotGetResult, UiSnapshotOmissionReason, UiSnapshotRef,
35 Verdict, VerdictRecordParams, VerdictRecordResult, VerdictStatus, Viewport,
36 WaitForElementArguments, WaitForElementCondition, WaitForElementResult,
37};
38
39pub const SCHEMA_SET_VERSION: u16 = 1;
40pub const SCHEMA_DIRECTORY: &str = "v1";
41pub const JSON_SCHEMA_DRAFT: &str = "https://json-schema.org/draft/2020-12/schema";
42
43#[derive(Clone, Debug, PartialEq)]
44pub struct SchemaDocument {
45 pub name: &'static str,
46 pub file_name: &'static str,
47 pub schema_id: String,
48 pub schema: Value,
49}
50
51pub fn documents() -> Vec<SchemaDocument> {
53 vec![
54 document::<ActionCall>("ActionCall", "action-call.schema.json"),
55 document::<ActionDefinition>("ActionDefinition", "action-definition.schema.json"),
56 document::<ActionExecution>("ActionExecution", "action-execution.schema.json"),
57 document::<ActionOutcome>("ActionOutcome", "action-outcome.schema.json"),
58 document::<ActionProtection>("ActionProtection", "action-protection.schema.json"),
59 document::<ActionResult>("ActionResult", "action-result.schema.json"),
60 document::<AssetRef>("AssetRef", "asset-ref.schema.json"),
61 document::<ClearElementArguments>(
62 "ClearElementArguments",
63 "clear-element-arguments.schema.json",
64 ),
65 document::<ClearElementResult>("ClearElementResult", "clear-element-result.schema.json"),
66 document::<CoordinateFallbackReason>(
67 "CoordinateFallbackReason",
68 "coordinate-fallback-reason.schema.json",
69 ),
70 document::<DeviceCapabilitiesRequestSchema>(
71 "DeviceCapabilitiesRequest",
72 "device-capabilities-request.schema.json",
73 ),
74 document::<DeviceCapabilitiesResponseSchema>(
75 "DeviceCapabilitiesResponse",
76 "device-capabilities-response.schema.json",
77 ),
78 document::<DeviceCapabilitiesResult>(
79 "DeviceCapabilitiesResult",
80 "device-capabilities-result.schema.json",
81 ),
82 document::<DeviceConnectRequestSchema>(
83 "DeviceConnectRequest",
84 "device-connect-request.schema.json",
85 ),
86 document::<DeviceConnectResponseSchema>(
87 "DeviceConnectResponse",
88 "device-connect-response.schema.json",
89 ),
90 document::<DeviceConnectResult>("DeviceConnectResult", "device-connect-result.schema.json"),
91 document::<DeviceDisconnectRequestSchema>(
92 "DeviceDisconnectRequest",
93 "device-disconnect-request.schema.json",
94 ),
95 document::<DeviceDisconnectResponseSchema>(
96 "DeviceDisconnectResponse",
97 "device-disconnect-response.schema.json",
98 ),
99 document::<DeviceDisconnectResult>(
100 "DeviceDisconnectResult",
101 "device-disconnect-result.schema.json",
102 ),
103 document::<DeviceExecuteParams>("DeviceExecuteParams", "device-execute-params.schema.json"),
104 document::<DeviceExecuteRequestSchema>(
105 "DeviceExecuteRequest",
106 "device-execute-request.schema.json",
107 ),
108 document::<DeviceExecuteResponseSchema>(
109 "DeviceExecuteResponse",
110 "device-execute-response.schema.json",
111 ),
112 document::<DeviceExecuteResult>("DeviceExecuteResult", "device-execute-result.schema.json"),
113 document::<DeviceId>("DeviceId", "device-id.schema.json"),
114 document::<DeviceInfo>("DeviceInfo", "device-info.schema.json"),
115 document::<DeviceObserveRequestSchema>(
116 "DeviceObserveRequest",
117 "device-observe-request.schema.json",
118 ),
119 document::<DeviceObserveResponseSchema>(
120 "DeviceObserveResponse",
121 "device-observe-response.schema.json",
122 ),
123 document::<DeviceObserveResult>("DeviceObserveResult", "device-observe-result.schema.json"),
124 document::<DeviceSelectParams>("DeviceSelectParams", "device-select-params.schema.json"),
125 document::<DeviceSelectRequestSchema>(
126 "DeviceSelectRequest",
127 "device-select-request.schema.json",
128 ),
129 document::<DeviceSelectResponseSchema>(
130 "DeviceSelectResponse",
131 "device-select-response.schema.json",
132 ),
133 document::<DeviceSelectResult>("DeviceSelectResult", "device-select-result.schema.json"),
134 document::<DevicesListRequestSchema>(
135 "DevicesListRequest",
136 "devices-list-request.schema.json",
137 ),
138 document::<DevicesListResponseSchema>(
139 "DevicesListResponse",
140 "devices-list-response.schema.json",
141 ),
142 document::<DevicesListResult>("DevicesListResult", "devices-list-result.schema.json"),
143 document::<ElementSelector>("ElementSelector", "element-selector.schema.json"),
144 document::<ElementTarget>("ElementTarget", "element-target.schema.json"),
145 document::<ErrorInfo>("ErrorInfo", "error-info.schema.json"),
146 document::<EventId>("EventId", "event-id.schema.json"),
147 document::<EventSequence>("EventSequence", "event-sequence.schema.json"),
148 document::<EventStreamCursor>("EventStreamCursor", "event-stream-cursor.schema.json"),
149 document::<EventStreamEndpoint>("EventStreamEndpoint", "event-stream-endpoint.schema.json"),
150 document::<EventStreamEpoch>("EventStreamEpoch", "event-stream-epoch.schema.json"),
151 document::<EventStreamOriginPolicy>(
152 "EventStreamOriginPolicy",
153 "event-stream-origin-policy.schema.json",
154 ),
155 document::<EventSubscriptionId>("EventSubscriptionId", "event-subscription-id.schema.json"),
156 document::<EventsClearRequestSchema>(
157 "EventsClearRequest",
158 "events-clear-request.schema.json",
159 ),
160 document::<EventsClearResponseSchema>(
161 "EventsClearResponse",
162 "events-clear-response.schema.json",
163 ),
164 document::<EventsClearResult>("EventsClearResult", "events-clear-result.schema.json"),
165 document::<EventsListParams>("EventsListParams", "events-list-params.schema.json"),
166 document::<EventsListRequestSchema>("EventsListRequest", "events-list-request.schema.json"),
167 document::<EventsListResponseSchema>(
168 "EventsListResponse",
169 "events-list-response.schema.json",
170 ),
171 document::<EventsListResult>("EventsListResult", "events-list-result.schema.json"),
172 document::<EventsStreamEventNotification>(
173 "EventsStreamEventNotification",
174 "events-stream-event-notification.schema.json",
175 ),
176 document::<EventsStreamEventParams>(
177 "EventsStreamEventParams",
178 "events-stream-event-params.schema.json",
179 ),
180 document::<EventsStreamOpenParams>(
181 "EventsStreamOpenParams",
182 "events-stream-open-params.schema.json",
183 ),
184 document::<EventsStreamOpenRequestSchema>(
185 "EventsStreamOpenRequest",
186 "events-stream-open-request.schema.json",
187 ),
188 document::<EventsStreamOpenResponseSchema>(
189 "EventsStreamOpenResponse",
190 "events-stream-open-response.schema.json",
191 ),
192 document::<EventsStreamOpenResult>(
193 "EventsStreamOpenResult",
194 "events-stream-open-result.schema.json",
195 ),
196 document::<EventsStreamTerminalNotification>(
197 "EventsStreamTerminalNotification",
198 "events-stream-terminal-notification.schema.json",
199 ),
200 document::<EventsStreamTerminalParams>(
201 "EventsStreamTerminalParams",
202 "events-stream-terminal-params.schema.json",
203 ),
204 document::<EventsStreamTermination>(
205 "EventsStreamTermination",
206 "events-stream-termination.schema.json",
207 ),
208 document::<EventsSubscribeParams>(
209 "EventsSubscribeParams",
210 "events-subscribe-params.schema.json",
211 ),
212 document::<EventsSubscribeRequestSchema>(
213 "EventsSubscribeRequest",
214 "events-subscribe-request.schema.json",
215 ),
216 document::<EventsSubscribeResponseSchema>(
217 "EventsSubscribeResponse",
218 "events-subscribe-response.schema.json",
219 ),
220 document::<EventsSubscribeResult>(
221 "EventsSubscribeResult",
222 "events-subscribe-result.schema.json",
223 ),
224 document::<FeatureOffer>("FeatureOffer", "feature-offer.schema.json"),
225 document::<FeatureSelection>("FeatureSelection", "feature-selection.schema.json"),
226 document::<FindElementArguments>(
227 "FindElementArguments",
228 "find-element-arguments.schema.json",
229 ),
230 document::<FindElementResult>("FindElementResult", "find-element-result.schema.json"),
231 document::<HelloParams>("HelloParams", "hello-params.schema.json"),
232 document::<HelloResult>("HelloResult", "hello-result.schema.json"),
233 document::<JsonRpcVersion>("JsonRpcVersion", "json-rpc-version.schema.json"),
234 document::<ManualActionArguments>(
235 "ManualActionArguments",
236 "manual-action-arguments.schema.json",
237 ),
238 document::<ManualActionStep>("ManualActionStep", "manual-action-step.schema.json"),
239 document::<ManualRecording>("ManualRecording", "manual-recording.schema.json"),
240 document::<MediaFrame>("MediaFrame", "media-frame.schema.json"),
241 document::<MediaStreamCaptureParams>(
242 "MediaStreamCaptureParams",
243 "media-stream-capture-params.schema.json",
244 ),
245 document::<MediaStreamCaptureRequestSchema>(
246 "MediaStreamCaptureRequest",
247 "media-stream-capture-request.schema.json",
248 ),
249 document::<MediaStreamCaptureResponseSchema>(
250 "MediaStreamCaptureResponse",
251 "media-stream-capture-response.schema.json",
252 ),
253 document::<MediaStreamCaptureResult>(
254 "MediaStreamCaptureResult",
255 "media-stream-capture-result.schema.json",
256 ),
257 document::<MediaStreamEndParams>(
258 "MediaStreamEndParams",
259 "media-stream-end-params.schema.json",
260 ),
261 document::<MediaStreamEndRequestSchema>(
262 "MediaStreamEndRequest",
263 "media-stream-end-request.schema.json",
264 ),
265 document::<MediaStreamEndResponseSchema>(
266 "MediaStreamEndResponse",
267 "media-stream-end-response.schema.json",
268 ),
269 document::<MediaStreamEndResult>(
270 "MediaStreamEndResult",
271 "media-stream-end-result.schema.json",
272 ),
273 document::<MediaStreamId>("MediaStreamId", "media-stream-id.schema.json"),
274 document::<MediaStreamInfo>("MediaStreamInfo", "media-stream-info.schema.json"),
275 document::<MediaStreamKind>("MediaStreamKind", "media-stream-kind.schema.json"),
276 document::<MediaStreamStartParams>(
277 "MediaStreamStartParams",
278 "media-stream-start-params.schema.json",
279 ),
280 document::<MediaStreamStartRequestSchema>(
281 "MediaStreamStartRequest",
282 "media-stream-start-request.schema.json",
283 ),
284 document::<MediaStreamStartResponseSchema>(
285 "MediaStreamStartResponse",
286 "media-stream-start-response.schema.json",
287 ),
288 document::<MediaStreamStartResult>(
289 "MediaStreamStartResult",
290 "media-stream-start-result.schema.json",
291 ),
292 document::<Observation>("Observation", "observation.schema.json"),
293 document::<PeerInfo>("PeerInfo", "peer-info.schema.json"),
294 document::<Platform>("Platform", "platform.schema.json"),
295 document::<ProtocolIncompatibilityReason>(
296 "ProtocolIncompatibilityReason",
297 "protocol-incompatibility-reason.schema.json",
298 ),
299 document::<ProtocolOffer>("ProtocolOffer", "protocol-offer.schema.json"),
300 document::<ProtocolRange>("ProtocolRange", "protocol-range.schema.json"),
301 document::<ProtocolSelection>("ProtocolSelection", "protocol-selection.schema.json"),
302 document::<ProtocolVersion>("ProtocolVersion", "protocol-version.schema.json"),
303 document::<RecordedActionCall>("RecordedActionCall", "recorded-action-call.schema.json"),
304 document::<RequestCancelParams>("RequestCancelParams", "request-cancel-params.schema.json"),
305 document::<RequestCancelRequestSchema>(
306 "RequestCancelRequest",
307 "request-cancel-request.schema.json",
308 ),
309 document::<RequestCancelResponseSchema>(
310 "RequestCancelResponse",
311 "request-cancel-response.schema.json",
312 ),
313 document::<RequestCancelResult>("RequestCancelResult", "request-cancel-result.schema.json"),
314 document::<RequestCancelStatus>("RequestCancelStatus", "request-cancel-status.schema.json"),
315 document::<RequestTimeoutMs>("RequestTimeoutMs", "request-timeout-ms.schema.json"),
316 document::<RpcError>("RpcError", "rpc-error.schema.json"),
317 document::<RpcId>("RpcId", "rpc-id.schema.json"),
318 document::<RpcParams>("RpcParams", "rpc-params.schema.json"),
319 document::<RpcRequest>("RpcRequest", "rpc-request.schema.json"),
320 document::<RpcResponse>("RpcResponse", "rpc-response.schema.json"),
321 document::<RpcServerMessage>("RpcServerMessage", "rpc-server-message.schema.json"),
322 document::<RpcServerNotification>(
323 "RpcServerNotification",
324 "rpc-server-notification.schema.json",
325 ),
326 document::<ScreenshotOmissionReason>(
327 "ScreenshotOmissionReason",
328 "screenshot-omission-reason.schema.json",
329 ),
330 document::<SessionCurrentRequestSchema>(
331 "SessionCurrentRequest",
332 "session-current-request.schema.json",
333 ),
334 document::<SessionCurrentResponseSchema>(
335 "SessionCurrentResponse",
336 "session-current-response.schema.json",
337 ),
338 document::<SessionCurrentResult>(
339 "SessionCurrentResult",
340 "session-current-result.schema.json",
341 ),
342 document::<SessionEndParams>("SessionEndParams", "session-end-params.schema.json"),
343 document::<SessionEndRequestSchema>("SessionEndRequest", "session-end-request.schema.json"),
344 document::<SessionEndResponseSchema>(
345 "SessionEndResponse",
346 "session-end-response.schema.json",
347 ),
348 document::<SessionEndResult>("SessionEndResult", "session-end-result.schema.json"),
349 document::<SessionExportParams>("SessionExportParams", "session-export-params.schema.json"),
350 document::<SessionExportRequestSchema>(
351 "SessionExportRequest",
352 "session-export-request.schema.json",
353 ),
354 document::<SessionExportResponseSchema>(
355 "SessionExportResponse",
356 "session-export-response.schema.json",
357 ),
358 document::<SessionExportResult>("SessionExportResult", "session-export-result.schema.json"),
359 document::<SessionExport>("SessionExport", "session-export.schema.json"),
360 document::<SessionId>("SessionId", "session-id.schema.json"),
361 document::<SessionInfo>("SessionInfo", "session-info.schema.json"),
362 document::<SessionOutcome>("SessionOutcome", "session-outcome.schema.json"),
363 document::<SessionStartRequestSchema>(
364 "SessionStartRequest",
365 "session-start-request.schema.json",
366 ),
367 document::<SessionStartResponseSchema>(
368 "SessionStartResponse",
369 "session-start-response.schema.json",
370 ),
371 document::<SessionStartResult>("SessionStartResult", "session-start-result.schema.json"),
372 document::<SessionState>("SessionState", "session-state.schema.json"),
373 document::<SessionTargetParams>("SessionTargetParams", "session-target-params.schema.json"),
374 document::<SessionsListRequestSchema>(
375 "SessionsListRequest",
376 "sessions-list-request.schema.json",
377 ),
378 document::<SessionsListResponseSchema>(
379 "SessionsListResponse",
380 "sessions-list-response.schema.json",
381 ),
382 document::<SessionsListResult>("SessionsListResult", "sessions-list-result.schema.json"),
383 document::<SetElementValueArguments>(
384 "SetElementValueArguments",
385 "set-element-value-arguments.schema.json",
386 ),
387 document::<SetElementValueResult>(
388 "SetElementValueResult",
389 "set-element-value-result.schema.json",
390 ),
391 document::<SystemDescribeRequestSchema>(
392 "SystemDescribeRequest",
393 "system-describe-request.schema.json",
394 ),
395 document::<SystemDescribeResponseSchema>(
396 "SystemDescribeResponse",
397 "system-describe-response.schema.json",
398 ),
399 document::<SystemDescribeResult>(
400 "SystemDescribeResult",
401 "system-describe-result.schema.json",
402 ),
403 document::<SystemHelloRequestSchema>(
404 "SystemHelloRequest",
405 "system-hello-request.schema.json",
406 ),
407 document::<SystemHelloResponseSchema>(
408 "SystemHelloResponse",
409 "system-hello-response.schema.json",
410 ),
411 document::<TapElementArguments>("TapElementArguments", "tap-element-arguments.schema.json"),
412 document::<TapElementResult>("TapElementResult", "tap-element-result.schema.json"),
413 document::<TestEventPayload>("TestEventPayload", "test-event-payload.schema.json"),
414 document::<TestEvent>("TestEvent", "test-event.schema.json"),
415 document::<TextMatchMode>("TextMatchMode", "text-match-mode.schema.json"),
416 document::<TextMatch>("TextMatch", "text-match.schema.json"),
417 document::<TransportInfo>("TransportInfo", "transport-info.schema.json"),
418 document::<UiContextKind>("UiContextKind", "ui-context-kind.schema.json"),
419 document::<UiContextRef>("UiContextRef", "ui-context-ref.schema.json"),
420 document::<UiContextSelector>("UiContextSelector", "ui-context-selector.schema.json"),
421 document::<UiNodeRef>("UiNodeRef", "ui-node-ref.schema.json"),
422 document::<UiNode>("UiNode", "ui-node.schema.json"),
423 document::<UiRect>("UiRect", "ui-rect.schema.json"),
424 document::<UiSnapshotGetParams>(
425 "UiSnapshotGetParams",
426 "ui-snapshot-get-params.schema.json",
427 ),
428 document::<UiSnapshotGetRequestSchema>(
429 "UiSnapshotGetRequest",
430 "ui-snapshot-get-request.schema.json",
431 ),
432 document::<UiSnapshotGetResponseSchema>(
433 "UiSnapshotGetResponse",
434 "ui-snapshot-get-response.schema.json",
435 ),
436 document::<UiSnapshotGetResult>(
437 "UiSnapshotGetResult",
438 "ui-snapshot-get-result.schema.json",
439 ),
440 document::<UiSnapshotOmissionReason>(
441 "UiSnapshotOmissionReason",
442 "ui-snapshot-omission-reason.schema.json",
443 ),
444 document::<UiSnapshotRef>("UiSnapshotRef", "ui-snapshot-ref.schema.json"),
445 document::<UiSnapshot>("UiSnapshot", "ui-snapshot.schema.json"),
446 document::<VerdictRecordParams>("VerdictRecordParams", "verdict-record-params.schema.json"),
447 document::<VerdictRecordRequestSchema>(
448 "VerdictRecordRequest",
449 "verdict-record-request.schema.json",
450 ),
451 document::<VerdictRecordResponseSchema>(
452 "VerdictRecordResponse",
453 "verdict-record-response.schema.json",
454 ),
455 document::<VerdictRecordResult>("VerdictRecordResult", "verdict-record-result.schema.json"),
456 document::<VerdictStatus>("VerdictStatus", "verdict-status.schema.json"),
457 document::<Verdict>("Verdict", "verdict.schema.json"),
458 document::<Viewport>("Viewport", "viewport.schema.json"),
459 document::<WaitForElementArguments>(
460 "WaitForElementArguments",
461 "wait-for-element-arguments.schema.json",
462 ),
463 document::<WaitForElementCondition>(
464 "WaitForElementCondition",
465 "wait-for-element-condition.schema.json",
466 ),
467 document::<WaitForElementResult>(
468 "WaitForElementResult",
469 "wait-for-element-result.schema.json",
470 ),
471 ]
472}
473
474fn document<T: JsonSchema>(name: &'static str, file_name: &'static str) -> SchemaDocument {
475 let schema_id = format!(
476 "urn:devicerail:schema:protocol:v{SCHEMA_SET_VERSION}:{}",
477 file_name.trim_end_matches(".schema.json")
478 );
479 let root = SchemaSettings::draft2020_12()
480 .for_deserialize()
481 .into_generator()
482 .into_root_schema_for::<T>();
483 let mut schema = serde_json::to_value(root).expect("schemars schema serializes to JSON");
484 let root = schema
485 .as_object_mut()
486 .expect("schemars root schema is an object");
487 root.insert("$id".to_owned(), Value::String(schema_id.clone()));
488 root.insert("title".to_owned(), Value::String(name.to_owned()));
489
490 SchemaDocument {
491 name,
492 file_name,
493 schema_id,
494 schema,
495 }
496}
497
498#[allow(dead_code)]
499#[derive(JsonSchema)]
500#[schemars(rename = "SystemHelloRequest")]
501#[serde(rename_all = "camelCase", deny_unknown_fields)]
502struct SystemHelloRequestSchema {
503 jsonrpc: JsonRpcVersion,
504 id: RpcId,
505 method: SystemHelloMethodSchema,
506 params: HelloParams,
507}
508
509#[allow(dead_code)]
510#[derive(JsonSchema)]
511enum SystemHelloMethodSchema {
512 #[serde(rename = "system.hello")]
513 SystemHello,
514}
515
516macro_rules! no_params_request_schema {
517 ($request:ident, $method_type:ident, $method_variant:ident, $title:literal, $method:literal) => {
518 #[allow(dead_code)]
519 #[derive(JsonSchema)]
520 #[schemars(rename = $title)]
521 #[serde(rename_all = "camelCase", deny_unknown_fields)]
522 struct $request {
523 jsonrpc: JsonRpcVersion,
524 id: RpcId,
525 method: $method_type,
526 #[serde(default, skip_serializing_if = "Option::is_none")]
527 #[schemars(with = "NoParamsSchema")]
528 params: Option<NoParamsSchema>,
529 }
530
531 #[allow(dead_code)]
532 #[derive(JsonSchema)]
533 enum $method_type {
534 #[serde(rename = $method)]
535 $method_variant,
536 }
537 };
538}
539
540macro_rules! timeout_no_params_request_schema {
541 ($request:ident, $method_type:ident, $method_variant:ident, $title:literal, $method:literal) => {
542 #[allow(dead_code)]
543 #[derive(JsonSchema)]
544 #[schemars(rename = $title)]
545 #[serde(rename_all = "camelCase", deny_unknown_fields)]
546 struct $request {
547 jsonrpc: JsonRpcVersion,
548 id: RpcId,
549 method: $method_type,
550 #[serde(default, skip_serializing_if = "Option::is_none")]
551 #[schemars(with = "RequestTimeoutMs")]
552 timeout_ms: Option<RequestTimeoutMs>,
553 #[serde(default, skip_serializing_if = "Option::is_none")]
554 #[schemars(with = "NoParamsSchema")]
555 params: Option<NoParamsSchema>,
556 }
557
558 #[allow(dead_code)]
559 #[derive(JsonSchema)]
560 enum $method_type {
561 #[serde(rename = $method)]
562 $method_variant,
563 }
564 };
565}
566
567macro_rules! response_schema {
568 ($response:ident, $success:ident, $title:literal, $result:ty) => {
569 #[allow(dead_code)]
570 #[derive(JsonSchema)]
571 #[schemars(rename = $title)]
572 #[serde(untagged)]
573 enum $response {
574 Success(Box<$success>),
575 Failure(SystemHelloFailureSchema),
576 }
577
578 #[allow(dead_code)]
579 #[derive(JsonSchema)]
580 #[serde(rename_all = "camelCase", deny_unknown_fields)]
581 struct $success {
582 jsonrpc: JsonRpcVersion,
583 id: RpcId,
584 result: $result,
585 }
586 };
587}
588
589no_params_request_schema!(
590 SystemDescribeRequestSchema,
591 SystemDescribeMethodSchema,
592 SystemDescribe,
593 "SystemDescribeRequest",
594 "system.describe"
595);
596timeout_no_params_request_schema!(
597 DeviceConnectRequestSchema,
598 DeviceConnectMethodSchema,
599 DeviceConnect,
600 "DeviceConnectRequest",
601 "device.connect"
602);
603timeout_no_params_request_schema!(
604 DeviceDisconnectRequestSchema,
605 DeviceDisconnectMethodSchema,
606 DeviceDisconnect,
607 "DeviceDisconnectRequest",
608 "device.disconnect"
609);
610timeout_no_params_request_schema!(
611 DeviceCapabilitiesRequestSchema,
612 DeviceCapabilitiesMethodSchema,
613 DeviceCapabilities,
614 "DeviceCapabilitiesRequest",
615 "device.capabilities"
616);
617timeout_no_params_request_schema!(
618 DeviceObserveRequestSchema,
619 DeviceObserveMethodSchema,
620 DeviceObserve,
621 "DeviceObserveRequest",
622 "device.observe"
623);
624no_params_request_schema!(
625 SessionStartRequestSchema,
626 SessionStartMethodSchema,
627 SessionStart,
628 "SessionStartRequest",
629 "session.start"
630);
631no_params_request_schema!(
632 SessionCurrentRequestSchema,
633 SessionCurrentMethodSchema,
634 SessionCurrent,
635 "SessionCurrentRequest",
636 "session.current"
637);
638no_params_request_schema!(
639 SessionsListRequestSchema,
640 SessionsListMethodSchema,
641 SessionsList,
642 "SessionsListRequest",
643 "sessions.list"
644);
645
646response_schema!(
647 SystemDescribeResponseSchema,
648 SystemDescribeSuccessSchema,
649 "SystemDescribeResponse",
650 SystemDescribeResult
651);
652response_schema!(
653 DeviceConnectResponseSchema,
654 DeviceConnectSuccessSchema,
655 "DeviceConnectResponse",
656 DeviceConnectResult
657);
658response_schema!(
659 DeviceDisconnectResponseSchema,
660 DeviceDisconnectSuccessSchema,
661 "DeviceDisconnectResponse",
662 DeviceDisconnectResult
663);
664response_schema!(
665 DeviceCapabilitiesResponseSchema,
666 DeviceCapabilitiesSuccessSchema,
667 "DeviceCapabilitiesResponse",
668 DeviceCapabilitiesResult
669);
670response_schema!(
671 DeviceObserveResponseSchema,
672 DeviceObserveSuccessSchema,
673 "DeviceObserveResponse",
674 DeviceObserveResult
675);
676response_schema!(
677 DeviceExecuteResponseSchema,
678 DeviceExecuteSuccessSchema,
679 "DeviceExecuteResponse",
680 DeviceExecuteResult
681);
682response_schema!(
683 UiSnapshotGetResponseSchema,
684 UiSnapshotGetSuccessSchema,
685 "UiSnapshotGetResponse",
686 UiSnapshotGetResult
687);
688response_schema!(
689 VerdictRecordResponseSchema,
690 VerdictRecordSuccessSchema,
691 "VerdictRecordResponse",
692 VerdictRecordResult
693);
694response_schema!(
695 SessionStartResponseSchema,
696 SessionStartSuccessSchema,
697 "SessionStartResponse",
698 SessionStartResult
699);
700response_schema!(
701 SessionCurrentResponseSchema,
702 SessionCurrentSuccessSchema,
703 "SessionCurrentResponse",
704 SessionCurrentResult
705);
706response_schema!(
707 SessionEndResponseSchema,
708 SessionEndSuccessSchema,
709 "SessionEndResponse",
710 SessionEndResult
711);
712response_schema!(
713 SessionsListResponseSchema,
714 SessionsListSuccessSchema,
715 "SessionsListResponse",
716 SessionsListResult
717);
718response_schema!(
719 SessionExportResponseSchema,
720 SessionExportSuccessSchema,
721 "SessionExportResponse",
722 SessionExportResult
723);
724response_schema!(
725 EventsListResponseSchema,
726 EventsListSuccessSchema,
727 "EventsListResponse",
728 EventsListResult
729);
730response_schema!(
731 EventsStreamOpenResponseSchema,
732 EventsStreamOpenSuccessSchema,
733 "EventsStreamOpenResponse",
734 EventsStreamOpenResult
735);
736response_schema!(
737 EventsSubscribeResponseSchema,
738 EventsSubscribeSuccessSchema,
739 "EventsSubscribeResponse",
740 EventsSubscribeResult
741);
742response_schema!(
743 EventsClearResponseSchema,
744 EventsClearSuccessSchema,
745 "EventsClearResponse",
746 EventsClearResult
747);
748response_schema!(
749 MediaStreamCaptureResponseSchema,
750 MediaStreamCaptureSuccessSchema,
751 "MediaStreamCaptureResponse",
752 MediaStreamCaptureResult
753);
754response_schema!(
755 MediaStreamEndResponseSchema,
756 MediaStreamEndSuccessSchema,
757 "MediaStreamEndResponse",
758 MediaStreamEndResult
759);
760response_schema!(
761 MediaStreamStartResponseSchema,
762 MediaStreamStartSuccessSchema,
763 "MediaStreamStartResponse",
764 MediaStreamStartResult
765);
766
767#[allow(dead_code)]
768#[derive(JsonSchema)]
769#[schemars(rename = "MediaStreamCaptureRequest")]
770#[serde(rename_all = "camelCase", deny_unknown_fields)]
771struct MediaStreamCaptureRequestSchema {
772 jsonrpc: JsonRpcVersion,
773 id: RpcId,
774 method: MediaStreamCaptureMethodSchema,
775 #[serde(default, skip_serializing_if = "Option::is_none")]
776 #[schemars(with = "RequestTimeoutMs")]
777 timeout_ms: Option<RequestTimeoutMs>,
778 params: MediaStreamCaptureParams,
779}
780
781#[allow(dead_code)]
782#[derive(JsonSchema)]
783enum MediaStreamCaptureMethodSchema {
784 #[serde(rename = "media.stream.capture")]
785 MediaStreamCapture,
786}
787
788#[allow(dead_code)]
789#[derive(JsonSchema)]
790#[schemars(rename = "MediaStreamEndRequest")]
791#[serde(rename_all = "camelCase", deny_unknown_fields)]
792struct MediaStreamEndRequestSchema {
793 jsonrpc: JsonRpcVersion,
794 id: RpcId,
795 method: MediaStreamEndMethodSchema,
796 params: MediaStreamEndParams,
797}
798
799#[allow(dead_code)]
800#[derive(JsonSchema)]
801enum MediaStreamEndMethodSchema {
802 #[serde(rename = "media.stream.end")]
803 MediaStreamEnd,
804}
805
806#[allow(dead_code)]
807#[derive(JsonSchema)]
808#[schemars(rename = "MediaStreamStartRequest")]
809#[serde(rename_all = "camelCase", deny_unknown_fields)]
810struct MediaStreamStartRequestSchema {
811 jsonrpc: JsonRpcVersion,
812 id: RpcId,
813 method: MediaStreamStartMethodSchema,
814 params: MediaStreamStartParams,
815}
816
817#[allow(dead_code)]
818#[derive(JsonSchema)]
819enum MediaStreamStartMethodSchema {
820 #[serde(rename = "media.stream.start")]
821 MediaStreamStart,
822}
823
824#[allow(dead_code)]
825#[derive(JsonSchema)]
826#[schemars(rename = "EventsStreamOpenRequest")]
827#[serde(rename_all = "camelCase", deny_unknown_fields)]
828struct EventsStreamOpenRequestSchema {
829 jsonrpc: JsonRpcVersion,
830 id: RpcId,
831 method: EventsStreamOpenMethodSchema,
832 params: EventsStreamOpenParams,
833}
834
835#[allow(dead_code)]
836#[derive(JsonSchema)]
837enum EventsStreamOpenMethodSchema {
838 #[serde(rename = "events.stream.open")]
839 EventsStreamOpen,
840}
841
842#[allow(dead_code)]
843#[derive(JsonSchema)]
844#[schemars(rename = "EventsSubscribeRequest")]
845#[serde(rename_all = "camelCase", deny_unknown_fields)]
846struct EventsSubscribeRequestSchema {
847 jsonrpc: JsonRpcVersion,
848 id: RpcId,
849 method: EventsSubscribeMethodSchema,
850 params: EventsSubscribeParams,
851}
852
853#[allow(dead_code)]
854#[derive(JsonSchema)]
855enum EventsSubscribeMethodSchema {
856 #[serde(rename = "events.subscribe")]
857 EventsSubscribe,
858}
859
860#[allow(dead_code)]
861#[derive(JsonSchema)]
862#[schemars(rename = "DeviceExecuteRequest")]
863#[serde(rename_all = "camelCase", deny_unknown_fields)]
864struct DeviceExecuteRequestSchema {
865 jsonrpc: JsonRpcVersion,
866 id: RpcId,
867 method: DeviceExecuteMethodSchema,
868 #[serde(default, skip_serializing_if = "Option::is_none")]
869 #[schemars(with = "RequestTimeoutMs")]
870 timeout_ms: Option<RequestTimeoutMs>,
871 params: DeviceExecuteParams,
872}
873
874#[allow(dead_code)]
875#[derive(JsonSchema)]
876enum DeviceExecuteMethodSchema {
877 #[serde(rename = "device.execute")]
878 DeviceExecute,
879}
880
881#[allow(dead_code)]
882#[derive(JsonSchema)]
883#[schemars(rename = "UiSnapshotGetRequest")]
884#[serde(rename_all = "camelCase", deny_unknown_fields)]
885struct UiSnapshotGetRequestSchema {
886 jsonrpc: JsonRpcVersion,
887 id: RpcId,
888 method: UiSnapshotGetMethodSchema,
889 params: UiSnapshotGetParams,
890}
891
892#[allow(dead_code)]
893#[derive(JsonSchema)]
894enum UiSnapshotGetMethodSchema {
895 #[serde(rename = "ui.snapshot.get")]
896 UiSnapshotGet,
897}
898
899#[allow(dead_code)]
900#[derive(JsonSchema)]
901#[schemars(rename = "VerdictRecordRequest")]
902#[serde(rename_all = "camelCase", deny_unknown_fields)]
903struct VerdictRecordRequestSchema {
904 jsonrpc: JsonRpcVersion,
905 id: RpcId,
906 method: VerdictRecordMethodSchema,
907 params: VerdictRecordParams,
908}
909
910#[allow(dead_code)]
911#[derive(JsonSchema)]
912enum VerdictRecordMethodSchema {
913 #[serde(rename = "verdict.record")]
914 VerdictRecord,
915}
916
917#[allow(dead_code)]
918#[derive(JsonSchema)]
919#[schemars(rename = "SessionEndRequest")]
920#[serde(rename_all = "camelCase", deny_unknown_fields)]
921struct SessionEndRequestSchema {
922 jsonrpc: JsonRpcVersion,
923 id: RpcId,
924 method: SessionEndMethodSchema,
925 #[serde(default, skip_serializing_if = "Option::is_none")]
926 #[schemars(with = "SessionEndParams")]
927 params: Option<SessionEndParams>,
928}
929
930#[allow(dead_code)]
931#[derive(JsonSchema)]
932enum SessionEndMethodSchema {
933 #[serde(rename = "session.end")]
934 SessionEnd,
935}
936
937#[allow(dead_code)]
938#[derive(JsonSchema)]
939#[schemars(rename = "SessionExportRequest")]
940#[serde(rename_all = "camelCase", deny_unknown_fields)]
941struct SessionExportRequestSchema {
942 jsonrpc: JsonRpcVersion,
943 id: RpcId,
944 method: SessionExportMethodSchema,
945 #[serde(default, skip_serializing_if = "Option::is_none")]
946 #[schemars(with = "SessionExportParams")]
947 params: Option<SessionExportParams>,
948}
949
950#[allow(dead_code)]
951#[derive(JsonSchema)]
952enum SessionExportMethodSchema {
953 #[serde(rename = "session.export")]
954 SessionExport,
955}
956
957#[allow(dead_code)]
958#[derive(JsonSchema)]
959#[schemars(rename = "EventsListRequest")]
960#[serde(rename_all = "camelCase", deny_unknown_fields)]
961struct EventsListRequestSchema {
962 jsonrpc: JsonRpcVersion,
963 id: RpcId,
964 method: EventsListMethodSchema,
965 #[serde(default, skip_serializing_if = "Option::is_none")]
966 #[schemars(with = "EventsListParams")]
967 params: Option<EventsListParams>,
968}
969
970#[allow(dead_code)]
971#[derive(JsonSchema)]
972enum EventsListMethodSchema {
973 #[serde(rename = "events.list")]
974 EventsList,
975}
976
977#[allow(dead_code)]
978#[derive(JsonSchema)]
979#[schemars(rename = "EventsClearRequest")]
980#[serde(rename_all = "camelCase", deny_unknown_fields)]
981struct EventsClearRequestSchema {
982 jsonrpc: JsonRpcVersion,
983 id: RpcId,
984 method: EventsClearMethodSchema,
985 #[serde(default, skip_serializing_if = "Option::is_none")]
986 #[schemars(with = "SessionTargetParams")]
987 params: Option<SessionTargetParams>,
988}
989
990#[allow(dead_code)]
991#[derive(JsonSchema)]
992enum EventsClearMethodSchema {
993 #[serde(rename = "events.clear")]
994 EventsClear,
995}
996
997#[allow(dead_code)]
998#[derive(JsonSchema)]
999#[schemars(rename = "DeviceSelectRequest")]
1000#[serde(rename_all = "camelCase", deny_unknown_fields)]
1001struct DeviceSelectRequestSchema {
1002 jsonrpc: JsonRpcVersion,
1003 id: RpcId,
1004 method: DeviceSelectMethodSchema,
1005 params: DeviceSelectParams,
1006}
1007
1008#[allow(dead_code)]
1009#[derive(JsonSchema)]
1010enum DeviceSelectMethodSchema {
1011 #[serde(rename = "device.select")]
1012 DeviceSelect,
1013}
1014
1015#[allow(dead_code)]
1016#[derive(JsonSchema)]
1017#[schemars(rename = "DeviceSelectResponse")]
1018#[serde(untagged)]
1019enum DeviceSelectResponseSchema {
1020 Success(DeviceSelectSuccessSchema),
1021 Failure(SystemHelloFailureSchema),
1022}
1023
1024#[allow(dead_code)]
1025#[derive(JsonSchema)]
1026#[serde(rename_all = "camelCase", deny_unknown_fields)]
1027struct DeviceSelectSuccessSchema {
1028 jsonrpc: JsonRpcVersion,
1029 id: RpcId,
1030 result: DeviceSelectResult,
1031}
1032
1033#[allow(dead_code)]
1034#[derive(JsonSchema)]
1035#[schemars(rename = "DevicesListRequest")]
1036#[serde(rename_all = "camelCase", deny_unknown_fields)]
1037struct DevicesListRequestSchema {
1038 jsonrpc: JsonRpcVersion,
1039 id: RpcId,
1040 method: DevicesListMethodSchema,
1041 #[serde(default, skip_serializing_if = "Option::is_none")]
1042 #[schemars(with = "NoParamsSchema")]
1043 params: Option<NoParamsSchema>,
1044}
1045
1046#[allow(dead_code)]
1047#[derive(JsonSchema)]
1048enum DevicesListMethodSchema {
1049 #[serde(rename = "devices.list")]
1050 DevicesList,
1051}
1052
1053#[allow(dead_code)]
1054#[derive(JsonSchema)]
1055#[serde(untagged)]
1056enum NoParamsSchema {
1057 Object(EmptyParamsObjectSchema),
1058 Array([Value; 0]),
1059}
1060
1061#[allow(dead_code)]
1062#[derive(JsonSchema)]
1063#[serde(deny_unknown_fields)]
1064struct EmptyParamsObjectSchema {}
1065
1066#[allow(dead_code)]
1067#[derive(JsonSchema)]
1068#[schemars(rename = "DevicesListResponse")]
1069#[serde(untagged)]
1070enum DevicesListResponseSchema {
1071 Success(DevicesListSuccessSchema),
1072 Failure(SystemHelloFailureSchema),
1073}
1074
1075#[allow(dead_code)]
1076#[derive(JsonSchema)]
1077#[serde(rename_all = "camelCase", deny_unknown_fields)]
1078struct DevicesListSuccessSchema {
1079 jsonrpc: JsonRpcVersion,
1080 id: RpcId,
1081 result: DevicesListResult,
1082}
1083
1084#[allow(dead_code)]
1085#[derive(JsonSchema)]
1086#[schemars(rename = "RequestCancelRequest")]
1087#[serde(rename_all = "camelCase", deny_unknown_fields)]
1088struct RequestCancelRequestSchema {
1089 jsonrpc: JsonRpcVersion,
1090 id: RpcId,
1091 method: RequestCancelMethodSchema,
1092 params: RequestCancelParams,
1093}
1094
1095#[allow(dead_code)]
1096#[derive(JsonSchema)]
1097enum RequestCancelMethodSchema {
1098 #[serde(rename = "request.cancel")]
1099 RequestCancel,
1100}
1101
1102#[allow(dead_code)]
1103#[derive(JsonSchema)]
1104#[schemars(rename = "RequestCancelResponse")]
1105#[serde(untagged)]
1106enum RequestCancelResponseSchema {
1107 Success(RequestCancelSuccessSchema),
1108 Failure(SystemHelloFailureSchema),
1109}
1110
1111#[allow(dead_code)]
1112#[derive(JsonSchema)]
1113#[serde(rename_all = "camelCase", deny_unknown_fields)]
1114struct RequestCancelSuccessSchema {
1115 jsonrpc: JsonRpcVersion,
1116 id: RpcId,
1117 result: RequestCancelResult,
1118}
1119
1120#[allow(dead_code)]
1121#[derive(JsonSchema)]
1122#[schemars(rename = "SystemHelloResponse")]
1123#[serde(untagged)]
1124enum SystemHelloResponseSchema {
1125 Success(SystemHelloSuccessSchema),
1126 Failure(SystemHelloFailureSchema),
1127}
1128
1129#[allow(dead_code)]
1130#[derive(JsonSchema)]
1131#[serde(rename_all = "camelCase", deny_unknown_fields)]
1132struct SystemHelloSuccessSchema {
1133 jsonrpc: JsonRpcVersion,
1134 id: RpcId,
1135 result: HelloResult,
1136}
1137
1138#[allow(dead_code)]
1139#[derive(JsonSchema)]
1140#[serde(rename_all = "camelCase", deny_unknown_fields)]
1141struct SystemHelloFailureSchema {
1142 jsonrpc: JsonRpcVersion,
1143 id: NullableRpcIdSchema,
1144 error: RpcError,
1145}
1146
1147#[allow(dead_code)]
1148#[derive(JsonSchema)]
1149#[serde(untagged)]
1150enum NullableRpcIdSchema {
1151 Id(RpcId),
1152 Null(()),
1153}
1154
1155#[cfg(test)]
1156mod tests {
1157 use super::{JSON_SCHEMA_DRAFT, documents};
1158
1159 const PUBLIC_SCHEMA_DOCUMENT_COUNT: usize = 174;
1160
1161 const PUBLIC_METHODS: [&str; 24] = [
1162 "device.capabilities",
1163 "device.connect",
1164 "device.disconnect",
1165 "device.execute",
1166 "device.observe",
1167 "device.select",
1168 "devices.list",
1169 "events.clear",
1170 "events.list",
1171 "events.stream.open",
1172 "events.subscribe",
1173 "media.stream.capture",
1174 "media.stream.end",
1175 "media.stream.start",
1176 "request.cancel",
1177 "session.current",
1178 "session.end",
1179 "session.export",
1180 "session.start",
1181 "sessions.list",
1182 "system.describe",
1183 "system.hello",
1184 "ui.snapshot.get",
1185 "verdict.record",
1186 ];
1187
1188 #[test]
1189 fn documents_have_unique_stable_names_and_ids() {
1190 let documents = documents();
1191 assert_eq!(documents.len(), PUBLIC_SCHEMA_DOCUMENT_COUNT);
1192 let mut names = documents
1193 .iter()
1194 .map(|document| document.file_name)
1195 .collect::<Vec<_>>();
1196 let original = names.clone();
1197 names.sort_unstable();
1198 names.dedup();
1199
1200 assert_eq!(names.len(), documents.len());
1201 assert_eq!(original, names, "documents must stay sorted by file name");
1202 for document in documents {
1203 assert_eq!(document.schema["$schema"], JSON_SCHEMA_DRAFT);
1204 assert_eq!(document.schema["$id"], document.schema_id);
1205 assert_eq!(document.schema["title"], document.name);
1206 }
1207 }
1208
1209 #[test]
1210 fn protected_action_types_have_independent_root_documents() {
1211 let documents = documents();
1212 for name in [
1213 "ActionProtection",
1214 "RecordedActionCall",
1215 "ScreenshotOmissionReason",
1216 ] {
1217 assert!(
1218 documents.iter().any(|document| document.name == name),
1219 "missing root schema for {name}"
1220 );
1221 }
1222 }
1223
1224 #[test]
1225 fn action_input_schema_remains_an_unconstrained_json_value() {
1226 let document = documents()
1227 .into_iter()
1228 .find(|document| document.name == "ActionDefinition")
1229 .expect("action definition schema exists");
1230
1231 assert_eq!(document.schema["properties"]["inputSchema"], true);
1232 }
1233
1234 #[test]
1235 fn every_public_method_has_request_and_response_schemas() {
1236 let documents = documents();
1237 for method in PUBLIC_METHODS {
1238 let stem = method.replace('.', "-");
1239 let request_file = format!("{stem}-request.schema.json");
1240 let response_file = format!("{stem}-response.schema.json");
1241
1242 let request = documents
1243 .iter()
1244 .find(|document| document.file_name == request_file)
1245 .unwrap_or_else(|| panic!("missing request schema for {method}"));
1246 assert!(
1247 request.schema.to_string().contains(method),
1248 "request schema for {method} must lock its method literal"
1249 );
1250 assert!(
1251 documents
1252 .iter()
1253 .any(|document| document.file_name == response_file),
1254 "missing response schema for {method}"
1255 );
1256 }
1257 }
1258
1259 #[test]
1260 fn every_method_response_reuses_one_failure_envelope() {
1261 let documents = documents();
1262 let mut expected_failure = None;
1263
1264 for method in PUBLIC_METHODS {
1265 let file_name = format!("{}-response.schema.json", method.replace('.', "-"));
1266 let response = documents
1267 .iter()
1268 .find(|document| document.file_name == file_name)
1269 .unwrap_or_else(|| panic!("missing response schema for {method}"));
1270 let failure = response.schema["$defs"]["SystemHelloFailureSchema"].clone();
1271 assert!(
1272 failure.is_object(),
1273 "{method} must define the failure branch"
1274 );
1275 if let Some(expected) = &expected_failure {
1276 assert_eq!(
1277 &failure, expected,
1278 "{method} must reuse the common failure envelope"
1279 );
1280 } else {
1281 expected_failure = Some(failure);
1282 }
1283 assert!(
1284 response.schema["anyOf"]
1285 .as_array()
1286 .expect("method response is a success/failure union")
1287 .iter()
1288 .any(|branch| { branch["$ref"] == "#/$defs/SystemHelloFailureSchema" }),
1289 "{method} response must expose the failure branch"
1290 );
1291 }
1292 }
1293}