Skip to content

Commit 17286ee

Browse files
committed
add json_schema type to the catalyst-types crate, bump version of the catalyst-types
1 parent 11401c9 commit 17286ee

File tree

3 files changed

+144
-1
lines changed

3 files changed

+144
-1
lines changed

rust/catalyst-types/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "catalyst-types"
3-
version = "0.0.10"
3+
version = "0.0.11"
44
edition.workspace = true
55
license.workspace = true
66
authors.workspace = true
@@ -29,6 +29,9 @@ uuid = { version = "1.12.0", features = ["v4", "v7", "serde"] }
2929
chrono = "0.4.39"
3030
tracing = "0.1.41"
3131
strum = { version = "0.27.1", features = ["derive"] }
32+
anyhow = "1.0.95"
33+
jsonschema = "0.28.3"
34+
serde_json = { version = "1.0.134", features = ["raw_value"] }
3235

3336
# Only include fmmap for non-wasm32 targets
3437
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
//! A wrapper around a JSON Schema validator.
2+
3+
use std::ops::Deref;
4+
5+
use anyhow::anyhow;
6+
use jsonschema::{options, Draft, Validator};
7+
use serde_json::Value;
8+
9+
/// Wrapper around a JSON Schema validator.
10+
///
11+
/// Attempts to detect the draft version from the `$schema` field.
12+
/// If not specified, it tries Draft2020-12 first, then falls back to Draft7.
13+
/// Returns an error if schema is invalid for both.
14+
pub struct JsonSchema(Validator);
15+
16+
impl Deref for JsonSchema {
17+
type Target = Validator;
18+
19+
fn deref(&self) -> &Self::Target {
20+
&self.0
21+
}
22+
}
23+
24+
impl TryFrom<&Value> for JsonSchema {
25+
type Error = anyhow::Error;
26+
27+
fn try_from(schema: &Value) -> std::result::Result<Self, Self::Error> {
28+
let draft_version = if let Some(schema) = schema.get("$schema").and_then(|s| s.as_str()) {
29+
if schema.contains("draft-07") {
30+
Some(Draft::Draft7)
31+
} else if schema.contains("2020-12") {
32+
Some(Draft::Draft202012)
33+
} else {
34+
None
35+
}
36+
} else {
37+
None
38+
};
39+
40+
if let Some(draft) = draft_version {
41+
let validator = options()
42+
.with_draft(draft)
43+
.build(schema)
44+
.map_err(|e| anyhow!("Invalid JSON Schema: {e}"))?;
45+
46+
Ok(JsonSchema(validator))
47+
} else {
48+
// if draft not specified or not detectable:
49+
// try draft2020-12
50+
if let Ok(validator) = options().with_draft(Draft::Draft202012).build(schema) {
51+
return Ok(JsonSchema(validator));
52+
}
53+
54+
// fallback to draft7
55+
if let Ok(validator) = options().with_draft(Draft::Draft7).build(schema) {
56+
return Ok(JsonSchema(validator));
57+
}
58+
59+
Err(anyhow!(
60+
"Could not detect draft version and schema is not valid against Draft2020-12 or Draft7"
61+
))
62+
}
63+
}
64+
}
65+
66+
#[cfg(test)]
67+
mod tests {
68+
use serde_json::json;
69+
70+
use super::*;
71+
72+
#[test]
73+
fn valid_draft7_schema() {
74+
let schema = json!({
75+
"$schema": "http://json-schema.org/draft-07/schema#",
76+
"type": "object",
77+
"properties": {
78+
"name": { "type": "string" }
79+
}
80+
});
81+
82+
let result = JsonSchema::try_from(&schema);
83+
assert!(result.is_ok(), "Expected Draft7 schema to be valid");
84+
}
85+
86+
#[test]
87+
fn valid_draft2020_12_schema() {
88+
let schema = json!({
89+
"$schema": "https://json-schema.org/draft/2020-12/schema",
90+
"type": "object",
91+
"properties": {
92+
"age": { "type": "integer" }
93+
}
94+
});
95+
96+
let result = JsonSchema::try_from(&schema);
97+
assert!(result.is_ok(), "Expected Draft2020-12 schema to be valid");
98+
}
99+
100+
#[test]
101+
fn schema_without_draft_should_fallback() {
102+
// Valid in both Draft2020-12 and Draft7
103+
let schema = json!({
104+
"type": "object",
105+
"properties": {
106+
"id": { "type": "number" }
107+
}
108+
});
109+
110+
let result = JsonSchema::try_from(&schema);
111+
assert!(
112+
result.is_ok(),
113+
"Expected schema without $schema to fallback and succeed"
114+
);
115+
}
116+
117+
#[test]
118+
fn invalid_schema_should_error() {
119+
// Invalid schema: "type" is not a valid keyword here
120+
let schema = json!({
121+
"$schema": "http://json-schema.org/draft-07/schema#",
122+
"type": "not-a-valid-type"
123+
});
124+
125+
let result = JsonSchema::try_from(&schema);
126+
assert!(
127+
result.is_err(),
128+
"Expected invalid schema to return an error"
129+
);
130+
}
131+
132+
#[test]
133+
fn empty_object_schema() {
134+
let schema = json!({});
135+
136+
let result = JsonSchema::try_from(&schema);
137+
assert!(result.is_ok());
138+
}
139+
}

rust/catalyst-types/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
pub mod catalyst_id;
44
pub mod cbor_utils;
55
pub mod conversion;
6+
pub mod json_schema;
67
#[cfg(not(target_arch = "wasm32"))]
78
pub mod mmap_file;
89
pub mod problem_report;

0 commit comments

Comments
 (0)