spikard 0.8.2 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/ext/spikard_rb/Cargo.lock +6 -6
  3. data/ext/spikard_rb/Cargo.toml +1 -1
  4. data/lib/spikard/version.rb +1 -1
  5. data/vendor/crates/spikard-bindings-shared/Cargo.toml +9 -1
  6. data/vendor/crates/spikard-bindings-shared/src/config_extractor.rs +61 -23
  7. data/vendor/crates/spikard-bindings-shared/src/conversion_traits.rs +16 -0
  8. data/vendor/crates/spikard-bindings-shared/src/di_traits.rs +1 -1
  9. data/vendor/crates/spikard-bindings-shared/src/error_response.rs +22 -19
  10. data/vendor/crates/spikard-bindings-shared/src/grpc_metadata.rs +14 -12
  11. data/vendor/crates/spikard-bindings-shared/src/handler_base.rs +15 -6
  12. data/vendor/crates/spikard-bindings-shared/src/lifecycle_base.rs +6 -0
  13. data/vendor/crates/spikard-bindings-shared/src/lifecycle_executor.rs +42 -36
  14. data/vendor/crates/spikard-bindings-shared/src/response_builder.rs +6 -1
  15. data/vendor/crates/spikard-bindings-shared/src/test_client_base.rs +18 -6
  16. data/vendor/crates/spikard-bindings-shared/src/validation_helpers.rs +28 -10
  17. data/vendor/crates/spikard-core/Cargo.toml +9 -1
  18. data/vendor/crates/spikard-core/src/bindings/response.rs +6 -9
  19. data/vendor/crates/spikard-core/src/debug.rs +2 -2
  20. data/vendor/crates/spikard-core/src/di/container.rs +1 -1
  21. data/vendor/crates/spikard-core/src/di/error.rs +1 -1
  22. data/vendor/crates/spikard-core/src/di/factory.rs +7 -3
  23. data/vendor/crates/spikard-core/src/di/graph.rs +1 -0
  24. data/vendor/crates/spikard-core/src/di/resolved.rs +23 -0
  25. data/vendor/crates/spikard-core/src/di/value.rs +1 -0
  26. data/vendor/crates/spikard-core/src/errors.rs +3 -0
  27. data/vendor/crates/spikard-core/src/http.rs +19 -18
  28. data/vendor/crates/spikard-core/src/lifecycle.rs +42 -18
  29. data/vendor/crates/spikard-core/src/parameters.rs +61 -35
  30. data/vendor/crates/spikard-core/src/problem.rs +18 -4
  31. data/vendor/crates/spikard-core/src/request_data.rs +9 -8
  32. data/vendor/crates/spikard-core/src/router.rs +20 -6
  33. data/vendor/crates/spikard-core/src/schema_registry.rs +23 -8
  34. data/vendor/crates/spikard-core/src/type_hints.rs +11 -5
  35. data/vendor/crates/spikard-core/src/validation/error_mapper.rs +29 -15
  36. data/vendor/crates/spikard-core/src/validation/mod.rs +45 -32
  37. data/vendor/crates/spikard-http/Cargo.toml +8 -1
  38. data/vendor/crates/spikard-rb/Cargo.toml +9 -1
  39. data/vendor/crates/spikard-rb/build.rs +1 -0
  40. data/vendor/crates/spikard-rb/src/lib.rs +58 -0
  41. data/vendor/crates/spikard-rb/src/lifecycle.rs +2 -2
  42. data/vendor/crates/spikard-rb-macros/Cargo.toml +9 -1
  43. data/vendor/crates/spikard-rb-macros/src/lib.rs +4 -5
  44. metadata +1 -1
