oxihuman_export/
asyncapi_export.rs1#![allow(dead_code)]
4
5use std::collections::BTreeMap;
8
9#[derive(Debug, Clone, PartialEq)]
11pub enum AsyncProtocol {
12 Amqp,
13 Mqtt,
14 WebSocket,
15 Kafka,
16 Http,
17}
18
19impl AsyncProtocol {
20 pub fn name(&self) -> &'static str {
22 match self {
23 Self::Amqp => "amqp",
24 Self::Mqtt => "mqtt",
25 Self::WebSocket => "ws",
26 Self::Kafka => "kafka",
27 Self::Http => "http",
28 }
29 }
30}
31
32#[derive(Debug, Clone)]
34pub struct AsyncMessage {
35 pub name: String,
36 pub summary: String,
37 pub payload_schema: String,
38}
39
40#[derive(Debug, Clone)]
42pub struct AsyncChannel {
43 pub name: String,
44 pub description: Option<String>,
45 pub subscribe: Option<AsyncMessage>,
46 pub publish: Option<AsyncMessage>,
47}
48
49impl AsyncChannel {
50 pub fn new(name: impl Into<String>) -> Self {
52 Self {
53 name: name.into(),
54 description: None,
55 subscribe: None,
56 publish: None,
57 }
58 }
59}
60
61#[derive(Debug, Clone, Default)]
63pub struct AsyncApiSpec {
64 pub title: String,
65 pub version: String,
66 pub protocol: Option<AsyncProtocol>,
67 pub channels: BTreeMap<String, AsyncChannel>,
68 pub servers: Vec<String>,
69}
70
71impl AsyncApiSpec {
72 pub fn add_channel(&mut self, channel: AsyncChannel) {
74 self.channels.insert(channel.name.clone(), channel);
75 }
76
77 pub fn channel_count(&self) -> usize {
79 self.channels.len()
80 }
81
82 pub fn find_channel(&self, name: &str) -> Option<&AsyncChannel> {
84 self.channels.get(name)
85 }
86}
87
88pub fn render_asyncapi_json(spec: &AsyncApiSpec) -> String {
90 let protocol_str = spec.protocol.as_ref().map(|p| p.name()).unwrap_or("ws");
91 let channels: Vec<String> = spec
92 .channels
93 .iter()
94 .map(|(name, ch)| {
95 format!(
96 r#""{name}":{{"description":"{}"}}"#,
97 ch.description.as_deref().unwrap_or("")
98 )
99 })
100 .collect();
101 format!(
102 r#"{{"asyncapi":"2.6.0","info":{{"title":"{}","version":"{}"}},"defaultContentType":"application/json","channels":{{{}}},"protocol":"{}"}}"#,
103 spec.title,
104 spec.version,
105 channels.join(","),
106 protocol_str
107 )
108}
109
110pub fn validate_spec(spec: &AsyncApiSpec) -> bool {
112 !spec.title.is_empty() && !spec.version.is_empty() && !spec.channels.is_empty()
113}
114
115pub fn subscribe_channel_count(spec: &AsyncApiSpec) -> usize {
117 spec.channels
118 .values()
119 .filter(|c| c.subscribe.is_some())
120 .count()
121}
122
123pub fn publish_channel_count(spec: &AsyncApiSpec) -> usize {
125 spec.channels
126 .values()
127 .filter(|c| c.publish.is_some())
128 .count()
129}
130
131pub fn add_server(spec: &mut AsyncApiSpec, url: impl Into<String>) {
133 spec.servers.push(url.into());
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 fn sample_spec() -> AsyncApiSpec {
141 let mut spec = AsyncApiSpec {
142 title: "Test Async".into(),
143 version: "1.0.0".into(),
144 protocol: Some(AsyncProtocol::WebSocket),
145 ..Default::default()
146 };
147 let mut ch = AsyncChannel::new("user/events");
148 ch.description = Some("User event stream".into());
149 ch.subscribe = Some(AsyncMessage {
150 name: "UserCreated".into(),
151 summary: "A user was created".into(),
152 payload_schema: "{}".into(),
153 });
154 spec.add_channel(ch);
155 spec
156 }
157
158 #[test]
159 fn channel_count() {
160 assert_eq!(sample_spec().channel_count(), 1);
161 }
162
163 #[test]
164 fn find_channel_found() {
165 assert!(sample_spec().find_channel("user/events").is_some());
166 }
167
168 #[test]
169 fn find_channel_missing() {
170 assert!(sample_spec().find_channel("nope").is_none());
171 }
172
173 #[test]
174 fn render_contains_asyncapi_version() {
175 assert!(render_asyncapi_json(&sample_spec()).contains("2.6.0"));
176 }
177
178 #[test]
179 fn render_contains_title() {
180 assert!(render_asyncapi_json(&sample_spec()).contains("Test Async"));
181 }
182
183 #[test]
184 fn validate_ok() {
185 assert!(validate_spec(&sample_spec()));
186 }
187
188 #[test]
189 fn validate_no_channels() {
190 let spec = AsyncApiSpec {
191 title: "T".into(),
192 version: "1.0".into(),
193 ..Default::default()
194 };
195 assert!(!validate_spec(&spec));
196 }
197
198 #[test]
199 fn subscribe_count() {
200 assert_eq!(subscribe_channel_count(&sample_spec()), 1);
202 }
203
204 #[test]
205 fn publish_count_zero() {
206 assert_eq!(publish_channel_count(&sample_spec()), 0);
208 }
209
210 #[test]
211 fn add_server_increments() {
212 let mut spec = sample_spec();
213 add_server(&mut spec, "ws://localhost:4000");
214 assert_eq!(spec.servers.len(), 1);
215 }
216}