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}