1use std::sync::Arc;
14
15use serde::{Deserialize, Serialize, Serializer};
16use uuid::Uuid;
17
18fn serialize_sha256_hash<S>(hash: &[u8; 32], serializer: S) -> Result<S::Ok, S::Error>
20where
21 S: Serializer,
22{
23 let hex_string = hash.iter().map(|b| format!("{b:02x}")).collect::<String>();
24 serializer.serialize_str(&hex_string)
25}
26
27fn deserialize_sha256_hash<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error>
29where
30 D: serde::Deserializer<'de>,
31{
32 use serde::Deserialize;
33 let hex_string = String::deserialize(deserializer)?;
34
35 if hex_string.len() != 64 {
37 return Err(serde::de::Error::custom(format!(
38 "Invalid SHA256 hash length: expected 64 hex characters, got {}",
39 hex_string.len()
40 )));
41 }
42
43 let mut hash = [0u8; 32];
44 for (i, chunk) in hex_string.as_bytes().chunks(2).enumerate() {
45 if chunk.len() != 2 {
46 return Err(serde::de::Error::custom("Invalid hex string format"));
47 }
48 let byte_str = std::str::from_utf8(chunk)
49 .map_err(|e| serde::de::Error::custom(format!("Invalid UTF-8: {e}")))?;
50 hash[i] = u8::from_str_radix(byte_str, 16)
51 .map_err(|e| serde::de::Error::custom(format!("Invalid hex digit: {e}")))?;
52 }
53
54 Ok(hash)
55}
56
57#[expect(
59 clippy::trivially_copy_pass_by_ref,
60 reason = "serde serialize_with requires &T signature"
61)]
62fn serialize_timestamp<S>(timestamp: &u64, serializer: S) -> Result<S::Ok, S::Error>
63where
64 S: Serializer,
65{
66 use chrono::{DateTime, Utc};
67
68 let secs = (*timestamp / 1_000_000_000) as i64;
70 let nanos = (*timestamp % 1_000_000_000) as u32;
71
72 match DateTime::<Utc>::from_timestamp(secs, nanos) {
73 Some(dt) => {
74 let s = dt.to_rfc3339_opts(chrono::SecondsFormat::Nanos, true);
75 serializer.serialize_str(&s)
76 }
77 None => {
78 let s = format!("{timestamp}");
80 serializer.serialize_str(&s)
81 }
82 }
83}
84
85fn deserialize_timestamp<'de, D>(deserializer: D) -> Result<u64, D::Error>
87where
88 D: serde::Deserializer<'de>,
89{
90 use chrono::{DateTime, Utc};
91 use serde::Deserialize;
92
93 let timestamp_str = String::deserialize(deserializer)?;
94
95 match DateTime::parse_from_rfc3339(×tamp_str) {
97 Ok(dt) => {
98 let dt_utc = dt.with_timezone(&Utc);
100 let secs = dt_utc.timestamp();
101 let nanos = dt_utc.timestamp_subsec_nanos();
102 Ok((secs as u64) * 1_000_000_000 + (nanos as u64))
103 }
104 Err(_) => {
105 timestamp_str
107 .parse::<u64>()
108 .map_err(|e| serde::de::Error::custom(format!("Invalid timestamp format: {e}")))
109 }
110 }
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct WasmModule {
115 pub module_uuid: Uuid,
117 pub module_meta: WasmModuleMeta,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
121pub enum WasmModuleAddResult {
122 Success(Uuid),
123 Error(String),
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct WasmModuleDescriptor {
128 pub name: String,
129 pub file_path: String,
130 pub module_type: WasmModuleType,
131 pub attach_points: Vec<WasmModuleAttachPoint>,
132 pub add_result: Option<WasmModuleAddResult>,
133}
134
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct WasmModuleMeta {
137 pub name: String,
139 pub file_path: String,
141 #[serde(
143 serialize_with = "serialize_sha256_hash",
144 deserialize_with = "deserialize_sha256_hash"
145 )]
146 pub sha256_hash: [u8; 32],
147 pub size_bytes: u64,
149 #[serde(
151 serialize_with = "serialize_timestamp",
152 deserialize_with = "deserialize_timestamp"
153 )]
154 pub created_at: u64,
155 #[serde(
157 serialize_with = "serialize_timestamp",
158 deserialize_with = "deserialize_timestamp"
159 )]
160 pub last_accessed_at: u64,
161 pub access_count: u64,
163 pub attach_points: Vec<WasmModuleAttachPoint>,
165 #[serde(skip)]
168 pub wasm_bytes: Arc<Vec<u8>>,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
172pub enum WasmModuleType {
173 Middleware,
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
177#[expect(
178 clippy::enum_variant_names,
179 reason = "On* prefix is the standard naming convention for middleware lifecycle hooks"
180)]
181pub enum MiddlewareAttachPoint {
182 OnRequest,
183 OnResponse,
184 OnError,
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq, Hash)]
188pub enum WasmModuleAttachPoint {
189 Middleware(MiddlewareAttachPoint),
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
193pub struct WasmModuleAddRequest {
194 pub modules: Vec<WasmModuleDescriptor>,
195}
196
197#[derive(Debug, Clone, Serialize, Deserialize)]
198pub struct WasmModuleAddResponse {
199 pub modules: Vec<WasmModuleDescriptor>,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize)]
203pub struct WasmModuleListResponse {
204 pub modules: Vec<WasmModule>,
205 pub metrics: WasmMetrics,
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize)]
209pub struct WasmMetrics {
210 pub total_executions: u64,
211 pub successful_executions: u64,
212 pub failed_executions: u64,
213 pub total_execution_time_ms: u64,
214 pub max_execution_time_ms: u64,
215 #[serde(skip_serializing_if = "Option::is_none")]
216 pub average_execution_time_ms: Option<f64>,
217}