Skip to main content

moosicbox_json_utils/
lib.rs

1//! Utilities for converting JSON and database values to Rust types.
2//!
3//! This crate provides traits and error types for converting values from various sources
4//! (JSON, database rows, etc.) into Rust types in a consistent way.
5
6#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
7#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
8#![allow(clippy::multiple_crate_versions)]
9#![allow(clippy::module_name_repetitions)]
10
11use thiserror::Error;
12
13#[cfg(feature = "database")]
14pub mod database;
15
16#[cfg(feature = "rusqlite")]
17pub mod rusqlite;
18
19#[cfg(feature = "serde_json")]
20pub mod serde_json;
21
22#[cfg(feature = "tantivy")]
23pub mod tantivy;
24
25/// Errors that can occur when parsing or converting values.
26#[derive(Debug, Error, PartialEq, Eq)]
27pub enum ParseError {
28    /// Failed to parse a property from the source value.
29    #[error("Failed to parse property: {0:?}")]
30    Parse(String),
31    /// Failed to convert the value to the target type.
32    #[error("Failed to convert to type: {0:?}")]
33    ConvertType(String),
34    /// A required value was missing from the source.
35    #[error("Missing required value: {0:?}")]
36    MissingValue(String),
37}
38
39/// Trait for converting a value to a target type.
40///
41/// This trait is implemented by various source types (database values, JSON values, etc.)
42/// to provide a uniform interface for type conversion.
43pub trait ToValueType<T> {
44    /// Converts this value to the target type.
45    ///
46    /// # Errors
47    ///
48    /// * If the value failed to parse
49    fn to_value_type(self) -> Result<T, ParseError>;
50
51    /// Handles conversion when the value is missing from the source.
52    ///
53    /// The default implementation returns the provided error, but implementations
54    /// can override this to provide default values (e.g., `None` for `Option<T>`).
55    ///
56    /// # Errors
57    ///
58    /// * If the missing value failed to parse
59    fn missing_value(&self, error: ParseError) -> Result<T, ParseError> {
60        Err(error)
61    }
62}
63
64/// Trait for handling missing values during conversion.
65///
66/// This trait is implemented by source types (like database rows) to define behavior
67/// when a requested field is missing.
68pub trait MissingValue<Type> {
69    /// Handles the case when a value is missing from the source.
70    ///
71    /// The default implementation returns the provided error.
72    ///
73    /// # Errors
74    ///
75    /// * If the missing value failed to parse
76    fn missing_value(&self, error: ParseError) -> Result<Type, ParseError> {
77        Err(error)
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test_log::test]
86    fn test_parse_error_display() {
87        let err = ParseError::Parse("test property".to_string());
88        assert_eq!(
89            err.to_string(),
90            "Failed to parse property: \"test property\""
91        );
92
93        let err = ParseError::ConvertType("u64".to_string());
94        assert_eq!(err.to_string(), "Failed to convert to type: \"u64\"");
95
96        let err = ParseError::MissingValue("field_name".to_string());
97        assert_eq!(err.to_string(), "Missing required value: \"field_name\"");
98    }
99
100    #[test_log::test]
101    fn test_parse_error_eq() {
102        assert_eq!(
103            ParseError::Parse("test".to_string()),
104            ParseError::Parse("test".to_string())
105        );
106        assert_ne!(
107            ParseError::Parse("test".to_string()),
108            ParseError::Parse("other".to_string())
109        );
110        assert_ne!(
111            ParseError::Parse("test".to_string()),
112            ParseError::ConvertType("test".to_string())
113        );
114    }
115
116    /// A test type that implements `ToValueType` to verify the default `missing_value` behavior.
117    struct TestValue(i32);
118
119    impl ToValueType<i32> for TestValue {
120        fn to_value_type(self) -> Result<i32, ParseError> {
121            Ok(self.0)
122        }
123        // Uses default missing_value implementation
124    }
125
126    /// A test type that implements `MissingValue` to verify the default behavior.
127    struct TestRow;
128
129    impl MissingValue<i32> for TestRow {
130        // Uses default missing_value implementation
131    }
132
133    #[test_log::test]
134    fn test_to_value_type_default_missing_value_returns_error() {
135        // Test that the default missing_value implementation returns the error
136        let value = TestValue(42);
137        let error = ParseError::MissingValue("test_field".to_string());
138        let result = value.missing_value(error);
139
140        assert!(result.is_err());
141        assert_eq!(
142            result.unwrap_err(),
143            ParseError::MissingValue("test_field".to_string())
144        );
145    }
146
147    #[test_log::test]
148    fn test_missing_value_trait_default_implementation() {
149        // Test that the default MissingValue implementation returns the error
150        let row = TestRow;
151        let error = ParseError::MissingValue("column_name".to_string());
152        let result: Result<i32, ParseError> = row.missing_value(error);
153
154        assert!(result.is_err());
155        assert_eq!(
156            result.unwrap_err(),
157            ParseError::MissingValue("column_name".to_string())
158        );
159    }
160}