From fc9dab98373895f42e1121f93902303ba83fa208 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Wed, 24 Jul 2024 16:10:52 +0200 Subject: [PATCH] Extract json schema validation to mod json_schema_validator --- dsp-meta/resources/schema-metadata-draft.json | 850 ++++++++++++++++++ .../convert/serde/json_schema_validator.rs | 55 ++ dsp-meta/src/api/convert/serde/mod.rs | 1 + dsp-meta/tests/draft_schema_test.rs | 128 +-- 4 files changed, 949 insertions(+), 85 deletions(-) create mode 100644 dsp-meta/resources/schema-metadata-draft.json create mode 100644 dsp-meta/src/api/convert/serde/json_schema_validator.rs diff --git a/dsp-meta/resources/schema-metadata-draft.json b/dsp-meta/resources/schema-metadata-draft.json new file mode 100644 index 00000000..e0730a1c --- /dev/null +++ b/dsp-meta/resources/schema-metadata-draft.json @@ -0,0 +1,850 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "https://dasch.swiss/schema/schema-metadata.json", + "title": "Metadata Schema", + "description": "Schema definition of DSP metadata", + "type": "object", + "properties": { + "$schema": { + "description": "Schema definition", + "type": "string" + }, + "project": { + "$ref": "#/definitions/project" + }, + "datasets": { + "description": "The datasets of the project", + "type": "array", + "minItems": 0, + "items": { + "type": "object", + "$ref": "#/definitions/dataset" + } + }, + "persons": { + "description": "", + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/person" + } + }, + "organizations": { + "description": "Organizations relevant to the project.", + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/organization" + } + }, + "grants": { + "description": "Grants relevant to the project.", + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/grant" + } + } + }, + "required": [ + "$schema", + "project", + "datasets" + ], + "additionalProperties": false, + "definitions": { + "text": { + "type": "object", + "title": "Text Property (multilingual)", + "description": "object of strings in a particular languages (e.g. `'en': 'some text'`).", + "patternProperties": { + "^[a-z]{2}$": { + "type": "string", + "description": "Pair of a language iso code (e.g. 'en') and a sting in the language defined by the language code." + } + }, + "minProperties": 1, + "additionalProperties": false + }, + "date": { + "type": "string", + "pattern": "^\\d{4}-\\d{2}-\\d{2}$", + "format": "date", + "description": "A date in the format `yyyy-mm-dd`" + }, + "url": { + "type": "object", + "description": "A URL (general URL or reference to an authority file)", + "properties": { + "__type": { + "description": "Internal Object type", + "const": "URL" + }, + "type": { + "type": "string", + "description": "The type of reference. Default is `URL`, any other type should indicate an authority file supported by the DSP.", + "enum": [ + "URL", + "Geonames", + "Pleiades", + "Skos", + "Periodo", + "Chronontology", + "GND", + "VIAF", + "Grid", + "ORCID", + "Creative Commons", + "DOI", + "ARK" + ], + "default": "URL" + }, + "url": { + "description": "The actual URL.", + "type": "string", + "format": "uri" + }, + "text": { + "type": "string", + "description": "A common identifier of where the URL points. Will be used as the display string of the URL." + } + }, + "required": [ + "__type", + "type", + "url" + ], + "additionalProperties": false + }, + "project": { + "description": "A project on the DSP", + "type": "object", + "properties": { + "__id": { + "type": "string", + "description": "Identifier of the project" + }, + "__type": { + "type": "string", + "description": "Type of this object", + "const": "Project" + }, + "__createdAt": { + "type": "string", + "description": "Creation date of this object" + }, + "__createdBy": { + "type": "string", + "description": "ID of the user who created this object" + }, + "__modifiedAt": { + "type": "string", + "description": "Last modification date of this object" + }, + "__modifiedBy": { + "type": "string", + "description": "ID of the user who last modified this object" + }, + "__deletedAt": { + "type": "string", + "description": "Deletion date of this object" + }, + "__deletedBy": { + "type": "string", + "description": "ID of the user who deleted this object" + }, + "shortcode": { + "type": "string", + "description": "The four digit hexadecimal project shortcode, assigned to the project by the DaSCH.", + "pattern": "^[0-9A-F]{4}$" + }, + "name": { + "type": "string", + "description": "The name of the project" + }, + "description": { + "description": "A human readable description of the project.", + "$ref": "#/definitions/text" + }, + "startDate": { + "description": "Start date of the project.", + "$ref": "#/definitions/date" + }, + "teaserText": { + "type": "string", + "description": "Short description of the project for previewing." + }, + "datasets": { + "description": "The datasets of which the project consists.", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "description": "ID of a dataset." + } + }, + "keywords": { + "type": "array", + "description": "Keywords/Tags describing the project.", + "items": { + "$ref": "#/definitions/text" + } + }, + "disciplines": { + "description": "Discipline/field of research of the project.", + "type": "array", + "minItems": 1, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + } + ] + } + }, + "temporalCoverage": { + "description": "Temporal coverage of the project.", + "type": "array", + "minItems": 1, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + } + ] + } + }, + "spatialCoverage": { + "description": "Spatial coverage of the project.", + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/url" + } + }, + "funders": { + "description": "Funders of the project.", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "description": "ID of the funding person or organization." + } + }, + "url": { + "description": "Primary URL of the project. This should be the project landing page or website hosted by the DaSCH.", + "$ref": "#/definitions/url" + }, + "secondaryURL": { + "description": "Optional secondary project URL. This can point to any external project website.", + "$ref": "#/definitions/url" + }, + "dataManagementPlan": { + "$ref": "#/definitions/dataManagementPlan" + }, + "endDate": { + "description": "End date of the project.", + "$ref": "#/definitions/date" + }, + "contactPoint": { + "type": "string", + "description": "ID of the person or organization." + }, + "howToCite": { + "type": "string", + "description": "A string representation of how the project can be cited." + }, + "publications": { + "description": "Publications produced during the lifetime of the project.", + "type": "array", + "items": { + "description": "Citation form of a publication.", + "type": "object", + "$ref": "#/definitions/publication" + } + }, + "grants": { + "description": "Financial grants of the project.", + "type": "array", + "items": { + "description": "Reference to a grant ID.", + "type": "string" + } + }, + "alternativeNames": { + "description": "Alternative names of the project.", + "type": "array", + "items": { + "$ref": "#/definitions/text" + } + } + }, + "required": [ + "__id", + "__type", + "shortcode", + "name", + "startDate", + "teaserText", + "datasets", + "keywords", + "disciplines" + ], + "additionalProperties": false + }, + "publication": { + "type": "object", + "description": "A publication.", + "properties": { + "text": { + "type": "string", + "description": "The citation form of the publication." + }, + "url": { + "description": "The URLs of the publication, preferably PIDs like DOI or ARK.", + "type": "array", + "items": { + "type": "object", + "$ref": "#/definitions/url" + } + } + }, + "required": [ + "text" + ], + "additionalProperties": false + }, + "grant": { + "type": "object", + "description": "A financial grant.", + "properties": { + "__id": { + "type": "string", + "description": "Unique id of the grant." + }, + "__type": { + "type": "string", + "const": "Grant" + }, + "__createdAt": { + "type": "string", + "description": "Creation date of this object" + }, + "__createdBy": { + "type": "string", + "description": "ID of the user who created this object" + }, + "__modifiedAt": { + "type": "string", + "description": "Last modification date of this object" + }, + "__modifiedBy": { + "type": "string", + "description": "ID of the user who last modified this object" + }, + "__deletedAt": { + "type": "string", + "description": "Deletion date of this object" + }, + "__deletedBy": { + "type": "string", + "description": "ID of the user who deleted this object" + }, + "funders": { + "description": "Legal bodies funding the grant.", + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "description": "ID of the funding person or organization." + } + }, + "number": { + "type": "string", + "description": "The number or identifier assigned to the grant by the funding body." + }, + "name": { + "type": "string", + "description": "The name that the type of grant is usually referred to." + }, + "url": { + "$ref": "#/definitions/url", + "description": "The URL of the grant." + } + }, + "required": [ + "__id", + "__type", + "funders" + ], + "additionalProperties": false + }, + "person": { + "type": "object", + "description": "A person relevant to the project.", + "properties": { + "__id": { + "type": "string", + "description": "Unique id of the person." + }, + "__type": { + "type": "string", + "const": "Person" + }, + "__createdAt": { + "type": "string", + "description": "Creation date of this object" + }, + "__createdBy": { + "type": "string", + "description": "ID of the user who created this object" + }, + "__modifiedAt": { + "type": "string", + "description": "Last modification date of this object" + }, + "__modifiedBy": { + "type": "string", + "description": "ID of the user who last modified this object" + }, + "__deletedAt": { + "type": "string", + "description": "Deletion date of this object" + }, + "__deletedBy": { + "type": "string", + "description": "ID of the user who deleted this object" + }, + "jobTitles": { + "type": "array", + "description": "The job titles of the person.", + "minItems": 1, + "items": { + "description": "A job title of the person", + "type": "string" + } + }, + "givenNames": { + "type": "array", + "description": "The given names of the person.", + "minItems": 1, + "items": { + "description": "Given name of the person", + "type": "string" + } + }, + "familyNames": { + "type": "array", + "description": "The family names of the person.", + "minItems": 1, + "items": { + "description": "Family name of the person", + "type": "string" + } + }, + "affiliation": { + "type": "array", + "description": "Organizational affiliation of the person.", + "minItems": 1, + "items": { + "description": "ID of an organization", + "type": "string" + } + }, + "address": { + "$ref": "#/definitions/address", + "description": "The postal address of the person." + }, + "email": { + "description": "primary e-mail address.", + "type": "string", + "format": "email" + }, + "secondaryEmail": { + "description": "primary e-mail address.", + "type": "string", + "format": "email" + }, + "authorityRefs": { + "type": "array", + "items": { + "$ref": "#/definitions/url" + } + } + }, + "required": [ + "__id", + "__type", + "givenNames", + "familyNames" + ], + "additionalProperties": false + }, + "dataset": { + "type": "object", + "description": "A dataset belonging to the project.", + "properties": { + "__id": { + "type": "string", + "description": "Unique id of the dataset." + }, + "__type": { + "type": "string", + "const": "Dataset" + }, + "__createdAt": { + "type": "string", + "description": "Creation date of this object" + }, + "__createdBy": { + "type": "string", + "description": "ID of the user who created this object" + }, + "__modifiedAt": { + "type": "string", + "description": "Last modification date of this object" + }, + "__modifiedBy": { + "type": "string", + "description": "ID of the user who last modified this object" + }, + "__deletedAt": { + "type": "string", + "description": "Deletion date of this object" + }, + "__deletedBy": { + "type": "string", + "description": "ID of the user who deleted this object" + }, + "title": { + "type": "string", + "description": "The title of the dataset." + }, + "accessConditions": { + "type": "string", + "description": "The conditions under which the data of the dataset can be accessed.", + "enum": [ + "open", + "restricted", + "closed" + ] + }, + "howToCite": { + "type": "string", + "description": "A string representation of how the dataset can be cited." + }, + "status": { + "type": "string", + "description": "The status of the dataset.", + "enum": [ + "In planning", + "Ongoing", + "On hold", + "Finished" + ] + }, + "abstracts": { + "type": "array", + "description": "Abstracts describing the dataset.", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/text" + }, + { + "$ref": "#/definitions/url" + } + ] + } + }, + "typeOfData": { + "type": "array", + "description": "The type of data held by the dataset.", + "minItems": 1, + "items": { + "type": "string", + "enum": [ + "XML", + "Text", + "Image", + "Video", + "Audio" + ] + } + }, + "licenses": { + "type": "array", + "description": "Licenses under which the dataset is published.", + "minItems": 1, + "items": { + "description": "A reference to a license", + "$ref": "#/definitions/license" + } + }, + "languages": { + "type": "array", + "description": "Languages present in the dataset.", + "items": { + "$ref": "#/definitions/text" + } + }, + "attributions": { + "type": "array", + "description": "Work on the dataset attributed to an individual persons.", + "minItems": 1, + "items": { + "$ref": "#/definitions/attribution" + } + }, + "alternativeTitles": { + "type": "array", + "description": "Alternative titles of the dataset.", + "items": { + "$ref": "#/definitions/text" + } + }, + "datePublished": { + "description": "Publishing date of the dataset.", + "$ref": "#/definitions/date" + }, + "dateCreated": { + "description": "Creation date of the dataset.", + "$ref": "#/definitions/date" + }, + "dateModified": { + "description": "Last modification date of the dataset.", + "$ref": "#/definitions/date" + }, + "distribution": { + "description": "A downloadable form of the dataset.", + "$ref": "#/definitions/url" + }, + "urls": { + "type": "array", + "items": { + "$ref": "#/definitions/url" + } + }, + "additional": { + "type": "array", + "description": "Additional information about the dataset.", + "items": { + "oneOf": [ + { + "$ref": "#/definitions/url" + }, + { + "$ref": "#/definitions/text" + } + ] + } + } + }, + "required": [ + "__id", + "__type" + ], + "additionalProperties": false + }, + "organization": { + "description": "An organization.", + "type": "object", + "properties": { + "__id": { + "type": "string", + "description": "ID of the organization." + }, + "__type": { + "type": "string", + "const": "Organization" + }, + "__createdAt": { + "type": "string", + "description": "Creation date of this object" + }, + "__createdBy": { + "type": "string", + "description": "ID of the user who created this object" + }, + "__modifiedAt": { + "type": "string", + "description": "Last modification date of this object" + }, + "__modifiedBy": { + "type": "string", + "description": "ID of the user who last modified this object" + }, + "__deletedAt": { + "type": "string", + "description": "Deletion date of this object" + }, + "__deletedBy": { + "type": "string", + "description": "ID of the user who deleted this object" + }, + "name": { + "type": "string", + "description": "Name of the organization." + }, + "url": { + "$ref": "#/definitions/url", + "description": "The URL of the organization's website." + }, + "address": { + "$ref": "#/definitions/address", + "description": "The postal address of the organization." + }, + "email": { + "description": "E-mail address.", + "type": "string", + "format": "email" + }, + "alternativeNames": { + "type": "array", + "description": "Alternative names of the organization.", + "items": { + "$ref": "#/definitions/text" + } + }, + "authorityRefs": { + "type": "array", + "description": "References to external authority file entries describing the organization.", + "items": { + "$ref": "#/definitions/url" + } + } + }, + "required": [ + "__id", + "__type", + "name" + ], + "additionalProperties": false + }, + "address": { + "description": "A postal address.", + "type": "object", + "properties": { + "__type": { + "type": "string", + "const": "Address" + }, + "street": { + "type": "string", + "description": "The street." + }, + "postalCode": { + "type": "string", + "description": "The postal code." + }, + "locality": { + "type": "string", + "description": "The locality/place name." + }, + "country": { + "type": "string", + "description": "The country." + }, + "canton": { + "type": "string", + "description": "region or organizational unit (canton, state, ...)" + }, + "additional": { + "type": "string", + "description": "Additional address information like c/o or post box" + } + }, + "required": [ + "__type", + "street", + "postalCode", + "country" + ], + "additionalProperties": false + }, + "dataManagementPlan": { + "type": "object", + "description": "Data management plan of the project", + "properties": { + "__type": { + "type": "string", + "const": "DataManagementPlan" + }, + "available": { + "type": "boolean" + }, + "url": { + "$ref": "#/definitions/url" + } + }, + "required": [ + "__type" + ], + "additionalProperties": false + }, + "attribution": { + "type": "object", + "description": "Attribution of work to a person or organization, taking in one or more roles.", + "properties": { + "__type": { + "type": "string", + "const": "Attribution" + }, + "agent": { + "type": "string", + "description": "ID of a person/organization." + }, + "roles": { + "type": "array", + "description": "Roles the person/organization had.", + "minItems": 1, + "items": { + "type": "string", + "description": "A role." + } + } + }, + "required": [ + "__type", + "agent", + "roles" + ], + "additionalProperties": false + }, + "license": { + "type": "object", + "description": "License information.", + "properties": { + "__type": { + "type": "string", + "const": "License" + }, + "license": { + "description": "the specific license", + "$ref": "#/definitions/url" + }, + "date": { + "description": "license date", + "$ref": "#/definitions/date" + }, + "details": { + "type": "string", + "description": "additional information on the license (e.g. the scope of the license, if multiple licenses are used)." + } + }, + "required": [ + "__type", + "license", + "date" + ], + "additionalProperties": false + } + } +} diff --git a/dsp-meta/src/api/convert/serde/json_schema_validator.rs b/dsp-meta/src/api/convert/serde/json_schema_validator.rs new file mode 100644 index 00000000..f96b9436 --- /dev/null +++ b/dsp-meta/src/api/convert/serde/json_schema_validator.rs @@ -0,0 +1,55 @@ +use std::collections::HashMap; +use std::fs::File; +use std::path::{Path}; + +use serde_json::Value; +use valico::json_schema::{Scope, ValidationState}; +use valico::json_schema::schema::ScopedSchema; + +use crate::api::convert::serde::json_schema_validator::SchemaVersion::Draft; +use crate::api::convert::serde::json_schema_validator::ValidationError::*; + +static DRAFT_SCHEMA: &str = include_str!("../../../../resources/schema-metadata-draft.json"); + +pub enum SchemaVersion { + Draft +} + +pub type Result = core::result::Result; +#[derive(Debug)] +pub enum ValidationError { + FileNotLoaded(std::io::Error), + SchemaError(valico::json_schema::schema::SchemaError), + NotAJsonFile(serde_json::Error), +} + +pub fn validate_file(path: &Path, schema_version: SchemaVersion) -> Result { + let contents = load_path_as_json(path)?; + let mut scope = Scope::new(); + let schema = load_json_schema(schema_version, &mut scope)?; + Ok(schema.validate(&contents)) +} + +pub fn validate_files(paths: Vec<&Path>, schema_version: SchemaVersion) -> Result> { + let mut scope = Scope::new(); + let schema = load_json_schema(schema_version, &mut scope)?; + let mut results = HashMap::with_capacity(paths.len()); + for path in paths { + let contents = load_path_as_json(path)?; + let state = schema.validate(&contents); + results.insert(path, state); + } + Ok(results) +} + +fn load_path_as_json(path: &Path) -> Result { + let file = File::open(path).map_err(|e| { FileNotLoaded(e) })?; + let value = serde_json::from_reader::(file); + value.map_err(|e| { NotAJsonFile(e) }) +} + +fn load_json_schema(schema_version: SchemaVersion, scope: &mut Scope) -> Result { + let schema_str = match schema_version { Draft => { DRAFT_SCHEMA } }; + let json = serde_json::from_str(schema_str).map_err(|e| NotAJsonFile(e))?; + scope.compile_and_return(json, false).map_err(|e| { SchemaError(e) }) +} \ No newline at end of file diff --git a/dsp-meta/src/api/convert/serde/mod.rs b/dsp-meta/src/api/convert/serde/mod.rs index 18825b6f..6655ed8d 100644 --- a/dsp-meta/src/api/convert/serde/mod.rs +++ b/dsp-meta/src/api/convert/serde/mod.rs @@ -1 +1,2 @@ pub mod draft_model; +pub mod json_schema_validator; diff --git a/dsp-meta/tests/draft_schema_test.rs b/dsp-meta/tests/draft_schema_test.rs index 0bf8a9fb..787a0d37 100644 --- a/dsp-meta/tests/draft_schema_test.rs +++ b/dsp-meta/tests/draft_schema_test.rs @@ -1,11 +1,9 @@ -use std::fs; -use std::fs::File; -use std::path::PathBuf; +use std::{env, fs}; +use std::path::{Path, PathBuf}; use api::convert::serde::draft_model::*; use dsp_meta::api; -use serde_json::Value; -use valico::json_schema; +use dsp_meta::api::convert::serde::json_schema_validator::{SchemaVersion, validate_files}; #[test] fn test_json_and_yaml_serialization_are_equal() { @@ -33,39 +31,26 @@ fn test_json_and_toml_serialization_are_equal() { #[test] fn test_deserialization_data() { - let paths = fs::read_dir("/Users/christian/git/dasch/dsp-meta/data/json") - .expect("Directory not found") - .filter_map(|entry| { - let entry = entry.ok()?; - let path = entry.path(); - if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("json") { - Some(path) - } else { - None - } - }) - .collect::>(); + let paths = collect_data_json_paths(); let mut success: usize = 0; let mut error: usize = 0; for path in paths { let path = path.as_path(); - if path.extension().and_then(|s| s.to_str()) == Some("json") { - println!("Checking {}:", path.to_str().get_or_insert("")); - let contents = - fs::read_to_string(path).expect("Should have been able to read the file"); - let metadata = serde_json::from_str::(&*contents); - match metadata { - Ok(_data) => { - success = success + 1; - println!("SUCCESS\n") // println!("DATA:\n {:?}\n", data), - } - Err(err) => { - error = error + 1; - println!("ERROR:\n {:?}\n", err) - } - }; - } + println!("Checking {}:", path.to_str().get_or_insert("")); + let contents = + fs::read_to_string(path).expect("Should have been able to read the file"); + let metadata = serde_json::from_str::(&*contents); + match metadata { + Ok(_data) => { + success = success + 1; + println!("SUCCESS\n") // println!("DATA:\n {:?}\n", data), + } + Err(err) => { + error = error + 1; + println!("ERROR:\n {:?}\n", err) + } + }; } println!( "Success: {}, Error: {}, Total: {}", @@ -75,59 +60,32 @@ fn test_deserialization_data() { ) } -#[test] -fn test_draft_json_schema() { - verify_all_json_files_in_directory_jsonschema("/Users/christian/git/dasch/dsp-meta/data/json/"); - assert!(true) -} - -fn verify_all_json_files_in_directory_jsonschema(directory: &str) { - let paths = fs::read_dir(directory).unwrap(); - let mut success: usize = 0; - let mut error: usize = 0; - let json_schema: Value = serde_json::from_reader( - File::open("/Users/christian/git/dasch/dsp-meta/data/schema-metadata-draft.json").unwrap(), - ) - .unwrap(); - let mut scope = json_schema::Scope::new(); - let schema = scope.compile_and_return(json_schema, false).unwrap(); - let mut valid: Vec = Vec::new(); - let mut invalid: Vec = Vec::new(); - - for path in paths { - let path = path.unwrap().path(); - if path.extension().and_then(|s| s.to_str()) == Some("json") { - let file = (*path.to_str().get_or_insert("")).to_string(); - println!("Checking {}:", file); - let contents = - fs::read_to_string(&path).expect("Should have been able to read the file"); - let metadata = serde_json::from_str::(&*contents).expect("parsed data as json"); - let result = schema.validate(&metadata); - let filename = - file["/Users/christian/git/dasch/dsp-meta/data/json/".len()..].to_string(); - if result.is_valid() { - success = success + 1; - valid.push(filename); - println!("VALID\n") // println!("DATA:\n {:?}\n", data), +fn collect_data_json_paths() -> Vec { + let mut current_dir = env::current_dir().ok().and_then(|e| e.parent().map(|p| p.to_path_buf())).expect("Project root dir"); + current_dir.push("data"); + current_dir.push("json"); + fs::read_dir(current_dir) + .expect("Failed to read data_dir") + .filter_map(|entry| { + let entry = entry.ok()?; + let path = entry.path(); + if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("json") { + Some(path) } else { - error = error + 1; - invalid.push(filename); - println!("INVALID: {:?}\n", result) // println!("DATA:\n {:?}\n", data), + None } - } - } - println!( - "Success: {}, Error: {}, Total: {}", - success, - error, - success + error - ); - println!(); - - println!("VALID files:\n{}", valid.join("\n")); - println!(); - - println!("INVALID files:\n{}", invalid.join("\n")); + }) + .collect::>() +} - assert!(invalid.is_empty()); +#[test] +fn test_draft_json_schema() { + let path_bufs = collect_data_json_paths(); + let paths: Vec<&Path> = path_bufs.iter().map(|p| p.as_path()).collect(); + let results = validate_files(paths, SchemaVersion::Draft).unwrap(); + for (key, value) in &results { + println!("{:?}: {:?}", key, value); + } + let failure_count = results.values().filter(|v| !v.is_valid()).count(); + assert_eq!(failure_count, 0); }