Skip to main content

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}