@@ -29,6 +29,10 @@ pub enum HookResult {
29
29
  /// Trait for implementing lifecycle hooks in language bindings
30
30
  pub trait LifecycleHook: Send + Sync {
31
31
  /// Execute the lifecycle hook
32
+ ///
33
+ /// # Errors
34
+ ///
35
+ /// Returns an error if hook execution fails.
32
36
  fn execute(&self, context: serde_json::Value) -> Result<HookResult, String>;
33
37
 
34
38
  /// Get the hook type
@@ -43,6 +47,7 @@ pub struct LifecycleConfig {
43
47
 
44
48
  impl LifecycleConfig {
45
49
  /// Create a new lifecycle configuration
50
+ #[must_use]
46
51
  pub fn new() -> Self {
47
52
  Self {
48
53
  hooks: std::collections::HashMap::new(),
@@ -55,6 +60,7 @@ impl LifecycleConfig {
55
60
  }
56
61
 
57
62
  /// Get hooks for a specific type
63
+ #[must_use]
58
64
  pub fn get_hooks(&self, hook_type: LifecycleHookType) -> Vec<Arc<dyn LifecycleHook>> {
59
65
  self.hooks.get(&hook_type).cloned().unwrap_or_default()
60
66
  }
@@ -10,7 +10,7 @@
10
10
  //! `LanguageLifecycleHook` to provide language-specific hook invocation, while
11
11
  //! `LifecycleExecutor` handles the common logic:
12
12
  //!
13
- //! - Hook result type handling (Continue vs ShortCircuit)
13
+ //! - Hook result type handling (Continue vs `ShortCircuit`)
14
14
  //! - Response/Request building from hook results
15
15
  //! - Error handling and conversion
16
16
  //!
@@ -73,7 +73,8 @@ pub struct RequestModifications {
73
73
 
74
74
  impl HookResultData {
75
75
  /// Create a Continue result (pass through)
76
- pub fn continue_execution() -> Self {
76
+ #[must_use]
77
+ pub const fn continue_execution() -> Self {
77
78
  Self {
78
79
  continue_execution: true,
79
80
  status_code: None,
@@ -84,7 +85,8 @@ impl HookResultData {
84
85
  }
85
86
 
86
87
  /// Create a short-circuit response result
87
- pub fn short_circuit(status_code: u16, body: Vec<u8>, headers: Option<HashMap<String, String>>) -> Self {
88
+ #[must_use]
89
+ pub const fn short_circuit(status_code: u16, body: Vec<u8>, headers: Option<HashMap<String, String>>) -> Self {
88
90
  Self {
89
91
  continue_execution: false,
90
92
  status_code: Some(status_code),
@@ -95,7 +97,8 @@ impl HookResultData {
95
97
  }
96
98
 
97
99
  /// Create a request modification result
98
- pub fn modify_request(modifications: RequestModifications) -> Self {
100
+ #[must_use]
101
+ pub const fn modify_request(modifications: RequestModifications) -> Self {
99
102
  Self {
100
103
  continue_execution: true,
101
104
  status_code: None,
@@ -117,6 +120,10 @@ pub trait LanguageLifecycleHook: Send + Sync {
117
120
  /// Prepare hook data from the incoming request/response
118
121
  ///
119
122
  /// This should convert axum HTTP types to language-specific representations.
123
+ ///
124
+ /// # Errors
125
+ ///
126
+ /// Returns an error if hook data preparation fails.
120
127
  fn prepare_hook_data(&self, req: &Request<Body>) -> Result<Self::HookData, String>;
121
128
 
122
129
  /// Invoke the language hook and return normalized result data
@@ -139,13 +146,17 @@ pub struct LifecycleExecutor<L: LanguageLifecycleHook> {
139
146
 
140
147
  impl<L: LanguageLifecycleHook> LifecycleExecutor<L> {
141
148
  /// Create a new executor for the given hook
142
- pub fn new(hook: Arc<L>) -> Self {
149
+ pub const fn new(hook: Arc<L>) -> Self {
143
150
  Self { hook }
144
151
  }
145
152
 
146
153
  /// Execute a request hook, handling Continue/ShortCircuit semantics
147
154
  ///
148
155
  /// Returns either the modified request or a short-circuit response.
156
+ ///
157
+ /// # Errors
158
+ ///
159
+ /// Returns an error if hook execution or modification fails.
149
160
  pub async fn execute_request_hook(
150
161
  &self,
151
162
  req: Request<Body>,
@@ -154,12 +165,12 @@ impl<L: LanguageLifecycleHook> LifecycleExecutor<L> {
154
165
  let result = self.hook.invoke_hook(hook_data).await?;
155
166
 
156
167
  if !result.continue_execution {
157
- let response = self.build_response_from_hook_result(&result)?;
168
+ let response = Self::build_response_from_hook_result(&result)?;
158
169
  return Ok(Err(response));
159
170
  }
160
171
 
161
172
  if let Some(modifications) = result.request_modifications {
162
- let modified_req = self.apply_request_modifications(req, modifications)?;
173
+ let modified_req = Self::apply_request_modifications(req, modifications)?;
163
174
  Ok(Ok(modified_req))
164
175
  } else {
165
176
  Ok(Ok(req))
@@ -170,6 +181,11 @@ impl<L: LanguageLifecycleHook> LifecycleExecutor<L> {
170
181
  ///
171
182
  /// Response hooks can only continue or modify the response,
172
183
  /// never short-circuit.
184
+ /// Execute the lifecycle hook on an outgoing response
185
+ ///
186
+ /// # Errors
187
+ ///
188
+ /// Returns an error if hook execution or response building fails.
173
189
  pub async fn execute_response_hook(&self, resp: Response<Body>) -> Result<Response<Body>, String> {
174
190
  let (parts, body) = resp.into_parts();
175
191
  let body_bytes = extract_body(body).await?;
@@ -178,7 +194,7 @@ impl<L: LanguageLifecycleHook> LifecycleExecutor<L> {
178
194
  .method("GET")
179
195
  .uri("/")
180
196
  .body(Body::empty())
181
- .map_err(|e| format!("Failed to build dummy request: {}", e))?;
197
+ .map_err(|e| format!("Failed to build dummy request: {e}"))?;
182
198
 
183
199
  let hook_data = self.hook.prepare_hook_data(&dummy_req)?;
184
200
  let result = self.hook.invoke_hook(hook_data).await?;
@@ -198,7 +214,7 @@ impl<L: LanguageLifecycleHook> LifecycleExecutor<L> {
198
214
  }
199
215
  }
200
216
 
201
- for (name, value) in parts.headers.iter() {
217
+ for (name, value) in &parts.headers {
202
218
  let key_str = name.as_str().to_lowercase();
203
219
  if !header_mod_keys.contains(&key_str) {
204
220
  builder = builder.header(name, value);
@@ -208,7 +224,7 @@ impl<L: LanguageLifecycleHook> LifecycleExecutor<L> {
208
224
  let body = modifications.body.unwrap_or(body_bytes);
209
225
  return builder
210
226
  .body(Body::from(body))
211
- .map_err(|e| format!("Failed to build modified response: {}", e));
227
+ .map_err(|e| format!("Failed to build modified response: {e}"));
212
228
  }
213
229
 
214
230
  let mut builder = Response::builder().status(parts.status);
@@ -219,14 +235,14 @@ impl<L: LanguageLifecycleHook> LifecycleExecutor<L> {
219
235
  }
220
236
  builder
221
237
  .body(Body::from(body_bytes))
222
- .map_err(|e| format!("Failed to rebuild response: {}", e))
238
+ .map_err(|e| format!("Failed to rebuild response: {e}"))
223
239
  }
224
240
 
225
241
  /// Build an axum Response from hook result data
226
- fn build_response_from_hook_result(&self, result: &HookResultData) -> Result<Response<Body>, String> {
242
+ fn build_response_from_hook_result(result: &HookResultData) -> Result<Response<Body>, String> {
227
243
  let status_code = result.status_code.unwrap_or(200);
228
244
  let status =
229
- StatusCode::from_u16(status_code).map_err(|e| format!("Invalid status code {}: {}", status_code, e))?;
245
+ StatusCode::from_u16(status_code).map_err(|e| format!("Invalid status code {status_code}: {e}"))?;
230
246
 
231
247
  let mut builder = Response::builder().status(status);
232
248
 
@@ -236,11 +252,7 @@ impl<L: LanguageLifecycleHook> LifecycleExecutor<L> {
236
252
  }
237
253
  }
238
254
 
239
- if !builder
240
- .headers_ref()
241
- .map(|h| h.contains_key("content-type"))
242
- .unwrap_or(false)
243
- {
255
+ if !builder.headers_ref().is_some_and(|h| h.contains_key("content-type")) {
244
256
  builder = builder.header("content-type", "application/json");
245
257
  }
246
258
 
@@ -248,43 +260,33 @@ impl<L: LanguageLifecycleHook> LifecycleExecutor<L> {
248
260
 
249
261
  builder
250
262
  .body(Body::from(body))
251
- .map_err(|e| format!("Failed to build response: {}", e))
263
+ .map_err(|e| format!("Failed to build response: {e}"))
252
264
  }
253
265
 
254
266
  /// Apply request modifications to a request
255
- fn apply_request_modifications(
256
- &self,
257
- req: Request<Body>,
258
- mods: RequestModifications,
259
- ) -> Result<Request<Body>, String> {
267
+ fn apply_request_modifications(req: Request<Body>, mods: RequestModifications) -> Result<Request<Body>, String> {
260
268
  let (mut parts, body) = req.into_parts();
261
269
 
262
270
  if let Some(method) = &mods.method {
263
- parts.method = method
264
- .parse()
265
- .map_err(|e| format!("Invalid method '{}': {}", method, e))?;
271
+ parts.method = method.parse().map_err(|e| format!("Invalid method '{method}': {e}"))?;
266
272
  }
267
273
 
268
274
  if let Some(path) = &mods.path {
269
- parts.uri = path.parse().map_err(|e| format!("Invalid path '{}': {}", path, e))?;
275
+ parts.uri = path.parse().map_err(|e| format!("Invalid path '{path}': {e}"))?;
270
276
  }
271
277
 
272
278
  if let Some(new_headers) = &mods.headers {
273
279
  for (key, value) in new_headers {
274
280
  let header_name: http::header::HeaderName =
275
- key.parse().map_err(|_| format!("Invalid header name: {}", key))?;
281
+ key.parse().map_err(|_| format!("Invalid header name: {key}"))?;
276
282
  let header_value: http::header::HeaderValue = value
277
283
  .parse()
278
- .map_err(|_| format!("Invalid header value for {}: {}", key, value))?;
284
+ .map_err(|_| format!("Invalid header value for {key}: {value}"))?;
279
285
  parts.headers.insert(header_name, header_value);
280
286
  }
281
287
  }
282
288
 
283
- let body = if let Some(new_body) = mods.body {
284
- Body::from(new_body)
285
- } else {
286
- body
287
- };
289
+ let body = mods.body.map_or(body, Body::from);
288
290
 
289
291
  Ok(Request::from_parts(parts, body))
290
292
  }
@@ -293,13 +295,17 @@ impl<L: LanguageLifecycleHook> LifecycleExecutor<L> {
293
295
  /// Extract body bytes from an axum Body
294
296
  ///
295
297
  /// This is a helper used by lifecycle executors to read response bodies.
298
+ ///
299
+ /// # Errors
300
+ ///
301
+ /// Returns an error if body collection fails.
296
302
  pub async fn extract_body(body: Body) -> Result<Vec<u8>, String> {
297
303
  use http_body_util::BodyExt;
298
304
 
299
305
  let bytes = body
300
306
  .collect()
301
307
  .await
302
- .map_err(|e| format!("Failed to read body: {}", e))?
308
+ .map_err(|e| format!("Failed to read body: {e}"))?
303
309
  .to_bytes();
304
310
  Ok(bytes.to_vec())
305
311
  }
@@ -12,6 +12,7 @@ pub struct ResponseBuilder {
12
12
 
13
13
  impl ResponseBuilder {
14
14
  /// Create a new response builder with default status 200 OK
15
+ #[must_use]
15
16
  pub fn new() -> Self {
16
17
  Self {
17
18
  status: StatusCode::OK,
@@ -21,18 +22,21 @@ impl ResponseBuilder {
21
22
  }
22
23
 
23
24
  /// Set the HTTP status code
24
- pub fn status(mut self, status: StatusCode) -> Self {
25
+ #[must_use]
26
+ pub const fn status(mut self, status: StatusCode) -> Self {
25
27
  self.status = status;
26
28
  self
27
29
  }
28
30
 
29
31
  /// Set the response body
32
+ #[must_use]
30
33
  pub fn body(mut self, body: serde_json::Value) -> Self {
31
34
  self.body = body;
32
35
  self
33
36
  }
34
37
 
35
38
  /// Add a response header
39
+ #[must_use]
36
40
  pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
37
41
  if let Ok(name) = key.into().parse::<header::HeaderName>()
38
42
  && let Ok(val) = value.into().parse::<header::HeaderValue>()
@@ -43,6 +47,7 @@ impl ResponseBuilder {
43
47
  }
44
48
 
45
49
  /// Build the response as (status, headers, body)
50
+ #[must_use]
46
51
  pub fn build(self) -> (StatusCode, HeaderMap, String) {
47
52
  let body = serde_json::to_string(&self.body).unwrap_or_else(|_| "{}".to_string());
48
53
  (self.status, self.headers, body)
@@ -23,13 +23,15 @@ impl TestClientConfig {
23
23
  }
24
24
 
25
25
  /// Set the timeout in milliseconds
26
- pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
26
+ #[must_use]
27
+ pub const fn with_timeout(mut self, timeout_ms: u64) -> Self {
27
28
  self.timeout_ms = timeout_ms;
28
29
  self
29
30
  }
30
31
 
31
32
  /// Set whether to follow redirects
32
- pub fn with_follow_redirects(mut self, follow_redirects: bool) -> Self {
33
+ #[must_use]
34
+ pub const fn with_follow_redirects(mut self, follow_redirects: bool) -> Self {
33
35
  self.follow_redirects = follow_redirects;
34
36
  self
35
37
  }
@@ -60,7 +62,13 @@ pub struct TestResponseMetadata {
60
62
 
61
63
  impl TestResponseMetadata {
62
64
  /// Create a new test response metadata
63
- pub fn new(status_code: u16, headers: HashMap<String, String>, body_size: usize, response_time_ms: u64) -> Self {
65
+ #[must_use]
66
+ pub const fn new(
67
+ status_code: u16,
68
+ headers: HashMap<String, String>,
69
+ body_size: usize,
70
+ response_time_ms: u64,
71
+ ) -> Self {
64
72
  Self {
65
73
  status_code,
66
74
  headers,
@@ -70,6 +78,7 @@ impl TestResponseMetadata {
70
78
  }
71
79
 
72
80
  /// Get a header value by name (case-insensitive)
81
+ #[must_use]
73
82
  pub fn get_header(&self, name: &str) -> Option<&String> {
74
83
  let lower_name = name.to_lowercase();
75
84
  self.headers
@@ -79,17 +88,20 @@ impl TestResponseMetadata {
79
88
  }
80
89
 
81
90
  /// Check if response was successful (2xx status code)
82
- pub fn is_success(&self) -> bool {
91
+ #[must_use]
92
+ pub const fn is_success(&self) -> bool {
83
93
  self.status_code >= 200 && self.status_code < 300
84
94
  }
85
95
 
86
96
  /// Check if response was a client error (4xx status code)
87
- pub fn is_client_error(&self) -> bool {
97
+ #[must_use]
98
+ pub const fn is_client_error(&self) -> bool {
88
99
  self.status_code >= 400 && self.status_code < 500
89
100
  }
90
101
 
91
102
  /// Check if response was a server error (5xx status code)
92
- pub fn is_server_error(&self) -> bool {
103
+ #[must_use]
104
+ pub const fn is_server_error(&self) -> bool {
93
105
  self.status_code >= 500 && self.status_code < 600
94
106
  }
95
107
  }
@@ -7,29 +7,37 @@ pub struct HeaderValidator;
7
7
 
8
8
  impl HeaderValidator {
9
9
  /// Validate that required headers are present
10
+ ///
11
+ /// # Errors
12
+ ///
13
+ /// Returns an error if required headers are missing.
10
14
  pub fn validate_required(headers: &[(String, String)], required: &[&str]) -> Result<(), String> {
11
15
  let header_names: std::collections::HashSet<_> = headers.iter().map(|(k, _)| k.to_lowercase()).collect();
12
16
 
13
17
  for req in required {
14
18
  if !header_names.contains(&req.to_lowercase()) {
15
- return Err(format!("Missing required header: {}", req));
19
+ return Err(format!("Missing required header: {req}"));
16
20
  }
17
21
  }
18
22
  Ok(())
19
23
  }
20
24
 
21
25
  /// Validate header format
26
+ ///
27
+ /// # Errors
28
+ ///
29
+ /// Returns an error if the header format is invalid.
22
30
  pub fn validate_format(key: &str, value: &str, format: HeaderFormat) -> Result<(), String> {
23
31
  match format {
24
32
  HeaderFormat::Bearer => {
25
33
  if !value.starts_with("Bearer ") {
26
- return Err(format!("{}: must start with 'Bearer '", key));
34
+ return Err(format!("{key}: must start with 'Bearer '"));
27
35
  }
28
36
  Ok(())
29
37
  }
30
38
  HeaderFormat::Json => {
31
39
  if !value.starts_with("application/json") {
32
- return Err(format!("{}: must be 'application/json'", key));
40
+ return Err(format!("{key}: must be 'application/json'"));
33
41
  }
34
42
  Ok(())
35
43
  }
@@ -38,6 +46,7 @@ impl HeaderValidator {
38
46
  }
39
47
 
40
48
  /// Header validation formats
49
+ #[derive(Copy, Clone)]
41
50
  pub enum HeaderFormat {
42
51
  /// Bearer token format
43
52
  Bearer,
@@ -50,6 +59,10 @@ pub struct BodyValidator;
50
59
 
51
60
  impl BodyValidator {
52
61
  /// Validate that required fields are present in a JSON object
62
+ ///
63
+ /// # Errors
64
+ ///
65
+ /// Returns an error if required fields are missing.
53
66
  pub fn validate_required_fields(body: &Value, required: &[&str]) -> Result<(), String> {
54
67
  let obj = body
55
68
  .as_object()
@@ -57,44 +70,48 @@ impl BodyValidator {
57
70
 
58
71
  for field in required {
59
72
  if !obj.contains_key(*field) {
60
- return Err(format!("Missing required field: {}", field));
73
+ return Err(format!("Missing required field: {field}"));
61
74
  }
62
75
  }
63
76
  Ok(())
64
77
  }
65
78
 
66
79
  /// Validate field type
80
+ ///
81
+ /// # Errors
82
+ ///
83
+ /// Returns an error if the field type doesn't match.
67
84
  pub fn validate_field_type(body: &Value, field: &str, expected_type: FieldType) -> Result<(), String> {
68
85
  let obj = body
69
86
  .as_object()
70
87
  .ok_or_else(|| "Body must be a JSON object".to_string())?;
71
88
 
72
- let value = obj.get(field).ok_or_else(|| format!("Field not found: {}", field))?;
89
+ let value = obj.get(field).ok_or_else(|| format!("Field not found: {field}"))?;
73
90
 
74
91
  match expected_type {
75
92
  FieldType::String => {
76
93
  if !value.is_string() {
77
- return Err(format!("{}: expected string", field));
94
+ return Err(format!("{field}: expected string"));
78
95
  }
79
96
  }
80
97
  FieldType::Number => {
81
98
  if !value.is_number() {
82
- return Err(format!("{}: expected number", field));
99
+ return Err(format!("{field}: expected number"));
83
100
  }
84
101
  }
85
102
  FieldType::Boolean => {
86
103
  if !value.is_boolean() {
87
- return Err(format!("{}: expected boolean", field));
104
+ return Err(format!("{field}: expected boolean"));
88
105
  }
89
106
  }
90
107
  FieldType::Object => {
91
108
  if !value.is_object() {
92
- return Err(format!("{}: expected object", field));
109
+ return Err(format!("{field}: expected object"));
93
110
  }
94
111
  }
95
112
  FieldType::Array => {
96
113
  if !value.is_array() {
97
- return Err(format!("{}: expected array", field));
114
+ return Err(format!("{field}: expected array"));
98
115
  }
99
116
  }
100
117
  }
@@ -103,6 +120,7 @@ impl BodyValidator {
103
120
  }
104
121
 
105
122
  /// Field types for validation
123
+ #[derive(Copy, Clone)]
106
124
  pub enum FieldType {
107
125
  String,
108
126
  Number,
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "spikard-core"
3
- version = "0.8.2"
3
+ version = "0.8.3"
4
4
  edition = "2024"
5
5
  authors = ["Na'aman Hirschfeld <nhirschfeld@gmail.com>"]
6
6
  license = "MIT"
@@ -12,6 +12,14 @@ categories = ["web-programming::http-server", "web-programming", "development-to
12
12
  documentation = "https://docs.rs/spikard-core"
13
13
  readme = "README.md"
14
14
 
15
+ [lints.rust]
16
+ unexpected_cfgs = { level = "allow", check-cfg = ['cfg(tarpaulin_include)'] }
17
+
18
+ [lints.clippy]
19
+ all = { level = "deny", priority = 0 }
20
+ pedantic = { level = "deny", priority = 0 }
21
+ nursery = { level = "deny", priority = 0 }
22
+
15
23
  [dependencies]
16
24
  serde = { version = "1.0", features = ["derive"] }
17
25
  serde_json = "1.0"
@@ -15,6 +15,8 @@ pub struct RawResponse {
15
15
 
16
16
  impl RawResponse {
17
17
  /// Construct a new response.
18
+ #[must_use]
19
+ #[allow(clippy::missing_const_for_fn)]
18
20
  pub fn new(status: u16, headers: HashMap<String, String>, body: Vec<u8>) -> Self {
19
21
  Self { status, headers, body }
20
22
  }
@@ -35,19 +37,13 @@ impl RawResponse {
35
37
  return;
36
38
  }
37
39
 
38
- let accept_encoding = header_value(request_headers, "Accept-Encoding").map(|value| value.to_ascii_lowercase());
39
- let accepts_brotli = accept_encoding
40
- .as_ref()
41
- .map(|value| value.contains("br"))
42
- .unwrap_or(false);
40
+ let accept_encoding = header_value(request_headers, "Accept-Encoding").map(str::to_ascii_lowercase);
41
+ let accepts_brotli = accept_encoding.as_ref().is_some_and(|value| value.contains("br"));
43
42
  if compression.brotli && accepts_brotli && self.try_compress_brotli(compression) {
44
43
  return;
45
44
  }
46
45
 
47
- let accepts_gzip = accept_encoding
48
- .as_ref()
49
- .map(|value| value.contains("gzip"))
50
- .unwrap_or(false);
46
+ let accepts_gzip = accept_encoding.as_ref().is_some_and(|value| value.contains("gzip"));
51
47
  if compression.gzip && accepts_gzip {
52
48
  self.try_compress_gzip(compression);
53
49
  }
@@ -110,6 +106,7 @@ pub struct StaticAsset {
110
106
 
111
107
  impl StaticAsset {
112
108
  /// Build a response snapshot if the incoming request targets this asset.
109
+ #[must_use]
113
110
  pub fn serve(&self, method: &str, normalized_path: &str) -> Option<RawResponse> {
114
111
  if !method.eq_ignore_ascii_case("GET") && !method.eq_ignore_ascii_case("HEAD") {
115
112
  return None;
@@ -1,8 +1,8 @@
1
1
  //! Debug logging utilities for spikard-http
2
2
  //!
3
3
  //! This module provides debug logging that can be enabled via:
4
- //! - Building in debug mode (cfg(debug_assertions))
5
- //! - Setting SPIKARD_DEBUG=1 environment variable
4
+ //! - Building in debug mode (`cfg(debug_assertions)`)
5
+ //! - Setting `SPIKARD_DEBUG=1` environment variable
6
6
 
7
7
  use std::sync::atomic::{AtomicBool, Ordering};
8
8
 
@@ -365,7 +365,7 @@ impl std::fmt::Debug for DependencyContainer {
365
365
  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366
366
  f.debug_struct("DependencyContainer")
367
367
  .field("dependencies", &self.dependencies.keys())
368
- .finish()
368
+ .finish_non_exhaustive()
369
369
  }
370
370
  }
371
371
 
@@ -33,7 +33,7 @@ pub enum DependencyError {
33
33
  /// ```
34
34
  #[error("Circular dependency detected: {cycle:?}")]
35
35
  CircularDependency {
36
- /// The cycle of dependencies (e.g., ["A", "B", "C", "A"])
36
+ /// The cycle of dependencies (e.g., `["A", "B", "C", "A"]`)
37
37
  cycle: Vec<String>,
38
38
  },
39
39
 
@@ -134,7 +134,7 @@ impl std::fmt::Debug for FactoryDependency {
134
134
  .field("dependencies", &self.dependencies)
135
135
  .field("cacheable", &self.cacheable)
136
136
  .field("singleton", &self.singleton)
137
- .finish()
137
+ .finish_non_exhaustive()
138
138
  }
139
139
  }
140
140
 
@@ -209,6 +209,7 @@ impl FactoryDependencyBuilder {
209
209
  /// })
210
210
  /// .build();
211
211
  /// ```
212
+ #[must_use]
212
213
  pub fn factory<F>(mut self, factory: F) -> Self
213
214
  where
214
215
  F: Fn(
@@ -245,6 +246,7 @@ impl FactoryDependencyBuilder {
245
246
  /// })
246
247
  /// .build();
247
248
  /// ```
249
+ #[must_use]
248
250
  pub fn depends_on(mut self, dependencies: Vec<String>) -> Self {
249
251
  self.dependencies = dependencies;
250
252
  self
@@ -272,7 +274,8 @@ impl FactoryDependencyBuilder {
272
274
  /// .cacheable(true) // Same ID for all uses in one request
273
275
  /// .build();
274
276
  /// ```
275
- pub fn cacheable(mut self, cacheable: bool) -> Self {
277
+ #[must_use]
278
+ pub const fn cacheable(mut self, cacheable: bool) -> Self {
276
279
  self.cacheable = cacheable;
277
280
  self
278
281
  }
@@ -300,7 +303,8 @@ impl FactoryDependencyBuilder {
300
303
  /// .singleton(true) // Share across all requests
301
304
  /// .build();
302
305
  /// ```
303
- pub fn singleton(mut self, singleton: bool) -> Self {
306
+ #[must_use]
307
+ pub const fn singleton(mut self, singleton: bool) -> Self {
304
308
  self.singleton = singleton;
305
309
  self
306
310
  }
@@ -133,6 +133,7 @@ impl DependencyGraph {
133
133
  /// // Adding c -> [] would not
134
134
  /// assert!(!graph.has_cycle_with("c", &[]));
135
135
  /// ```
136
+ #[must_use]
136
137
  pub fn has_cycle_with(&self, new_key: &str, new_deps: &[String]) -> bool {
137
138
  let mut temp_graph = self.graph.clone();
138
139
  temp_graph.insert(new_key.to_string(), new_deps.to_vec());