Skip to main content

cc_lb_plugin_wire/
self_check.rs

1extern crate alloc;
2
3use alloc::string::{String, ToString};
4use alloc::vec::Vec;
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8/// Stages during self-check execution.
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "snake_case")]
11pub enum SelfCheckStage {
12    /// Preparing self-check request.
13    RequestPreparation,
14    /// Testing wire function.
15    WireFunctionTest,
16    /// Verifying capabilities.
17    CapabilityVerification,
18    /// Validating response.
19    ResponseValidation,
20}
21
22/// Status of a self-check execution.
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "snake_case")]
25pub enum SelfCheckStatus {
26    /// Self-check passed successfully.
27    Success,
28    /// Self-check failed (see failures for details).
29    Failure,
30}
31
32/// Describes a single failure during self-check.
33#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(deny_unknown_fields)]
35pub struct SelfCheckFailure {
36    /// Stage at which the failure occurred.
37    pub stage: SelfCheckStage,
38    /// Human-readable error message.
39    pub message: String,
40}
41
42/// Request to perform a self-check on a plugin.
43///
44/// Self-check is a dry-run validation that tests wire functions without invoking handlers.
45/// Uses the skip-handler model: serialize/deserialize round-trip only.
46#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
47#[serde(deny_unknown_fields)]
48pub struct SelfCheckRequest {
49    /// Wire function names to test.
50    pub functions_to_test: Vec<String>,
51    /// Unix timestamp (seconds) when the self-check was initiated.
52    pub initiated_at: i64,
53}
54
55/// Response from a self-check execution.
56///
57/// Skip-handler model: contains only validation results, not handler execution results.
58#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
59#[serde(deny_unknown_fields)]
60pub struct SelfCheckResponse {
61    /// Overall status of the self-check.
62    pub status: SelfCheckStatus,
63    /// Details of any failures encountered.
64    pub failures: Vec<SelfCheckFailure>,
65    /// Unix timestamp (seconds) when the self-check completed.
66    pub completed_at: i64,
67}
68
69/// Error type for self-check operations.
70#[derive(Debug, Clone, Error, PartialEq, Eq)]
71pub enum SelfCheckError {
72    #[error("self-check request is empty (no functions to test)")]
73    EmptyRequest,
74
75    #[error(
76        "self-check function list exceeds maximum of {}",
77        crate::limits::IMPLEMENTED_FUNCTIONS_MAX
78    )]
79    TooManyFunctions,
80
81    #[error("invalid timestamp: {0}")]
82    InvalidTimestamp(String),
83
84    #[error("self-check response serialization failed: {0}")]
85    SerializationFailed(String),
86}
87
88impl SelfCheckRequest {
89    /// Validate this request.
90    pub fn validate(&self) -> Result<(), SelfCheckError> {
91        if self.functions_to_test.is_empty() {
92            return Err(SelfCheckError::EmptyRequest);
93        }
94
95        if self.functions_to_test.len() > crate::limits::IMPLEMENTED_FUNCTIONS_MAX {
96            return Err(SelfCheckError::TooManyFunctions);
97        }
98
99        if self.initiated_at <= 0 {
100            return Err(SelfCheckError::InvalidTimestamp(
101                "initiated_at must be positive".to_string(),
102            ));
103        }
104
105        Ok(())
106    }
107}
108
109impl SelfCheckResponse {
110    /// Validate this response.
111    pub fn validate(&self) -> Result<(), SelfCheckError> {
112        if self.completed_at <= 0 {
113            return Err(SelfCheckError::InvalidTimestamp(
114                "completed_at must be positive".to_string(),
115            ));
116        }
117
118        if self.failures.len() > crate::limits::IMPLEMENTED_FUNCTIONS_MAX {
119            return Err(SelfCheckError::TooManyFunctions);
120        }
121
122        Ok(())
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129    use alloc::format;
130    use alloc::vec;
131
132    #[test]
133    fn test_self_check_stage_serde() {
134        let stages = [
135            SelfCheckStage::RequestPreparation,
136            SelfCheckStage::WireFunctionTest,
137            SelfCheckStage::CapabilityVerification,
138            SelfCheckStage::ResponseValidation,
139        ];
140
141        for stage in &stages {
142            let json = serde_json::to_string(stage).unwrap();
143            let deserialized: SelfCheckStage = serde_json::from_str(&json).unwrap();
144            assert_eq!(*stage, deserialized);
145        }
146    }
147
148    #[test]
149    fn test_self_check_status_serde() {
150        let statuses = [SelfCheckStatus::Success, SelfCheckStatus::Failure];
151
152        for status in &statuses {
153            let json = serde_json::to_string(status).unwrap();
154            let deserialized: SelfCheckStatus = serde_json::from_str(&json).unwrap();
155            assert_eq!(*status, deserialized);
156        }
157    }
158
159    #[test]
160    fn test_self_check_failure_serde() {
161        let failure = SelfCheckFailure {
162            stage: SelfCheckStage::WireFunctionTest,
163            message: "test failed".to_string(),
164        };
165
166        let json = serde_json::to_string(&failure).unwrap();
167        let deserialized: SelfCheckFailure = serde_json::from_str(&json).unwrap();
168        assert_eq!(failure, deserialized);
169    }
170
171    #[test]
172    fn test_self_check_failure_deny_unknown_fields() {
173        let json = r#"{"stage":"wire_function_test","message":"test","unknown":"field"}"#;
174        let result: Result<SelfCheckFailure, _> = serde_json::from_str(json);
175        assert!(result.is_err());
176    }
177
178    #[test]
179    fn test_self_check_request_valid() {
180        let request = SelfCheckRequest {
181            functions_to_test: vec!["route".to_string(), "observe".to_string()],
182            initiated_at: 1000,
183        };
184        assert!(request.validate().is_ok());
185    }
186
187    #[test]
188    fn test_self_check_request_empty() {
189        let request = SelfCheckRequest {
190            functions_to_test: vec![],
191            initiated_at: 1000,
192        };
193        assert_eq!(request.validate(), Err(SelfCheckError::EmptyRequest));
194    }
195
196    #[test]
197    fn test_self_check_request_too_many_functions() {
198        let mut functions = Vec::new();
199        for i in 0..=crate::limits::IMPLEMENTED_FUNCTIONS_MAX {
200            functions.push(format!("func_{}", i));
201        }
202
203        let request = SelfCheckRequest {
204            functions_to_test: functions,
205            initiated_at: 1000,
206        };
207        assert_eq!(request.validate(), Err(SelfCheckError::TooManyFunctions));
208    }
209
210    #[test]
211    fn test_self_check_request_invalid_timestamp() {
212        let request = SelfCheckRequest {
213            functions_to_test: vec!["route".to_string()],
214            initiated_at: 0,
215        };
216        assert!(request.validate().is_err());
217
218        let request2 = SelfCheckRequest {
219            functions_to_test: vec!["route".to_string()],
220            initiated_at: -100,
221        };
222        assert!(request2.validate().is_err());
223    }
224
225    #[test]
226    fn test_self_check_request_serde() {
227        let request = SelfCheckRequest {
228            functions_to_test: vec!["route".to_string(), "observe".to_string()],
229            initiated_at: 1234567890,
230        };
231
232        let json = serde_json::to_string(&request).unwrap();
233        let deserialized: SelfCheckRequest = serde_json::from_str(&json).unwrap();
234        assert_eq!(request, deserialized);
235    }
236
237    #[test]
238    fn test_self_check_request_deny_unknown_fields() {
239        let json = r#"{"functions_to_test":["route"],"initiated_at":1000,"unknown":"field"}"#;
240        let result: Result<SelfCheckRequest, _> = serde_json::from_str(json);
241        assert!(result.is_err());
242    }
243
244    #[test]
245    fn test_self_check_response_success() {
246        let response = SelfCheckResponse {
247            status: SelfCheckStatus::Success,
248            failures: vec![],
249            completed_at: 2000,
250        };
251        assert!(response.validate().is_ok());
252    }
253
254    #[test]
255    fn test_self_check_response_with_failures() {
256        let response = SelfCheckResponse {
257            status: SelfCheckStatus::Failure,
258            failures: vec![SelfCheckFailure {
259                stage: SelfCheckStage::WireFunctionTest,
260                message: "function not found".to_string(),
261            }],
262            completed_at: 2000,
263        };
264        assert!(response.validate().is_ok());
265    }
266
267    #[test]
268    fn test_self_check_response_invalid_timestamp() {
269        let response = SelfCheckResponse {
270            status: SelfCheckStatus::Success,
271            failures: vec![],
272            completed_at: 0,
273        };
274        assert!(response.validate().is_err());
275    }
276
277    #[test]
278    fn test_self_check_response_serde() {
279        let response = SelfCheckResponse {
280            status: SelfCheckStatus::Success,
281            failures: vec![],
282            completed_at: 1234567890,
283        };
284
285        let json = serde_json::to_string(&response).unwrap();
286        let deserialized: SelfCheckResponse = serde_json::from_str(&json).unwrap();
287        assert_eq!(response, deserialized);
288    }
289
290    #[test]
291    fn test_self_check_response_deny_unknown_fields() {
292        let json = r#"{"status":"success","failures":[],"completed_at":2000,"unknown":"field"}"#;
293        let result: Result<SelfCheckResponse, _> = serde_json::from_str(json);
294        assert!(result.is_err());
295    }
296
297    #[test]
298    fn test_status_snake_case_names() {
299        assert_eq!(
300            serde_json::to_string(&SelfCheckStatus::Success).unwrap(),
301            "\"success\""
302        );
303        assert_eq!(
304            serde_json::to_string(&SelfCheckStatus::Failure).unwrap(),
305            "\"failure\""
306        );
307    }
308
309    #[test]
310    fn test_stage_snake_case_names() {
311        assert_eq!(
312            serde_json::to_string(&SelfCheckStage::RequestPreparation).unwrap(),
313            "\"request_preparation\""
314        );
315        assert_eq!(
316            serde_json::to_string(&SelfCheckStage::WireFunctionTest).unwrap(),
317            "\"wire_function_test\""
318        );
319    }
320}