Skip to main content

rskit_version/
schema.rs

1//! Schema-version compatibility gating.
2//!
3//! A small,
4//! general-purpose helper for versioned documents (config files, manifests, on-disk formats) that declare a `schema` field
5//! and must reject any version the current code does not understand.
6//! This is distinct from [`crate::semver`] (which parses semantic-version strings):
7//! a schema version is typically a small monotonic integer,
8//! so the gate is generic over any `Copy + Eq + Display` value.
9
10use std::fmt::Display;
11
12use rskit_errors::{AppError, AppResult};
13
14/// Return the configured schema version, or the supported default when absent,
15/// rejecting any value the current code does not support.
16///
17/// `field` names the schema field for error reporting.
18/// A `None` `configured` value defaults to `supported`.
19/// A `configured` value that differs from `supported` is a typed [`AppError::invalid_input`] —
20/// the caller cannot safely interpret a document written for a different schema version.
21///
22/// # Errors
23///
24/// Returns [`AppError::invalid_input`] when `configured` is present and not equal to `supported`.
25///
26/// # Examples
27///
28/// ```
29/// use rskit_version::schema::supported_schema;
30///
31/// // Absent value defaults to the supported version.
32/// assert_eq!(supported_schema("schema", None, 1).unwrap(), 1);
33/// // A matching value is accepted.
34/// assert_eq!(supported_schema("schema", Some(1), 1).unwrap(), 1);
35/// // A mismatching value is rejected.
36/// assert!(supported_schema("schema", Some(2), 1).is_err());
37/// ```
38pub fn supported_schema<T>(field: &str, configured: Option<T>, supported: T) -> AppResult<T>
39where
40    T: Copy + Eq + Display,
41{
42    let schema = configured.unwrap_or(supported);
43    if schema != supported {
44        return Err(AppError::invalid_input(
45            field,
46            format!("unsupported schema {schema}; supported schema is {supported}"),
47        ));
48    }
49    Ok(schema)
50}
51
52#[cfg(test)]
53mod tests {
54    use rskit_errors::ErrorCode;
55
56    use super::supported_schema;
57
58    #[test]
59    fn defaults_absent_value() {
60        assert_eq!(supported_schema("schema", None, 1).unwrap(), 1);
61    }
62
63    #[test]
64    fn accepts_supported_value() {
65        assert_eq!(supported_schema("schema", Some(1), 1).unwrap(), 1);
66    }
67
68    #[test]
69    fn rejects_unsupported_value() {
70        let error = supported_schema("schema", Some(2), 1).unwrap_err();
71
72        assert_eq!(error.code(), ErrorCode::InvalidInput);
73        assert!(error.message().contains("unsupported schema 2"));
74    }
75}