use asyncapi_rust::{AsyncApi, ToAsyncApiMessage, schemars::JsonSchema};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, JsonSchema, ToAsyncApiMessage)]
pub enum BasicMessage {
Ping,
Pong,
}
#[derive(Serialize, Deserialize, JsonSchema, ToAsyncApiMessage)]
#[serde(tag = "type")]
pub enum TaggedMessage {
Echo { text: String },
Broadcast { room: String, text: String },
}
#[derive(Serialize, Deserialize, JsonSchema, ToAsyncApiMessage)]
#[serde(tag = "message")]
pub enum RenamedMessage {
#[serde(rename = "user.join")]
UserJoin { username: String },
#[serde(rename = "user.leave")]
UserLeave { username: String },
#[serde(rename = "chat.message")]
ChatMessage { username: String, text: String },
}
#[derive(Serialize, Deserialize, JsonSchema, ToAsyncApiMessage)]
pub struct SimpleMessage {
pub id: u64,
pub text: String,
}
#[derive(Serialize, Deserialize, JsonSchema, ToAsyncApiMessage)]
#[serde(tag = "type")]
pub enum DocumentedMessage {
#[asyncapi(
summary = "User joins a chat room",
description = "Sent when a user enters a room"
)]
Join { username: String, room: String },
#[asyncapi(
summary = "User leaves a chat room",
description = "Sent when a user exits a room",
title = "Leave Room"
)]
Leave { username: String, room: String },
#[asyncapi(content_type = "application/octet-stream")]
File { filename: String, data: Vec<u8> },
#[asyncapi(triggers_binary)]
Binary { data: Vec<u8> },
}
#[test]
fn test_basic_enum_messages() {
let names = BasicMessage::asyncapi_message_names();
assert_eq!(names, vec!["Ping", "Pong"]);
assert_eq!(BasicMessage::asyncapi_message_count(), 2);
assert_eq!(BasicMessage::asyncapi_tag_field(), None);
}
#[test]
fn test_tagged_enum() {
let names = TaggedMessage::asyncapi_message_names();
assert_eq!(names, vec!["Echo", "Broadcast"]);
assert_eq!(TaggedMessage::asyncapi_message_count(), 2);
assert_eq!(TaggedMessage::asyncapi_tag_field(), Some("type"));
}
#[test]
fn test_renamed_enum() {
let names = RenamedMessage::asyncapi_message_names();
assert_eq!(names, vec!["user.join", "user.leave", "chat.message"]);
assert_eq!(RenamedMessage::asyncapi_message_count(), 3);
assert_eq!(RenamedMessage::asyncapi_tag_field(), Some("message"));
}
#[test]
fn test_struct_message() {
let names = SimpleMessage::asyncapi_message_names();
assert_eq!(names, vec!["SimpleMessage"]);
assert_eq!(SimpleMessage::asyncapi_message_count(), 1);
assert_eq!(SimpleMessage::asyncapi_tag_field(), None);
}
#[test]
fn test_schema_generation() {
let messages = SimpleMessage::asyncapi_messages();
assert_eq!(messages.len(), 1);
let message = &messages[0];
assert_eq!(message.name, Some("SimpleMessage".to_string()));
assert_eq!(message.content_type, Some("application/json".to_string()));
assert!(message.payload.is_some());
if let Some(schema) = &message.payload {
assert!(matches!(schema, asyncapi_rust::Schema::Object(_)));
}
}
#[test]
fn test_enum_schema_generation() {
let messages = TaggedMessage::asyncapi_messages();
assert_eq!(messages.len(), 2);
assert_eq!(messages[0].name, Some("Echo".to_string()));
assert_eq!(messages[1].name, Some("Broadcast".to_string()));
assert!(messages[0].payload.is_some());
assert!(messages[1].payload.is_some());
}
#[test]
fn test_asyncapi_attributes() {
let messages = DocumentedMessage::asyncapi_messages();
assert_eq!(messages.len(), 4);
let join = &messages[0];
assert_eq!(join.name, Some("Join".to_string()));
assert_eq!(join.summary, Some("User joins a chat room".to_string()));
assert_eq!(
join.description,
Some("Sent when a user enters a room".to_string())
);
assert_eq!(join.content_type, Some("application/json".to_string()));
let leave = &messages[1];
assert_eq!(leave.name, Some("Leave".to_string()));
assert_eq!(leave.title, Some("Leave Room".to_string()));
assert_eq!(leave.summary, Some("User leaves a chat room".to_string()));
assert_eq!(
leave.description,
Some("Sent when a user exits a room".to_string())
);
let file = &messages[2];
assert_eq!(file.name, Some("File".to_string()));
assert_eq!(
file.content_type,
Some("application/octet-stream".to_string())
);
let binary = &messages[3];
assert_eq!(binary.name, Some("Binary".to_string()));
assert_eq!(
binary.content_type,
Some("application/octet-stream".to_string())
);
}
#[derive(AsyncApi)]
#[asyncapi(
title = "Test API",
version = "1.0.0",
description = "A test API specification"
)]
struct TestApi;
#[test]
fn test_asyncapi_derive() {
let spec = TestApi::asyncapi_spec();
assert_eq!(spec.asyncapi, "3.0.0");
assert_eq!(spec.info.title, "Test API");
assert_eq!(spec.info.version, "1.0.0");
assert_eq!(
spec.info.description,
Some("A test API specification".to_string())
);
assert!(spec.servers.is_none());
assert!(spec.channels.is_none());
assert!(spec.operations.is_none());
assert!(spec.components.is_none());
}
#[derive(AsyncApi)]
#[asyncapi(title = "Minimal API", version = "0.1.0")]
struct MinimalApi;
#[test]
fn test_asyncapi_minimal() {
let spec = MinimalApi::asyncapi_spec();
assert_eq!(spec.asyncapi, "3.0.0");
assert_eq!(spec.info.title, "Minimal API");
assert_eq!(spec.info.version, "0.1.0");
assert_eq!(spec.info.description, None);
}
#[allow(clippy::duplicated_attributes)] #[derive(AsyncApi)]
#[asyncapi(
title = "Full API",
version = "1.0.0",
description = "Complete API spec"
)]
#[asyncapi_server(
name = "production",
host = "api.example.com",
protocol = "wss",
description = "Production server"
)]
#[asyncapi_server(name = "development", host = "localhost:8080", protocol = "ws")]
#[asyncapi_channel(name = "chat", address = "/ws/chat")]
#[asyncapi_operation(name = "sendMessage", action = "send", channel = "chat")]
#[asyncapi_operation(name = "receiveMessage", action = "receive", channel = "chat")]
struct FullApi;
#[test]
fn test_asyncapi_full() {
let spec = FullApi::asyncapi_spec();
assert_eq!(spec.info.title, "Full API");
assert_eq!(spec.info.version, "1.0.0");
assert_eq!(spec.info.description, Some("Complete API spec".to_string()));
let servers = spec.servers.expect("Should have servers");
assert_eq!(servers.len(), 2);
let prod_server = servers
.get("production")
.expect("Should have production server");
assert_eq!(prod_server.host, "api.example.com");
assert_eq!(prod_server.protocol, "wss");
assert_eq!(
prod_server.description,
Some("Production server".to_string())
);
let dev_server = servers
.get("development")
.expect("Should have development server");
assert_eq!(dev_server.host, "localhost:8080");
assert_eq!(dev_server.protocol, "ws");
assert_eq!(dev_server.description, None);
let channels = spec.channels.expect("Should have channels");
assert_eq!(channels.len(), 1);
let chat_channel = channels.get("chat").expect("Should have chat channel");
assert_eq!(chat_channel.address, Some("/ws/chat".to_string()));
let operations = spec.operations.expect("Should have operations");
assert_eq!(operations.len(), 2);
let send_op = operations
.get("sendMessage")
.expect("Should have sendMessage operation");
assert!(matches!(
send_op.action,
asyncapi_rust::OperationAction::Send
));
assert_eq!(send_op.channel.reference, "#/channels/chat");
let receive_op = operations
.get("receiveMessage")
.expect("Should have receiveMessage operation");
assert!(matches!(
receive_op.action,
asyncapi_rust::OperationAction::Receive
));
assert_eq!(receive_op.channel.reference, "#/channels/chat");
}
#[derive(Serialize, Deserialize, JsonSchema, ToAsyncApiMessage)]
#[serde(tag = "type")]
enum ApiMessage {
#[serde(rename = "user.join")]
#[asyncapi(summary = "User joins", description = "User enters a room")]
UserJoin { username: String, room: String },
#[serde(rename = "user.leave")]
#[asyncapi(summary = "User leaves")]
UserLeave { username: String, room: String },
}
#[derive(Serialize, Deserialize, JsonSchema, ToAsyncApiMessage)]
#[serde(tag = "type")]
enum SystemMessage {
#[serde(rename = "system.status")]
#[asyncapi(summary = "System status")]
SystemStatus { status: String },
}
#[derive(AsyncApi)]
#[asyncapi(title = "Message Integration API", version = "1.0.0")]
#[asyncapi_messages(ApiMessage, SystemMessage)]
struct MessageIntegrationApi;
#[test]
fn test_asyncapi_with_messages() {
let spec = MessageIntegrationApi::asyncapi_spec();
assert_eq!(spec.info.title, "Message Integration API");
assert_eq!(spec.info.version, "1.0.0");
let components = spec.components.expect("Should have components");
let messages = components
.messages
.expect("Should have messages in components");
assert_eq!(messages.len(), 3);
let user_join = messages
.get("user.join")
.expect("Should have user.join message");
assert_eq!(user_join.name, Some("user.join".to_string()));
assert_eq!(user_join.summary, Some("User joins".to_string()));
assert_eq!(
user_join.description,
Some("User enters a room".to_string())
);
assert!(user_join.payload.is_some());
let user_leave = messages
.get("user.leave")
.expect("Should have user.leave message");
assert_eq!(user_leave.name, Some("user.leave".to_string()));
assert_eq!(user_leave.summary, Some("User leaves".to_string()));
let system_status = messages
.get("system.status")
.expect("Should have system.status message");
assert_eq!(system_status.name, Some("system.status".to_string()));
assert_eq!(system_status.summary, Some("System status".to_string()));
}