Skip to main content

modelcards/
validate.rs

1//! # Validation
2//! 
3//! The `validate` module provides functions to validate a model card against a schema.
4//! 
5//! ## Functions
6//! 
7//! The module provides the following functions:
8//! 
9//! - `check_against_schema` - Check a model card against a schema.
10//! - `validate_against_schema` - Validate a model card against a schema.
11//! 
12//! ## Errors
13//! 
14//! The functions will return an error if the model card is not valid against the schema.
15//! The [`anyhow`] crate is used for error handling.
16//! 
17
18use std::path::Path;
19use crate::{assets, utils::load_json_file};
20use anyhow::{bail, Result};
21use serde_json::Value;
22use valico::json_schema::scope;
23
24/// Check a model card against a schema.
25/// 
26/// The function takes a path to a model card and a path to a schema and checks the model card against the schema.
27/// 
28/// ## Arguments
29/// 
30/// - `path` - A path to a model card or a directory containing a model card.
31/// - `modelcard` - A path to a schema file.
32/// 
33/// ## Returns
34/// 
35/// The function returns a `Result` with a boolean indicating whether the model card is valid against the schema.
36/// 
37/// ## Errors
38/// 
39/// The function will return an error if the model card is not valid against the schema.
40/// 
41/// ## Example
42/// 
43/// ```rust,no_run
44/// use std::path::Path;
45/// use modelcards::validate::check_against_schema;
46/// 
47/// let schema_path = Path::new("tests/schemas/my_schema.json");
48/// let data_path = Path::new("tests/data/sample.json");
49/// let result = check_against_schema(schema_path, data_path).unwrap();
50/// assert_eq!(result, true);
51/// ```
52/// 
53pub fn check_against_schema(path: &Path, modelcard: &Path) -> Result<bool> {
54
55    if !path.exists() {
56        bail!("Path does not exist: {:?}", path);
57    }
58
59    let schema_file = if path.is_dir() {
60        //TODO: get schema from config
61        path.join("schema/modelcard.schema.json")
62    } else {
63        path.to_path_buf()
64    };
65    let schema_v7 = load_json_file(&schema_file)?;
66    let modelcard = load_json_file(modelcard)?;
67
68    validate_against_schema(modelcard, Some(schema_v7))
69}
70
71/// Validate a model card against a schema.
72/// 
73/// The function takes a model card and a schema and validates the model card against the schema.
74/// 
75/// ## Arguments
76/// 
77/// - `modelcard` - A JSON object representing the model card.
78/// - `schema` - A JSON object representing the schema.
79/// 
80/// ## Returns
81/// 
82/// The function returns a `Result` with a boolean indicating whether the model card is valid against the schema.
83/// 
84/// ## Errors
85/// 
86/// The function will return an error if the model card is not valid against the schema.
87/// 
88/// ## Example
89/// 
90/// ```rust
91/// use serde_json::json;
92/// use modelcards::validate::validate_against_schema;
93/// 
94/// let modelcard = json!({
95///     "name": "Model Name",
96///     "schema_version": "0.0.2"
97/// });
98/// let schema = json!({
99///     "type": "object",
100///     "properties": {
101///         "name": {
102///             "type": "string"
103///         },
104///         "schema_version": {
105///             "type": "string"
106///         }
107///     },
108///     "required": ["name", "schema_version"]
109/// });
110/// let result = validate_against_schema(modelcard, Some(schema)).unwrap();
111/// assert_eq!(result, true);
112/// ```
113/// 
114pub fn validate_against_schema(modelcard: Value, schema: Option<Value>) -> Result<bool> {
115
116    let schema_v7 = match schema {
117        Some(s) => s,
118        None => serde_json::from_str(assets::schema::get_schema())?,
119    };
120
121    let mut scope = scope::Scope::new();
122    //let schema = scope.compile_and_return(schema_v7, true).ok().unwrap();
123    match scope.compile_and_return(schema_v7, true) {
124        Ok(s) => {
125            let vs = s.validate(&modelcard);
126            if !vs.is_valid() {
127                let mut errors = vec![];
128                for e in vs.errors.into_iter() {
129                    errors.push(format!("[{}] {}: {}", e.get_code(), e.get_path(), e.get_title()));
130                    if let Some(detail) = e.get_detail() {
131                        errors.push(format!("    {}", detail));
132                    }
133                    //TODO: add state errors from any_of and one_of
134                }
135                let mut missing = vec![];
136                for e in vs.missing.into_iter() {
137                    missing.push(format!("{}", e));
138                }
139                if !missing.is_empty() {
140                    errors.push(format!("Missing fields:\n - {}", missing.join("\n - ")));
141                }
142                bail!("Validation failed:\n{}", errors.join("\n"));
143            }
144        },
145        Err(e) => {
146            bail!("Could not compile schema: {:?}", e);
147        }
148    }
149
150    Ok(true)
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    use crate::assets::schema;
158    use crate::utils::create_file;
159
160    use std::{
161        env::temp_dir,
162        fs::{create_dir, remove_dir_all},
163        path::{PathBuf, Path}
164    };
165    use anyhow::Result;
166
167    fn get_temp_dir(path: &str, create: bool) -> PathBuf {
168        let mut dir = temp_dir();
169        dir.push(path);
170        if dir.exists() {
171            remove_dir_all(&dir).expect("Could not free test directory");
172        }
173        if create {
174            create_dir(&dir).expect("Could not create test directory");
175        }
176        dir
177    }
178
179    fn populate_modelcards_dir(path: &Path) -> Result<()> {
180        create_file(&path.join("sample.json"), schema::get_sample())?;
181        create_dir(path.join("schema"))?;
182        create_file(&path.join("schema/modelcard.schema.json"), schema::get_schema())?;
183        Ok(())
184    }
185
186    #[test]
187    fn check_valid_against_schema() {
188        let dir = get_temp_dir("test_check_against_schema", true);
189        populate_modelcards_dir(&dir).expect("Could not populate modelcards directory");
190        assert!(check_against_schema(&dir, &dir.join("sample.json")).is_ok());
191    }
192
193    #[test]
194    fn check_valid_against_missing_schema() {
195        let dir = get_temp_dir("test_check_missing_schema", true);
196        populate_modelcards_dir(&dir).expect("Could not populate modelcards directory");
197        //force error with missing schema
198        assert!(!check_against_schema(&dir, &dir.join("sample_2.json")).is_ok());
199    }
200}