torrust_tracker_deployer_lib/infrastructure/schema/schema_generator.rs
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
6use schemars::{schema_for, JsonSchema};
7use thiserror::Error;
8
9/// Errors that can occur during schema generation
10#[derive(Debug, Error)]
11pub 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
21impl 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/// ```
55pub struct SchemaGenerator;
56
57impl 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)]
98mod 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}