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}