Skip to content

Commit 3ade9db

Browse files
committed
feat: [#229] add schema generator infrastructure component
1 parent 072ac9e commit 3ade9db

File tree

3 files changed

+167
-0
lines changed

3 files changed

+167
-0
lines changed

src/infrastructure/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@
1515
//! - `external_validators` - E2E validation from outside VMs (HTTP health checks)
1616
//! - `persistence` - Persistence infrastructure (repositories, file locking, storage)
1717
//! - `trace` - Trace file generation for error analysis
18+
//! - `schema` - JSON Schema generation from Rust types
1819
1920
pub mod external_validators;
2021
pub mod persistence;
2122
pub mod remote_actions;
23+
pub mod schema;
2224
pub mod templating;
2325
pub mod trace;

src/infrastructure/schema/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
//! JSON Schema Generation Infrastructure
2+
//!
3+
//! This module provides infrastructure for generating JSON Schemas from Rust types
4+
//! using the Schemars library. It serves as a thin wrapper around the external
5+
//! dependency, isolating it from the application and domain layers.
6+
//!
7+
//! ## Architecture
8+
//!
9+
//! This is an **Infrastructure Layer** component because:
10+
//! - Wraps external dependency (Schemars)
11+
//! - Pure technical mechanism with no business logic
12+
//! - Provides serialization/export functionality
13+
//!
14+
//! See [docs/features/json-schema-generation/specification.md](../../../docs/features/json-schema-generation/specification.md)
15+
//! for architectural rationale.
16+
17+
mod schema_generator;
18+
19+
pub use schema_generator::{SchemaGenerationError, SchemaGenerator};
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
//! Schema Generator
2+
//!
3+
//! Provides JSON Schema generation from Rust types that implement `JsonSchema`.
4+
//! This is a thin wrapper around the Schemars library.
5+
6+
use schemars::{schema_for, JsonSchema};
7+
use thiserror::Error;
8+
9+
/// Errors that can occur during schema generation
10+
#[derive(Debug, Error)]
11+
pub enum SchemaGenerationError {
12+
/// Failed to serialize schema to JSON
13+
#[error("Failed to serialize schema to JSON")]
14+
SerializationFailed {
15+
/// The underlying serialization error
16+
#[source]
17+
source: serde_json::Error,
18+
},
19+
}
20+
21+
impl SchemaGenerationError {
22+
/// Returns actionable help text for resolving this error
23+
///
24+
/// Following the project's tiered help system pattern.
25+
#[must_use]
26+
pub fn help(&self) -> String {
27+
match self {
28+
Self::SerializationFailed { .. } => {
29+
"Schema serialization failed. This is likely a bug in the schema generator.\n\
30+
\n\
31+
What to do:\n\
32+
1. Check if the type has valid JsonSchema derives\n\
33+
2. Report this as a bug if the error persists\n\
34+
3. Include the full error message in your bug report"
35+
.to_string()
36+
}
37+
}
38+
}
39+
}
40+
41+
/// Schema generator for creating JSON Schemas from Rust types
42+
///
43+
/// This is a stateless utility that wraps the Schemars library,
44+
/// providing a clean interface for schema generation.
45+
///
46+
/// # Examples
47+
///
48+
/// ```rust
49+
/// use torrust_tracker_deployer_lib::infrastructure::schema::SchemaGenerator;
50+
/// use torrust_tracker_deployer_lib::application::command_handlers::create::config::EnvironmentCreationConfig;
51+
///
52+
/// let schema_json = SchemaGenerator::generate::<EnvironmentCreationConfig>()?;
53+
/// # Ok::<(), Box<dyn std::error::Error>>(())
54+
/// ```
55+
pub struct SchemaGenerator;
56+
57+
impl SchemaGenerator {
58+
/// Generates a JSON Schema for the given type
59+
///
60+
/// The type must implement `JsonSchema` from the Schemars library.
61+
///
62+
/// # Type Parameters
63+
///
64+
/// * `T` - The type to generate a schema for (must implement `JsonSchema`)
65+
///
66+
/// # Returns
67+
///
68+
/// * `Ok(String)` - The JSON Schema as a pretty-printed JSON string
69+
/// * `Err(SchemaGenerationError)` - If serialization fails
70+
///
71+
/// # Examples
72+
///
73+
/// ```rust
74+
/// use torrust_tracker_deployer_lib::infrastructure::schema::SchemaGenerator;
75+
/// use torrust_tracker_deployer_lib::application::command_handlers::create::config::EnvironmentCreationConfig;
76+
///
77+
/// let schema = SchemaGenerator::generate::<EnvironmentCreationConfig>()?;
78+
/// assert!(schema.contains("\"$schema\""));
79+
/// assert!(schema.contains("\"environment\""));
80+
/// # Ok::<(), Box<dyn std::error::Error>>(())
81+
/// ```
82+
///
83+
/// # Errors
84+
///
85+
/// Returns `SchemaGenerationError::SerializationFailed` if the schema
86+
/// cannot be serialized to JSON (this should be extremely rare).
87+
pub fn generate<T: JsonSchema>() -> Result<String, SchemaGenerationError> {
88+
// Generate schema using Schemars
89+
let schema = schema_for!(T);
90+
91+
// Serialize to pretty-printed JSON
92+
serde_json::to_string_pretty(&schema)
93+
.map_err(|source| SchemaGenerationError::SerializationFailed { source })
94+
}
95+
}
96+
97+
#[cfg(test)]
98+
mod tests {
99+
use super::*;
100+
use schemars::JsonSchema;
101+
use serde::{Deserialize, Serialize};
102+
103+
// Test helper struct
104+
#[derive(Serialize, Deserialize, JsonSchema)]
105+
struct TestConfig {
106+
name: String,
107+
value: i32,
108+
}
109+
110+
#[test]
111+
fn it_should_generate_valid_json_schema_when_given_valid_type() {
112+
let result = SchemaGenerator::generate::<TestConfig>();
113+
assert!(result.is_ok());
114+
115+
let schema = result.unwrap();
116+
assert!(schema.contains("\"$schema\""));
117+
assert!(schema.contains("\"properties\""));
118+
}
119+
120+
#[test]
121+
fn it_should_include_type_properties_in_generated_schema() {
122+
let schema = SchemaGenerator::generate::<TestConfig>().unwrap();
123+
assert!(schema.contains("\"name\""));
124+
assert!(schema.contains("\"value\""));
125+
}
126+
127+
#[test]
128+
fn it_should_generate_pretty_printed_json_output() {
129+
let schema = SchemaGenerator::generate::<TestConfig>().unwrap();
130+
// Pretty-printed JSON has newlines
131+
assert!(schema.contains('\n'));
132+
// And indentation
133+
assert!(schema.contains(" "));
134+
}
135+
136+
#[test]
137+
fn it_should_provide_help_text_for_serialization_error() {
138+
let error = SchemaGenerationError::SerializationFailed {
139+
source: serde_json::Error::io(std::io::Error::other("test")),
140+
};
141+
142+
let help = error.help();
143+
assert!(help.contains("What to do:"));
144+
assert!(help.contains("bug"));
145+
}
146+
}

0 commit comments

Comments
 (0)