asyncapi_rust_models/lib.rs
1//! Runtime data structures for AsyncAPI 3.0 specifications
2//!
3//! This crate provides Rust types that represent [AsyncAPI 3.0](https://www.asyncapi.com/docs/reference/specification/v3.0.0)
4//! specification objects. These types are used by the proc macros to generate
5//! specifications at compile time and can also be constructed manually.
6//!
7//! ## Overview
8//!
9//! The main types mirror the AsyncAPI 3.0 specification structure:
10//!
11//! - [`AsyncApiSpec`] - Root specification object
12//! - [`Info`] - General API information
13//! - [`Server`] - Server connection details
14//! - [`Channel`] - Communication channels
15//! - [`Operation`] - Send/receive operations
16//! - [`Message`] - Message definitions
17//! - [`Schema`] - JSON Schema definitions
18//! - [`Components`] - Reusable components
19//!
20//! ## Serialization
21//!
22//! All types implement [`serde::Serialize`] and [`serde::Deserialize`] for JSON
23//! serialization, following the AsyncAPI 3.0 specification's JSON Schema.
24//!
25//! ## Example
26//!
27//! ```rust
28//! use asyncapi_rust_models::*;
29//! use indexmap::IndexMap;
30//!
31//! // Create a simple AsyncAPI specification
32//! let spec = AsyncApiSpec {
33//! asyncapi: "3.0.0".to_string(),
34//! info: Info {
35//! title: "My API".to_string(),
36//! version: "1.0.0".to_string(),
37//! description: Some("A simple API".to_string()),
38//! },
39//! servers: None,
40//! channels: None,
41//! operations: None,
42//! components: None,
43//! };
44//!
45//! // Serialize to JSON
46//! let json = serde_json::to_string_pretty(&spec).unwrap();
47//! ```
48
49#![deny(missing_docs)]
50#![warn(clippy::all)]
51
52use indexmap::IndexMap;
53use serde::{Deserialize, Serialize};
54
55/// AsyncAPI 3.0 Specification
56///
57/// Root document object representing a complete AsyncAPI specification.
58///
59/// This is the top-level object that contains all information about an API,
60/// including servers, channels, operations, and reusable components.
61///
62/// # Example
63///
64/// ```rust
65/// use asyncapi_rust_models::*;
66///
67/// let spec = AsyncApiSpec {
68/// asyncapi: "3.0.0".to_string(),
69/// info: Info {
70/// title: "My WebSocket API".to_string(),
71/// version: "1.0.0".to_string(),
72/// description: Some("Real-time messaging API".to_string()),
73/// },
74/// servers: None,
75/// channels: None,
76/// operations: None,
77/// components: None,
78/// };
79/// ```
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct AsyncApiSpec {
82 /// AsyncAPI version (e.g., "3.0.0")
83 pub asyncapi: String,
84
85 /// General information about the API
86 pub info: Info,
87
88 /// Server connection details
89 #[serde(skip_serializing_if = "Option::is_none")]
90 pub servers: Option<IndexMap<String, Server>>,
91
92 /// Available channels (communication paths)
93 #[serde(skip_serializing_if = "Option::is_none")]
94 pub channels: Option<IndexMap<String, Channel>>,
95
96 /// Operations (send/receive)
97 #[serde(skip_serializing_if = "Option::is_none")]
98 pub operations: Option<IndexMap<String, Operation>>,
99
100 /// Reusable components (messages, schemas, etc.)
101 #[serde(skip_serializing_if = "Option::is_none")]
102 pub components: Option<Components>,
103}
104
105/// API information object
106///
107/// Contains general metadata about the API such as title, version, and description.
108/// This information is displayed in documentation tools and helps users understand
109/// the purpose and version of the API.
110#[derive(Debug, Clone, Serialize, Deserialize)]
111pub struct Info {
112 /// API title
113 ///
114 /// A human-readable name for the API (e.g., "Chat WebSocket API")
115 pub title: String,
116
117 /// API version
118 ///
119 /// The version of the API (e.g., "1.0.0"). Should follow semantic versioning.
120 pub version: String,
121
122 /// API description
123 ///
124 /// A longer description of the API's purpose and functionality (optional).
125 #[serde(skip_serializing_if = "Option::is_none")]
126 pub description: Option<String>,
127}
128
129/// Server connection information
130///
131/// Defines connection details for a server that hosts the API. Multiple servers
132/// can be defined to support different environments (production, staging, development).
133///
134/// # Example
135///
136/// ```rust
137/// use asyncapi_rust_models::{Server, ServerVariable};
138/// use indexmap::IndexMap;
139///
140/// let mut variables = IndexMap::new();
141/// variables.insert("userId".to_string(), ServerVariable {
142/// description: Some("User ID for connection".to_string()),
143/// default: None,
144/// enum_values: None,
145/// examples: Some(vec!["12".to_string(), "13".to_string()]),
146/// });
147///
148/// let server = Server {
149/// host: "chat.example.com:443".to_string(),
150/// protocol: "wss".to_string(),
151/// pathname: Some("/api/ws/{userId}".to_string()),
152/// description: Some("Production WebSocket server".to_string()),
153/// variables: Some(variables),
154/// ..Default::default()
155/// };
156/// ```
157#[derive(Debug, Clone, Serialize, Deserialize, Default)]
158pub struct Server {
159 /// Server URL or host
160 ///
161 /// The hostname or URL where the server is hosted. May include port number.
162 /// Examples: "localhost:8080", "api.example.com", "ws.example.com:443"
163 pub host: String,
164
165 /// Protocol (e.g., "wss", "ws", "grpc")
166 ///
167 /// The protocol used to communicate with the server.
168 /// Common values: "ws" (WebSocket), "wss" (WebSocket Secure), "grpc", "mqtt"
169 pub protocol: String,
170
171 /// Optional pathname for the server URL
172 ///
173 /// The pathname to append to the host. Can contain variables in curly braces (e.g., "/api/ws/{userId}")
174 #[serde(skip_serializing_if = "Option::is_none")]
175 pub pathname: Option<String>,
176
177 /// Server description
178 ///
179 /// An optional human-readable description of the server's purpose or environment
180 #[serde(skip_serializing_if = "Option::is_none")]
181 pub description: Option<String>,
182
183 /// Server variables
184 ///
185 /// A map of variable name to ServerVariable definition for variables used in the pathname
186 #[serde(skip_serializing_if = "Option::is_none")]
187 pub variables: Option<IndexMap<String, ServerVariable>>,
188
189 /// Protocol specific bindings.
190 #[serde(skip_serializing_if = "Option::is_none", default)]
191 pub bindings: Option<ServerBindings>,
192}
193
194/// Protocol specific server bindings
195#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct ServerBindings {
197 /// Mqtt protocol specific bindings
198 #[serde(skip_serializing_if = "Option::is_none")]
199 pub mqtt: Option<MqttServerBindings>,
200}
201
202/// Mqtt last will structure
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct MqttLastWill {
205 /// The topic where the Last Will and Testament message will be sent.
206 pub topic: String,
207
208 /// Defines how hard the broker/client will try to ensure that the Last Will and Testament message is received. Its value MUST be either 0, 1 or 2.
209 pub qos: u8,
210
211 /// Last Will message.
212 pub message: String,
213
214 /// Whether the broker should retain the Last Will and Testament message or not.
215 pub retain: bool,
216}
217
218/// Represents mqtt binding properties which can be either a number or a json schema like sessionExpiryInterval or maximumPacketSize
219#[derive(Debug, Clone, Serialize, Deserialize)]
220#[serde(untagged)]
221pub enum MqttBindingNumValue {
222 /// The value variant
223 Value(u32),
224 /// The schema variant
225 Schema(Schema),
226}
227
228/// Mqtt server binding
229#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct MqttServerBindings {
231 /// The client identifier.
232 #[serde(skip_serializing_if = "Option::is_none", rename = "clientId")]
233 pub client_id: Option<String>,
234
235 /// Whether to create a persistent connection or not. When false,
236 /// the connection will be persistent. This is called clean start in MQTTv5.
237 #[serde(skip_serializing_if = "Option::is_none", rename = "cleanSession")]
238 pub clean_session: Option<bool>,
239
240 /// Last Will and Testament configuration. topic, qos, message and retain are properties of this object as shown below.
241 #[serde(skip_serializing_if = "Option::is_none", rename = "lastWill")]
242 pub last_will: Option<MqttLastWill>,
243
244 /// Interval in seconds of the longest period of time the broker and the client can endure without sending a message.
245 #[serde(skip_serializing_if = "Option::is_none", rename = "keepAlive")]
246 pub keep_alive: Option<u32>,
247
248 /// Interval in seconds or a Schema Object containing the definition of the interval. The broker maintains a session for a disconnected client until this interval expires.
249 #[serde(
250 skip_serializing_if = "Option::is_none",
251 rename = "sessionExpiryInterval"
252 )]
253 pub session_expiry_interval: Option<MqttBindingNumValue>,
254
255 /// Number of bytes or a Schema Object representing the maximum packet size the client is willing to accept.
256 #[serde(skip_serializing_if = "Option::is_none", rename = "maximumPacketSize")]
257 pub max_packet_size: Option<MqttBindingNumValue>,
258
259 /// The version of this binding. If omitted, "latest" MUST be assumed.
260 #[serde(skip_serializing_if = "Option::is_none", rename = "bindingVersion")]
261 pub binding_version: Option<String>,
262}
263
264/// Server variable definition
265///
266/// Defines a variable that can be used in the server pathname. Variables are
267/// substituted at runtime with actual values.
268///
269/// # Example
270///
271/// ```rust
272/// use asyncapi_rust_models::ServerVariable;
273///
274/// let user_id_var = ServerVariable {
275/// description: Some("Authenticated user ID".to_string()),
276/// default: None,
277/// enum_values: None,
278/// examples: Some(vec!["12".to_string(), "13".to_string()]),
279/// };
280/// ```
281#[derive(Debug, Clone, Serialize, Deserialize)]
282pub struct ServerVariable {
283 /// Variable description
284 ///
285 /// Human-readable description of what this variable represents
286 #[serde(skip_serializing_if = "Option::is_none")]
287 pub description: Option<String>,
288
289 /// Default value
290 ///
291 /// The default value to use if no value is provided
292 #[serde(skip_serializing_if = "Option::is_none")]
293 pub default: Option<String>,
294
295 /// Enumeration of allowed values
296 ///
297 /// If specified, only these values are valid for this variable
298 #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
299 pub enum_values: Option<Vec<String>>,
300
301 /// Example values
302 ///
303 /// A list of example values for documentation purposes
304 #[serde(skip_serializing_if = "Option::is_none")]
305 pub examples: Option<Vec<String>>,
306}
307
308/// Communication channel
309///
310/// Represents a communication path through which messages are exchanged.
311/// Channels define where messages are sent and received (e.g., WebSocket endpoints,
312/// message queue topics, gRPC methods).
313///
314/// # Example
315///
316/// ```rust
317/// use asyncapi_rust_models::{Channel, Parameter};
318/// use indexmap::IndexMap;
319///
320/// let mut parameters = IndexMap::new();
321/// parameters.insert("userId".to_string(), Parameter {
322/// description: Some("User ID for this WebSocket connection".to_string()),
323/// default: None,
324/// enum_values: None,
325/// examples: Some(vec!["42".to_string(), "100".to_string()]),
326/// location: None,
327/// });
328///
329/// let channel = Channel {
330/// address: Some("/ws/chat/{userId}".to_string()),
331/// messages: None,
332/// parameters: Some(parameters),
333/// };
334/// ```
335#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct Channel {
337 /// Channel address/path
338 ///
339 /// The location where this channel is available. For WebSocket, this is typically
340 /// the WebSocket path (e.g., "/ws/chat"). For other protocols, this could be a
341 /// topic name, queue name, or method path.
342 #[serde(skip_serializing_if = "Option::is_none")]
343 pub address: Option<String>,
344
345 /// Messages available on this channel
346 ///
347 /// A map of message identifiers to message definitions or references.
348 /// Messages define the structure of data that flows through this channel.
349 #[serde(skip_serializing_if = "Option::is_none")]
350 pub messages: Option<IndexMap<String, MessageRef>>,
351
352 /// Channel parameters
353 ///
354 /// A map of parameter names to their schema definitions for variables used in the address
355 #[serde(skip_serializing_if = "Option::is_none")]
356 pub parameters: Option<IndexMap<String, Parameter>>,
357}
358
359/// Channel parameter definition
360///
361/// Defines a parameter that can be used in the channel address, following the
362/// [AsyncAPI 3.0 Parameter Object](https://www.asyncapi.com/docs/reference/specification/v3.0.0#parameterObject).
363///
364/// Note: AsyncAPI 3.0 removed the `schema` property from Parameter (present in 2.x).
365/// Parameters now use `description`, `default`, `enum`, `examples`, and `location`.
366///
367/// # Example
368///
369/// ```rust
370/// use asyncapi_rust_models::Parameter;
371///
372/// let user_id_param = Parameter {
373/// description: Some("User ID for this WebSocket connection".to_string()),
374/// default: Some("0".to_string()),
375/// enum_values: None,
376/// examples: Some(vec!["42".to_string(), "100".to_string()]),
377/// location: None,
378/// };
379/// ```
380#[derive(Debug, Clone, Serialize, Deserialize)]
381pub struct Parameter {
382 /// Human-readable description of what this parameter represents
383 #[serde(skip_serializing_if = "Option::is_none")]
384 pub description: Option<String>,
385
386 /// Default value for this parameter
387 #[serde(skip_serializing_if = "Option::is_none")]
388 pub default: Option<String>,
389
390 /// Enumeration of allowed values for this parameter
391 #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
392 pub enum_values: Option<Vec<String>>,
393
394 /// Example values for this parameter
395 #[serde(skip_serializing_if = "Option::is_none")]
396 pub examples: Option<Vec<String>>,
397
398 /// Runtime expression specifying the location of the parameter value
399 ///
400 /// See <https://www.asyncapi.com/docs/reference/specification/v3.0.0#runtimeExpression>
401 #[serde(skip_serializing_if = "Option::is_none")]
402 pub location: Option<String>,
403}
404
405/// Reference to a message definition
406///
407/// Messages can be defined either inline or as references to reusable components.
408/// This enum supports both patterns, following the AsyncAPI 3.0 specification.
409///
410/// # Example
411///
412/// ```rust
413/// use asyncapi_rust_models::{MessageRef, Message};
414///
415/// // Reference to a component message
416/// let ref_msg = MessageRef::Reference {
417/// reference: "#/components/messages/ChatMessage".to_string(),
418/// };
419///
420/// // Inline message definition
421/// let inline_msg = MessageRef::Inline(Box::new(Message {
422/// name: Some("ChatMessage".to_string()),
423/// title: Some("Chat Message".to_string()),
424/// summary: Some("A chat message".to_string()),
425/// description: None,
426/// content_type: Some("application/json".to_string()),
427/// payload: None,
428/// ..Default::default()
429/// }));
430/// ```
431#[derive(Debug, Clone, Serialize, Deserialize)]
432#[serde(untagged)]
433pub enum MessageRef {
434 /// Reference to component message
435 ///
436 /// Points to a reusable message definition in the components section.
437 /// Format: "#/components/messages/{messageName}"
438 Reference {
439 /// $ref path
440 #[serde(rename = "$ref")]
441 reference: String,
442 },
443 /// Inline message definition
444 ///
445 /// Embeds the message definition directly rather than referencing a component
446 Inline(Box<Message>),
447}
448
449/// Message definition
450///
451/// Represents a message that can be sent or received through a channel.
452/// Messages describe the structure, content type, and documentation for data
453/// exchanged in asynchronous communication.
454///
455/// # Example
456///
457/// ```rust
458/// use asyncapi_rust_models::{Message, Schema, SchemaObject};
459/// use indexmap::IndexMap;
460///
461/// let message = Message {
462/// name: Some("ChatMessage".to_string()),
463/// title: Some("Chat Message".to_string()),
464/// summary: Some("A message in a chat room".to_string()),
465/// description: Some("Sent when a user posts a message".to_string()),
466/// content_type: Some("application/json".to_string()),
467/// payload: Some(Schema::Object(Box::new(SchemaObject {
468/// schema_type: Some(serde_json::json!("object")),
469/// properties: None,
470/// required: None,
471/// description: Some("Chat message payload".to_string()),
472/// title: None,
473/// enum_values: None,
474/// const_value: None,
475/// items: None,
476/// additional_properties: None,
477/// one_of: None,
478/// any_of: None,
479/// all_of: None,
480/// additional: IndexMap::new(),
481/// }))),
482/// ..Default::default()
483/// };
484/// ```
485#[derive(Debug, Clone, Serialize, Deserialize, Default)]
486pub struct Message {
487 /// Message name
488 ///
489 /// A machine-readable identifier for the message (e.g., "ChatMessage", "user.join")
490 #[serde(skip_serializing_if = "Option::is_none")]
491 pub name: Option<String>,
492
493 /// Message title
494 ///
495 /// A human-readable title for the message
496 #[serde(skip_serializing_if = "Option::is_none")]
497 pub title: Option<String>,
498
499 /// Message summary
500 ///
501 /// A short summary of what the message is for
502 #[serde(skip_serializing_if = "Option::is_none")]
503 pub summary: Option<String>,
504
505 /// Message description
506 ///
507 /// A detailed description of the message's purpose and usage
508 #[serde(skip_serializing_if = "Option::is_none")]
509 pub description: Option<String>,
510
511 /// Content type (e.g., "application/json")
512 ///
513 /// The MIME type of the message payload. Common values:
514 /// - "application/json" (default for text messages)
515 /// - "application/octet-stream" (binary data)
516 /// - "application/x-protobuf" (Protocol Buffers)
517 /// - "application/x-msgpack" (MessagePack)
518 #[serde(rename = "contentType", skip_serializing_if = "Option::is_none")]
519 pub content_type: Option<String>,
520
521 /// Message payload schema
522 ///
523 /// JSON Schema defining the structure of the message payload
524 #[serde(skip_serializing_if = "Option::is_none")]
525 pub payload: Option<Schema>,
526
527 /// Protocol specific bindings.
528 #[serde(skip_serializing_if = "Option::is_none", default)]
529 pub bindings: Option<MessageBindings>,
530}
531
532/// Protocol specific message bindings
533#[derive(Serialize, Deserialize, Clone, Debug)]
534pub struct MessageBindings {
535 /// Mqtt protocol specific message bindings
536 #[serde(skip_serializing_if = "Option::is_none")]
537 pub mqtt: Option<MqttMessageBindings>,
538}
539
540/// Mqtt response topic
541#[derive(Serialize, Deserialize, Clone, Debug)]
542#[serde(untagged)]
543pub enum MqttResponseTopic {
544 /// Topic Uri
545 Uri(String),
546 /// Schema for the response topic
547 Schema(Schema),
548}
549
550/// Mqtt message bindings
551#[derive(Serialize, Deserialize, Clone, Debug)]
552pub struct MqttMessageBindings {
553 /// Either: 0 (zero): Indicates that the payload is unspecified bytes, or 1: Indicates that the payload is UTF-8 encoded character data.
554 #[serde(
555 skip_serializing_if = "Option::is_none",
556 rename = "payloadFormatIndicator"
557 )]
558 pub payload_format_indicator: Option<u8>,
559
560 /// Correlation Data is used by the sender of the request message to identify which request the response message is for when it is received.
561 #[serde(skip_serializing_if = "Option::is_none", rename = "correlationData")]
562 pub correlation_data: Option<Schema>,
563
564 /// String describing the content type of the message payload. This should not conflict with the contentType field of the associated AsyncAPI Message object.
565 #[serde(skip_serializing_if = "Option::is_none", rename = "contentType")]
566 pub content_type: Option<String>,
567
568 /// The topic (channel URI) for a response message.
569 #[serde(skip_serializing_if = "Option::is_none", rename = "responseTopic")]
570 pub response_topic: Option<MqttResponseTopic>,
571
572 /// The version of this binding. If omitted, "latest" MUST be assumed.
573 #[serde(skip_serializing_if = "Option::is_none", rename = "bindingVersion")]
574 pub binding_version: Option<String>,
575}
576
577/// Operation (send or receive)
578///
579/// Defines an action that can be performed on a channel. Operations describe
580/// whether an application sends or receives messages through a specific channel.
581///
582/// # Example
583///
584/// ```rust
585/// use asyncapi_rust_models::{Operation, OperationAction, ChannelRef};
586///
587/// let operation = Operation {
588/// action: OperationAction::Send,
589/// channel: ChannelRef {
590/// reference: "#/channels/chat".to_string(),
591/// },
592/// messages: None,
593/// ..Default::default()
594/// };
595/// ```
596#[derive(Debug, Clone, Serialize, Deserialize, Default)]
597pub struct Operation {
598 /// Operation action (send or receive)
599 ///
600 /// Specifies whether the application sends or receives messages
601 pub action: OperationAction,
602
603 /// Channel reference
604 ///
605 /// Points to the channel where this operation takes place
606 pub channel: ChannelRef,
607
608 /// Messages for this operation
609 ///
610 /// Optional list of messages that can be used with this operation
611 #[serde(skip_serializing_if = "Option::is_none")]
612 pub messages: Option<Vec<MessageRef>>,
613
614 /// Protocol specific bindings
615 #[serde(skip_serializing_if = "Option::is_none", default)]
616 pub bindings: Option<OperationBindings>,
617}
618
619/// Protocol specific operation bindings
620#[derive(Serialize, Deserialize, Clone, Debug)]
621pub struct OperationBindings {
622 /// Mqtt specific operation bindings
623 #[serde(skip_serializing_if = "Option::is_none")]
624 pub mqtt: Option<MqttOperationBindings>,
625}
626
627/// Mqtt operation bindings
628#[derive(Serialize, Deserialize, Clone, Debug)]
629pub struct MqttOperationBindings {
630 /// Defines the Quality of Service (QoS) levels for the message flow between client and server.
631 /// Its value MUST be either 0 (At most once delivery), 1 (At least once delivery), or 2 (Exactly once delivery).
632 #[serde(skip_serializing_if = "Option::is_none")]
633 pub qos: Option<u8>,
634
635 /// Whether the broker should retain the message or not.
636 #[serde(skip_serializing_if = "Option::is_none")]
637 pub retain: Option<bool>,
638
639 /// Interval in seconds or a Schema Object containing the definition of the lifetime of the message.
640 #[serde(
641 skip_serializing_if = "Option::is_none",
642 rename = "messageExpiryInterval"
643 )]
644 pub message_expiry_interval: Option<MqttBindingNumValue>,
645
646 /// The version of this binding. If omitted, "latest" MUST be assumed.
647 #[serde(skip_serializing_if = "Option::is_none", rename = "bindingVersion")]
648 pub binding_version: Option<String>,
649}
650
651/// Operation action type
652#[derive(Debug, Clone, Serialize, Deserialize, Default)]
653#[serde(rename_all = "lowercase")]
654pub enum OperationAction {
655 /// Send message
656 #[default]
657 Send,
658 /// Receive message
659 Receive,
660}
661
662/// Reference to a channel
663#[derive(Debug, Clone, Serialize, Deserialize, Default)]
664pub struct ChannelRef {
665 /// $ref path
666 #[serde(rename = "$ref")]
667 pub reference: String,
668}
669
670/// Reusable components
671#[derive(Debug, Clone, Serialize, Deserialize)]
672pub struct Components {
673 /// Message definitions
674 #[serde(skip_serializing_if = "Option::is_none")]
675 pub messages: Option<IndexMap<String, Message>>,
676
677 /// Schema definitions
678 #[serde(skip_serializing_if = "Option::is_none")]
679 pub schemas: Option<IndexMap<String, Schema>>,
680}
681
682/// JSON Schema object
683///
684/// Flexible representation that can hold any valid JSON Schema. This type supports
685/// both schema references (using `$ref`) and complete inline schema definitions.
686///
687/// Schemas define the structure and validation rules for message payloads,
688/// following the JSON Schema specification.
689///
690/// # Example
691///
692/// ## Reference Schema
693///
694/// ```rust
695/// use asyncapi_rust_models::Schema;
696///
697/// let schema = Schema::Reference {
698/// reference: "#/components/schemas/ChatMessage".to_string(),
699/// };
700/// ```
701///
702/// ## Object Schema
703///
704/// ```rust
705/// use asyncapi_rust_models::{Schema, SchemaObject};
706/// use indexmap::IndexMap;
707///
708/// let schema = Schema::Object(Box::new(SchemaObject {
709/// schema_type: Some(serde_json::json!("object")),
710/// properties: None,
711/// required: Some(vec!["username".to_string(), "room".to_string()]),
712/// description: Some("A chat message".to_string()),
713/// title: Some("ChatMessage".to_string()),
714/// enum_values: None,
715/// const_value: None,
716/// items: None,
717/// additional_properties: None,
718/// one_of: None,
719/// any_of: None,
720/// all_of: None,
721/// additional: IndexMap::new(),
722/// }));
723/// ```
724#[derive(Debug, Clone, Serialize, Deserialize)]
725#[serde(untagged)]
726pub enum Schema {
727 /// Reference to another schema ($ref)
728 ///
729 /// Points to a reusable schema definition in the components section.
730 /// Format: "#/components/schemas/{schemaName}"
731 Reference {
732 /// $ref path
733 #[serde(rename = "$ref")]
734 reference: String,
735 },
736 /// Full schema object (boxed to reduce enum size)
737 ///
738 /// Contains a complete JSON Schema definition with all properties inline
739 Object(Box<SchemaObject>),
740 /// Catch-all for valid JSON Schemas that don't match the above variants
741 ///
742 /// Handles minimal schemas like `{}`, `{"title": "..."}`, or boolean schemas
743 /// (`true`/`false`) that are valid per the JSON Schema spec but carry no
744 /// structural information. `schemars` emits these for open-ended types such
745 /// as `serde_json::Value`.
746 Any(serde_json::Value),
747}
748
749/// Schema object with all JSON Schema properties
750///
751/// Complete representation of a JSON Schema with support for all standard properties.
752/// This struct provides fine-grained control over schema definitions for message payloads.
753///
754/// # Example
755///
756/// ```rust
757/// use asyncapi_rust_models::{Schema, SchemaObject};
758/// use indexmap::IndexMap;
759///
760/// // String property schema
761/// let username_schema = Schema::Object(Box::new(SchemaObject {
762/// schema_type: Some(serde_json::json!("string")),
763/// properties: None,
764/// required: None,
765/// description: Some("User's display name".to_string()),
766/// title: None,
767/// enum_values: None,
768/// const_value: None,
769/// items: None,
770/// additional_properties: None,
771/// one_of: None,
772/// any_of: None,
773/// all_of: None,
774/// additional: IndexMap::new(),
775/// }));
776///
777/// // Object schema with properties
778/// let mut properties = IndexMap::new();
779/// properties.insert("username".to_string(), Box::new(username_schema));
780///
781/// let message_schema = SchemaObject {
782/// schema_type: Some(serde_json::json!("object")),
783/// properties: Some(properties),
784/// required: Some(vec!["username".to_string()]),
785/// description: Some("A chat message".to_string()),
786/// title: Some("ChatMessage".to_string()),
787/// enum_values: None,
788/// const_value: None,
789/// items: None,
790/// additional_properties: None,
791/// one_of: None,
792/// any_of: None,
793/// all_of: None,
794/// additional: IndexMap::new(),
795/// };
796/// ```
797#[derive(Debug, Clone, Serialize, Deserialize)]
798pub struct SchemaObject {
799 /// Schema type
800 ///
801 /// The JSON Schema type: "object", "array", "string", "number", "integer", "boolean", "null"
802 /// Can also be an array of types for schemas that allow multiple types (e.g., ["string", "null"])
803 #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
804 pub schema_type: Option<serde_json::Value>,
805
806 /// Properties (for object type)
807 ///
808 /// Map of property names to their schemas when schema_type is "object"
809 #[serde(skip_serializing_if = "Option::is_none")]
810 pub properties: Option<IndexMap<String, Box<Schema>>>,
811
812 /// Required properties
813 ///
814 /// List of property names that must be present (for object types)
815 #[serde(skip_serializing_if = "Option::is_none")]
816 pub required: Option<Vec<String>>,
817
818 /// Description
819 ///
820 /// Human-readable description of what this schema represents
821 #[serde(skip_serializing_if = "Option::is_none")]
822 pub description: Option<String>,
823
824 /// Title
825 ///
826 /// A short title for the schema
827 #[serde(skip_serializing_if = "Option::is_none")]
828 pub title: Option<String>,
829
830 /// Enum values
831 ///
832 /// List of allowed values (for enum types)
833 #[serde(rename = "enum", skip_serializing_if = "Option::is_none")]
834 pub enum_values: Option<Vec<serde_json::Value>>,
835
836 /// Const value
837 ///
838 /// A single constant value that this schema must match
839 #[serde(rename = "const", skip_serializing_if = "Option::is_none")]
840 pub const_value: Option<serde_json::Value>,
841
842 /// Items schema (for array type)
843 ///
844 /// Schema for array elements when schema_type is "array"
845 #[serde(skip_serializing_if = "Option::is_none")]
846 pub items: Option<Box<Schema>>,
847
848 /// Additional properties
849 ///
850 /// Schema for additional properties not explicitly defined (for object types)
851 #[serde(
852 rename = "additionalProperties",
853 skip_serializing_if = "Option::is_none"
854 )]
855 pub additional_properties: Option<Box<Schema>>,
856
857 /// OneOf schemas
858 ///
859 /// Value must match exactly one of these schemas (XOR logic)
860 #[serde(rename = "oneOf", skip_serializing_if = "Option::is_none")]
861 pub one_of: Option<Vec<Schema>>,
862
863 /// AnyOf schemas
864 ///
865 /// Value must match at least one of these schemas (OR logic)
866 #[serde(rename = "anyOf", skip_serializing_if = "Option::is_none")]
867 pub any_of: Option<Vec<Schema>>,
868
869 /// AllOf schemas
870 ///
871 /// Value must match all of these schemas (AND logic)
872 #[serde(rename = "allOf", skip_serializing_if = "Option::is_none")]
873 pub all_of: Option<Vec<Schema>>,
874
875 /// Additional fields that may be present in the schema
876 ///
877 /// Captures any additional JSON Schema properties not explicitly defined above
878 #[serde(flatten)]
879 pub additional: IndexMap<String, serde_json::Value>,
880}
881
882impl Default for AsyncApiSpec {
883 fn default() -> Self {
884 Self {
885 asyncapi: "3.0.0".to_string(),
886 info: Info {
887 title: "API".to_string(),
888 version: "1.0.0".to_string(),
889 description: None,
890 },
891 servers: None,
892 channels: None,
893 operations: None,
894 components: None,
895 }
896 }
897}
898
899#[cfg(test)]
900mod tests {
901 use super::*;
902
903 #[test]
904 fn test_spec_serialization() {
905 let spec = AsyncApiSpec::default();
906 let json = serde_json::to_string(&spec).unwrap();
907 assert!(json.contains("asyncapi"));
908 assert!(json.contains("3.0.0"));
909 }
910
911 #[test]
912 fn test_spec_deserialization() {
913 let json = r#"{
914 "asyncapi": "3.0.0",
915 "info": {
916 "title": "Test API",
917 "version": "1.0.0"
918 }
919 }"#;
920 let spec: AsyncApiSpec = serde_json::from_str(json).unwrap();
921 assert_eq!(spec.asyncapi, "3.0.0");
922 assert_eq!(spec.info.title, "Test API");
923 }
924}