Skip to main content

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}