Skip to main content

iac_forge/
error.rs

1use thiserror::Error;
2
3#[derive(Debug, Error)]
4#[non_exhaustive]
5pub enum IacForgeError {
6    #[error("IO error: {0}")]
7    Io(#[from] std::io::Error),
8
9    #[error("TOML parse error: {0}")]
10    Toml(#[from] toml::de::Error),
11
12    #[error("JSON error: {0}")]
13    Json(#[from] serde_json::Error),
14
15    #[error("OpenAPI error: {0}")]
16    OpenApi(#[from] openapi_forge::ForgeError),
17
18    #[error("missing CRUD endpoint: {resource} needs {endpoint}")]
19    MissingEndpoint { resource: String, endpoint: String },
20
21    #[error("schema not found in spec: {0}")]
22    SchemaNotFound(String),
23
24    #[error("unknown type override: {value} on field {field}")]
25    UnknownTypeOverride { field: String, value: String },
26
27    #[error("validation error: {0}")]
28    ValidationError(String),
29
30    #[error("backend error: {0}")]
31    BackendError(String),
32}
33
34#[cfg(test)]
35mod tests {
36    use super::*;
37
38    #[test]
39    fn display_io_error() {
40        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file missing");
41        let err = IacForgeError::Io(io_err);
42        let msg = err.to_string();
43        assert!(msg.contains("IO error"), "got: {msg}");
44        assert!(msg.contains("file missing"), "got: {msg}");
45    }
46
47    #[test]
48    fn from_io_error() {
49        let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
50        let err: IacForgeError = io_err.into();
51        assert!(matches!(err, IacForgeError::Io(_)));
52    }
53
54    #[test]
55    fn display_toml_error() {
56        let toml_err = toml::from_str::<toml::Value>("{{invalid").unwrap_err();
57        let err = IacForgeError::Toml(toml_err);
58        let msg = err.to_string();
59        assert!(msg.contains("TOML parse error"), "got: {msg}");
60    }
61
62    #[test]
63    fn from_toml_error() {
64        let toml_err = toml::from_str::<toml::Value>("{{").unwrap_err();
65        let err: IacForgeError = toml_err.into();
66        assert!(matches!(err, IacForgeError::Toml(_)));
67    }
68
69    #[test]
70    fn display_json_error() {
71        let json_err = serde_json::from_str::<serde_json::Value>("{bad}").unwrap_err();
72        let err = IacForgeError::Json(json_err);
73        let msg = err.to_string();
74        assert!(msg.contains("JSON error"), "got: {msg}");
75    }
76
77    #[test]
78    fn from_json_error() {
79        let json_err = serde_json::from_str::<serde_json::Value>("not json").unwrap_err();
80        let err: IacForgeError = json_err.into();
81        assert!(matches!(err, IacForgeError::Json(_)));
82    }
83
84    #[test]
85    fn display_missing_endpoint() {
86        let err = IacForgeError::MissingEndpoint {
87            resource: "my_secret".to_string(),
88            endpoint: "/create-secret".to_string(),
89        };
90        let msg = err.to_string();
91        assert!(msg.contains("my_secret"), "got: {msg}");
92        assert!(msg.contains("/create-secret"), "got: {msg}");
93        assert!(msg.contains("missing CRUD endpoint"), "got: {msg}");
94    }
95
96    #[test]
97    fn display_schema_not_found() {
98        let err = IacForgeError::SchemaNotFound("CreateFoo".to_string());
99        let msg = err.to_string();
100        assert!(msg.contains("CreateFoo"), "got: {msg}");
101        assert!(msg.contains("schema not found"), "got: {msg}");
102    }
103
104    #[test]
105    fn display_unknown_type_override() {
106        let err = IacForgeError::UnknownTypeOverride {
107            field: "status".to_string(),
108            value: "BADTYPE".to_string(),
109        };
110        let msg = err.to_string();
111        assert!(msg.contains("unknown type override"), "got: {msg}");
112        assert!(msg.contains("status"), "got: {msg}");
113        assert!(msg.contains("BADTYPE"), "got: {msg}");
114    }
115
116    #[test]
117    fn error_source_unknown_type_override_is_none() {
118        use std::error::Error;
119        let err = IacForgeError::UnknownTypeOverride {
120            field: "f".to_string(),
121            value: "v".to_string(),
122        };
123        assert!(err.source().is_none());
124    }
125
126    #[test]
127    fn display_validation_error() {
128        let err = IacForgeError::ValidationError("field X is invalid".to_string());
129        let msg = err.to_string();
130        assert!(msg.contains("validation error"), "got: {msg}");
131        assert!(msg.contains("field X is invalid"), "got: {msg}");
132    }
133
134    #[test]
135    fn display_backend_error() {
136        let err = IacForgeError::BackendError("template rendering failed".to_string());
137        let msg = err.to_string();
138        assert!(msg.contains("backend error"), "got: {msg}");
139        assert!(msg.contains("template rendering failed"), "got: {msg}");
140    }
141
142    #[test]
143    fn error_is_send_and_sync() {
144        fn assert_send_sync<T: Send + Sync>() {}
145        assert_send_sync::<IacForgeError>();
146    }
147
148    #[test]
149    fn error_source_io() {
150        use std::error::Error;
151        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "gone");
152        let err = IacForgeError::Io(io_err);
153        assert!(err.source().is_some());
154    }
155
156    #[test]
157    fn error_source_json() {
158        use std::error::Error;
159        let json_err = serde_json::from_str::<serde_json::Value>("bad").unwrap_err();
160        let err = IacForgeError::Json(json_err);
161        assert!(err.source().is_some());
162    }
163
164    #[test]
165    fn error_source_toml() {
166        use std::error::Error;
167        let toml_err = toml::from_str::<toml::Value>("{{").unwrap_err();
168        let err = IacForgeError::Toml(toml_err);
169        assert!(err.source().is_some());
170    }
171
172    #[test]
173    fn error_source_backend_is_none() {
174        use std::error::Error;
175        let err = IacForgeError::BackendError("fail".to_string());
176        assert!(err.source().is_none());
177    }
178
179    #[test]
180    fn error_source_validation_is_none() {
181        use std::error::Error;
182        let err = IacForgeError::ValidationError("invalid".to_string());
183        assert!(err.source().is_none());
184    }
185
186    #[test]
187    fn error_source_schema_not_found_is_none() {
188        use std::error::Error;
189        let err = IacForgeError::SchemaNotFound("Schema".to_string());
190        assert!(err.source().is_none());
191    }
192
193    #[test]
194    fn error_source_missing_endpoint_is_none() {
195        use std::error::Error;
196        let err = IacForgeError::MissingEndpoint {
197            resource: "r".to_string(),
198            endpoint: "/ep".to_string(),
199        };
200        assert!(err.source().is_none());
201    }
202
203    #[test]
204    fn debug_format_includes_variant() {
205        let err = IacForgeError::SchemaNotFound("TestSchema".to_string());
206        let debug = format!("{err:?}");
207        assert!(debug.contains("SchemaNotFound"), "got: {debug}");
208    }
209
210    #[test]
211    fn display_missing_endpoint_with_empty_strings() {
212        let err = IacForgeError::MissingEndpoint {
213            resource: String::new(),
214            endpoint: String::new(),
215        };
216        let msg = err.to_string();
217        assert!(msg.contains("missing CRUD endpoint"), "got: {msg}");
218    }
219}