ofd-validator 0.5.2__tar.gz → 0.5.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/Cargo.lock +3 -3
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/PKG-INFO +4 -9
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/README.md +3 -8
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/Cargo.toml +1 -1
- ofd_validator-0.5.4/crates/ofd-validator-core/src/schema_cache.rs +288 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-python/Cargo.toml +1 -1
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/pyproject.toml +1 -1
- ofd_validator-0.5.2/crates/ofd-validator-core/src/schema_cache.rs +0 -107
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/Cargo.toml +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/lib.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/orchestrator.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/types.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/util.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/validators/duplicate_link.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/validators/folder_name.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/validators/gtin.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/validators/json_validator.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/validators/logo_validator.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/validators/missing_files.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/validators/mod.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/validators/store_host.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/validators/store_id.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/validators/url_tracker.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/validators/variant_material.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-python/src/lib.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-python/src/orchestrator.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-python/src/types.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-python/src/util.rs +0 -0
- {ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-python/src/validators.rs +0 -0
|
@@ -805,7 +805,7 @@ dependencies = [
|
|
|
805
805
|
|
|
806
806
|
[[package]]
|
|
807
807
|
name = "ofd-validator-core"
|
|
808
|
-
version = "0.5.
|
|
808
|
+
version = "0.5.4"
|
|
809
809
|
dependencies = [
|
|
810
810
|
"image",
|
|
811
811
|
"jsonschema",
|
|
@@ -819,7 +819,7 @@ dependencies = [
|
|
|
819
819
|
|
|
820
820
|
[[package]]
|
|
821
821
|
name = "ofd-validator-js"
|
|
822
|
-
version = "0.5.
|
|
822
|
+
version = "0.5.4"
|
|
823
823
|
dependencies = [
|
|
824
824
|
"napi",
|
|
825
825
|
"napi-build",
|
|
@@ -832,7 +832,7 @@ dependencies = [
|
|
|
832
832
|
|
|
833
833
|
[[package]]
|
|
834
834
|
name = "ofd-validator-python"
|
|
835
|
-
version = "0.5.
|
|
835
|
+
version = "0.5.4"
|
|
836
836
|
dependencies = [
|
|
837
837
|
"ofd-validator-core",
|
|
838
838
|
"pyo3",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ofd-validator
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
4
4
|
Classifier: Development Status :: 4 - Beta
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: License :: OSI Approved :: MIT License
|
|
@@ -51,19 +51,14 @@ Requires Python 3.10+. No additional Python dependencies needed (self-contained
|
|
|
51
51
|
|
|
52
52
|
### Node.js
|
|
53
53
|
|
|
54
|
-
Published to [
|
|
55
|
-
|
|
56
|
-
```
|
|
57
|
-
@openfilamentcollective:registry=https://npm.pkg.github.com
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
Then install:
|
|
54
|
+
Published to [npm](https://www.npmjs.com/package/@openfilamentcollective/ofd-validator). Install it like any public package — the matching prebuilt binary is pulled in automatically, **no Rust toolchain required**:
|
|
61
55
|
|
|
62
56
|
```bash
|
|
63
57
|
npm install @openfilamentcollective/ofd-validator
|
|
64
58
|
```
|
|
65
59
|
|
|
66
|
-
Prebuilt binaries included for Linux (x64, arm64), macOS (x64, arm64), and Windows (x64).
|
|
60
|
+
Prebuilt binaries included for Linux (x64, arm64 — glibc), macOS (x64, arm64), and Windows (x64).
|
|
61
|
+
(No prebuilt for musl/Alpine yet — those would need to build from source.)
|
|
67
62
|
|
|
68
63
|
## Python Usage
|
|
69
64
|
|
|
@@ -24,19 +24,14 @@ Requires Python 3.10+. No additional Python dependencies needed (self-contained
|
|
|
24
24
|
|
|
25
25
|
### Node.js
|
|
26
26
|
|
|
27
|
-
Published to [
|
|
28
|
-
|
|
29
|
-
```
|
|
30
|
-
@openfilamentcollective:registry=https://npm.pkg.github.com
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
Then install:
|
|
27
|
+
Published to [npm](https://www.npmjs.com/package/@openfilamentcollective/ofd-validator). Install it like any public package — the matching prebuilt binary is pulled in automatically, **no Rust toolchain required**:
|
|
34
28
|
|
|
35
29
|
```bash
|
|
36
30
|
npm install @openfilamentcollective/ofd-validator
|
|
37
31
|
```
|
|
38
32
|
|
|
39
|
-
Prebuilt binaries included for Linux (x64, arm64), macOS (x64, arm64), and Windows (x64).
|
|
33
|
+
Prebuilt binaries included for Linux (x64, arm64 — glibc), macOS (x64, arm64), and Windows (x64).
|
|
34
|
+
(No prebuilt for musl/Alpine yet — those would need to build from source.)
|
|
40
35
|
|
|
41
36
|
## Python Usage
|
|
42
37
|
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
use serde_json::Value;
|
|
2
|
+
use std::collections::HashMap;
|
|
3
|
+
use std::sync::Arc;
|
|
4
|
+
|
|
5
|
+
/// Maps schema names (e.g. "brand", "material") to their filenames.
|
|
6
|
+
pub const SCHEMA_FILES: &[(&str, &str)] = &[
|
|
7
|
+
("store", "store_schema.json"),
|
|
8
|
+
("brand", "brand_schema.json"),
|
|
9
|
+
("material", "material_schema.json"),
|
|
10
|
+
("material_types", "material_types_schema.json"),
|
|
11
|
+
("filament", "filament_schema.json"),
|
|
12
|
+
("variant", "variant_schema.json"),
|
|
13
|
+
("sizes", "sizes_schema.json"),
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
/// Schema name for a `*_schema.json` filename, e.g. "sizes_schema.json" -> "sizes".
|
|
17
|
+
fn name_from_filename(filename: &str) -> Option<&str> {
|
|
18
|
+
filename.strip_suffix("_schema.json").filter(|n| is_schema_name(n))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/// Schema names are plain identifiers (e.g. "material_types"), never paths or URIs.
|
|
22
|
+
fn is_schema_name(name: &str) -> bool {
|
|
23
|
+
!name.is_empty() && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Filename for a schema name, e.g. "sizes" -> "sizes_schema.json".
|
|
27
|
+
fn filename_for(name: &str) -> String {
|
|
28
|
+
SCHEMA_FILES
|
|
29
|
+
.iter()
|
|
30
|
+
.find(|(n, _)| *n == name)
|
|
31
|
+
.map(|(_, f)| f.to_string())
|
|
32
|
+
.unwrap_or_else(|| format!("{}_schema.json", name))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Register a schema under every key a `$ref` might use to reach it.
|
|
36
|
+
/// A `$id` never replaces an already-registered key, so callers must register
|
|
37
|
+
/// known schemas before extra ones.
|
|
38
|
+
fn register_uris(
|
|
39
|
+
schemas_by_uri: &mut HashMap<String, Arc<Value>>,
|
|
40
|
+
filename: &str,
|
|
41
|
+
schema: &Arc<Value>,
|
|
42
|
+
) {
|
|
43
|
+
schemas_by_uri.insert(format!("./{}", filename), Arc::clone(schema));
|
|
44
|
+
schemas_by_uri.insert(filename.to_string(), Arc::clone(schema));
|
|
45
|
+
if let Some(id) = schema.get("$id").and_then(|v| v.as_str()) {
|
|
46
|
+
schemas_by_uri
|
|
47
|
+
.entry(id.to_string())
|
|
48
|
+
.or_insert_with(|| Arc::clone(schema));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
#[derive(Clone)]
|
|
53
|
+
pub struct SchemaCache {
|
|
54
|
+
/// Schemas indexed by name (e.g. "brand", "material")
|
|
55
|
+
schemas_by_name: HashMap<String, Arc<Value>>,
|
|
56
|
+
/// All schemas indexed by various URI keys for $ref resolution
|
|
57
|
+
schemas_by_uri: HashMap<String, Arc<Value>>,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
impl SchemaCache {
|
|
61
|
+
/// Create a schema cache from a directory on the filesystem.
|
|
62
|
+
#[cfg(feature = "filesystem")]
|
|
63
|
+
pub fn from_directory(schemas_dir: &std::path::Path) -> Self {
|
|
64
|
+
let mut schemas_by_name = HashMap::new();
|
|
65
|
+
let mut schemas_by_uri = HashMap::new();
|
|
66
|
+
|
|
67
|
+
// Known schemas first, then any other `*_schema.json` in the directory so that a
|
|
68
|
+
// new cross-file `$ref` (e.g. "./sustainability_schema.json") resolves without a release.
|
|
69
|
+
let mut files: Vec<(String, String)> = SCHEMA_FILES
|
|
70
|
+
.iter()
|
|
71
|
+
.map(|(n, f)| (n.to_string(), f.to_string()))
|
|
72
|
+
.collect();
|
|
73
|
+
match std::fs::read_dir(schemas_dir) {
|
|
74
|
+
Ok(entries) => {
|
|
75
|
+
let mut extra: Vec<(String, String)> = entries
|
|
76
|
+
.filter_map(|e| e.ok())
|
|
77
|
+
.filter_map(|e| e.file_name().into_string().ok())
|
|
78
|
+
.filter(|f| !files.iter().any(|(_, known)| known == f))
|
|
79
|
+
.filter_map(|f| name_from_filename(&f).map(|n| (n.to_string(), f.clone())))
|
|
80
|
+
.collect();
|
|
81
|
+
extra.sort();
|
|
82
|
+
files.extend(extra);
|
|
83
|
+
}
|
|
84
|
+
Err(e) => eprintln!(
|
|
85
|
+
"Warning: failed to read schema directory {}: {}",
|
|
86
|
+
schemas_dir.display(),
|
|
87
|
+
e
|
|
88
|
+
),
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
for (name, filename) in &files {
|
|
92
|
+
let path = schemas_dir.join(filename);
|
|
93
|
+
let content = match std::fs::read_to_string(&path) {
|
|
94
|
+
Ok(c) => c,
|
|
95
|
+
// A missing known schema is reported when something validates against it.
|
|
96
|
+
Err(e) if e.kind() == std::io::ErrorKind::NotFound => continue,
|
|
97
|
+
Err(e) => {
|
|
98
|
+
eprintln!("Warning: failed to read schema {}: {}", path.display(), e);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
match serde_json::from_str::<Value>(&content) {
|
|
103
|
+
Ok(schema) => {
|
|
104
|
+
let schema = Arc::new(schema);
|
|
105
|
+
|
|
106
|
+
schemas_by_name.insert(name.clone(), Arc::clone(&schema));
|
|
107
|
+
|
|
108
|
+
let relpath = format!("{}/{}", schemas_dir.display(), filename);
|
|
109
|
+
schemas_by_uri.insert(relpath, Arc::clone(&schema));
|
|
110
|
+
register_uris(&mut schemas_by_uri, filename, &schema);
|
|
111
|
+
}
|
|
112
|
+
Err(e) => eprintln!("Warning: invalid JSON in schema {}: {}", path.display(), e),
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
Self {
|
|
117
|
+
schemas_by_name,
|
|
118
|
+
schemas_by_uri,
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Create a schema cache from pre-loaded schema values.
|
|
123
|
+
/// Keys in the map should be schema names (e.g. "brand", "material"); a name outside
|
|
124
|
+
/// `SCHEMA_FILES` is registered as `<name>_schema.json` for `$ref` resolution.
|
|
125
|
+
/// Keys that are not plain names (paths, URIs) are ignored.
|
|
126
|
+
pub fn from_map(schemas: HashMap<String, Value>) -> Self {
|
|
127
|
+
let mut schemas_by_name = HashMap::new();
|
|
128
|
+
let mut schemas_by_uri = HashMap::new();
|
|
129
|
+
|
|
130
|
+
// Known schemas first, then the rest in a stable order, so `$id`s resolve deterministically.
|
|
131
|
+
let mut names: Vec<&String> = schemas.keys().filter(|n| is_schema_name(n)).collect();
|
|
132
|
+
names.sort_by_key(|n| {
|
|
133
|
+
let known = SCHEMA_FILES.iter().position(|(k, _)| k == n);
|
|
134
|
+
(known.unwrap_or(usize::MAX), n.as_str())
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
for name in names {
|
|
138
|
+
let filename = filename_for(name);
|
|
139
|
+
let schema = Arc::new(schemas[name].clone());
|
|
140
|
+
|
|
141
|
+
schemas_by_name.insert(name.clone(), Arc::clone(&schema));
|
|
142
|
+
register_uris(&mut schemas_by_uri, &filename, &schema);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
Self {
|
|
146
|
+
schemas_by_name,
|
|
147
|
+
schemas_by_uri,
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
pub fn get(&self, schema_name: &str) -> Option<&Value> {
|
|
152
|
+
self.schemas_by_name.get(schema_name).map(|v| v.as_ref())
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
pub fn resolve_ref(&self, uri: &str) -> Option<Value> {
|
|
156
|
+
if let Some(schema) = self.schemas_by_uri.get(uri) {
|
|
157
|
+
return Some((**schema).clone());
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
let stripped = uri.strip_prefix("./").unwrap_or(uri);
|
|
161
|
+
if let Some(schema) = self.schemas_by_uri.get(stripped) {
|
|
162
|
+
return Some((**schema).clone());
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for (key, schema) in &self.schemas_by_uri {
|
|
166
|
+
if key.ends_with(stripped) {
|
|
167
|
+
return Some((**schema).clone());
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
None
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#[cfg(test)]
|
|
176
|
+
mod tests {
|
|
177
|
+
use super::*;
|
|
178
|
+
use crate::validators::validate_json;
|
|
179
|
+
use serde_json::json;
|
|
180
|
+
|
|
181
|
+
fn schemas() -> HashMap<String, Value> {
|
|
182
|
+
let mut m = HashMap::new();
|
|
183
|
+
m.insert(
|
|
184
|
+
"filament".to_string(),
|
|
185
|
+
json!({
|
|
186
|
+
"type": "object",
|
|
187
|
+
"properties": {
|
|
188
|
+
"name": {"type": "string"},
|
|
189
|
+
"sustainability": {"$ref": "./sustainability_schema.json"}
|
|
190
|
+
},
|
|
191
|
+
"additionalProperties": false
|
|
192
|
+
}),
|
|
193
|
+
);
|
|
194
|
+
m.insert(
|
|
195
|
+
"sustainability".to_string(),
|
|
196
|
+
json!({
|
|
197
|
+
"$id": "./sustainability_schema.json",
|
|
198
|
+
"type": "object",
|
|
199
|
+
"properties": {"note": {"type": "string"}},
|
|
200
|
+
"additionalProperties": false
|
|
201
|
+
}),
|
|
202
|
+
);
|
|
203
|
+
m
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
#[test]
|
|
207
|
+
fn name_from_filename_strips_suffix() {
|
|
208
|
+
assert_eq!(name_from_filename("sizes_schema.json"), Some("sizes"));
|
|
209
|
+
assert_eq!(name_from_filename("_schema.json"), None);
|
|
210
|
+
assert_eq!(name_from_filename("trait_rules.json"), None);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#[test]
|
|
214
|
+
fn from_map_resolves_cross_file_ref() {
|
|
215
|
+
let cache = SchemaCache::from_map(schemas());
|
|
216
|
+
let ok = validate_json(&json!({"name": "x", "sustainability": {"note": "y"}}), "filament", &cache, None);
|
|
217
|
+
assert!(ok.errors.is_empty(), "{:?}", ok.errors);
|
|
218
|
+
|
|
219
|
+
let bad = validate_json(&json!({"name": "x", "sustainability": {"bogus": 1}}), "filament", &cache, None);
|
|
220
|
+
assert!(!bad.errors.is_empty());
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
#[test]
|
|
224
|
+
fn from_map_ignores_non_name_keys() {
|
|
225
|
+
let mut m = schemas();
|
|
226
|
+
m.insert("./sustainability_schema.json".to_string(), json!({"type": "string"}));
|
|
227
|
+
let cache = SchemaCache::from_map(m);
|
|
228
|
+
assert!(cache.get("./sustainability_schema.json").is_none());
|
|
229
|
+
let ok = validate_json(&json!({"sustainability": {"note": "y"}}), "filament", &cache, None);
|
|
230
|
+
assert!(ok.errors.is_empty(), "{:?}", ok.errors);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
#[test]
|
|
234
|
+
fn extra_schema_id_does_not_shadow_known_schema() {
|
|
235
|
+
let mut m = schemas();
|
|
236
|
+
m.insert("material_types".to_string(), json!({"$id": "./material_types_schema.json", "type": "string"}));
|
|
237
|
+
// A stray copy claiming the same `$id` must not replace the real schema.
|
|
238
|
+
m.insert("aaa_copy".to_string(), json!({"$id": "./material_types_schema.json", "type": "number"}));
|
|
239
|
+
let cache = SchemaCache::from_map(m);
|
|
240
|
+
assert_eq!(
|
|
241
|
+
cache.resolve_ref("./material_types_schema.json"),
|
|
242
|
+
Some(json!({"$id": "./material_types_schema.json", "type": "string"}))
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/// Temp directory removed on drop, so a failed assertion doesn't leave files behind.
|
|
247
|
+
#[cfg(feature = "filesystem")]
|
|
248
|
+
struct TempDir(std::path::PathBuf);
|
|
249
|
+
|
|
250
|
+
#[cfg(feature = "filesystem")]
|
|
251
|
+
impl TempDir {
|
|
252
|
+
fn new(label: &str) -> Self {
|
|
253
|
+
let dir = std::env::temp_dir().join(format!("ofd-schema-cache-{}-{}", label, std::process::id()));
|
|
254
|
+
std::fs::remove_dir_all(&dir).ok();
|
|
255
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
256
|
+
Self(dir)
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
#[cfg(feature = "filesystem")]
|
|
261
|
+
impl Drop for TempDir {
|
|
262
|
+
fn drop(&mut self) {
|
|
263
|
+
std::fs::remove_dir_all(&self.0).ok();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
#[cfg(feature = "filesystem")]
|
|
268
|
+
#[test]
|
|
269
|
+
fn from_directory_loads_unlisted_schema_files() {
|
|
270
|
+
let dir = TempDir::new("unlisted");
|
|
271
|
+
// sustainability_schema.json is not in SCHEMA_FILES, so this exercises the directory scan.
|
|
272
|
+
for (name, schema) in schemas() {
|
|
273
|
+
std::fs::write(dir.0.join(format!("{}_schema.json", name)), schema.to_string()).unwrap();
|
|
274
|
+
}
|
|
275
|
+
std::fs::write(dir.0.join("extra_schema.json"), json!({"type": "string"}).to_string()).unwrap();
|
|
276
|
+
// A malformed extra schema is skipped (with a warning) rather than breaking the cache.
|
|
277
|
+
std::fs::write(dir.0.join("broken_schema.json"), "{").unwrap();
|
|
278
|
+
|
|
279
|
+
let cache = SchemaCache::from_directory(&dir.0);
|
|
280
|
+
assert!(cache.get("extra").is_some());
|
|
281
|
+
assert!(cache.get("sustainability").is_some());
|
|
282
|
+
assert!(cache.get("broken").is_none());
|
|
283
|
+
let bad = validate_json(&json!({"sustainability": {"bogus": 1}}), "filament", &cache, None);
|
|
284
|
+
assert!(!bad.errors.is_empty());
|
|
285
|
+
let ok = validate_json(&json!({"sustainability": {"note": "y"}}), "filament", &cache, None);
|
|
286
|
+
assert!(ok.errors.is_empty(), "{:?}", ok.errors);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
use serde_json::Value;
|
|
2
|
-
use std::collections::HashMap;
|
|
3
|
-
use std::sync::Arc;
|
|
4
|
-
|
|
5
|
-
/// Maps schema names (e.g. "brand", "material") to their filenames.
|
|
6
|
-
pub const SCHEMA_FILES: &[(&str, &str)] = &[
|
|
7
|
-
("store", "store_schema.json"),
|
|
8
|
-
("brand", "brand_schema.json"),
|
|
9
|
-
("material", "material_schema.json"),
|
|
10
|
-
("material_types", "material_types_schema.json"),
|
|
11
|
-
("filament", "filament_schema.json"),
|
|
12
|
-
("variant", "variant_schema.json"),
|
|
13
|
-
("sizes", "sizes_schema.json"),
|
|
14
|
-
];
|
|
15
|
-
|
|
16
|
-
#[derive(Clone)]
|
|
17
|
-
pub struct SchemaCache {
|
|
18
|
-
/// Schemas indexed by name (e.g. "brand", "material")
|
|
19
|
-
schemas_by_name: HashMap<String, Arc<Value>>,
|
|
20
|
-
/// All schemas indexed by various URI keys for $ref resolution
|
|
21
|
-
schemas_by_uri: HashMap<String, Arc<Value>>,
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
impl SchemaCache {
|
|
25
|
-
/// Create a schema cache from a directory on the filesystem.
|
|
26
|
-
#[cfg(feature = "filesystem")]
|
|
27
|
-
pub fn from_directory(schemas_dir: &std::path::Path) -> Self {
|
|
28
|
-
use crate::util::load_json;
|
|
29
|
-
|
|
30
|
-
let mut schemas_by_name = HashMap::new();
|
|
31
|
-
let mut schemas_by_uri = HashMap::new();
|
|
32
|
-
|
|
33
|
-
for (name, filename) in SCHEMA_FILES {
|
|
34
|
-
let path = schemas_dir.join(filename);
|
|
35
|
-
if let Some(schema) = load_json(&path) {
|
|
36
|
-
let schema = Arc::new(schema);
|
|
37
|
-
|
|
38
|
-
schemas_by_name.insert(name.to_string(), Arc::clone(&schema));
|
|
39
|
-
|
|
40
|
-
let relpath = format!("{}/{}", schemas_dir.display(), filename);
|
|
41
|
-
schemas_by_uri.insert(relpath, Arc::clone(&schema));
|
|
42
|
-
schemas_by_uri.insert(format!("./{}", filename), Arc::clone(&schema));
|
|
43
|
-
schemas_by_uri.insert(filename.to_string(), Arc::clone(&schema));
|
|
44
|
-
|
|
45
|
-
if let Some(id) = schema.get("$id").and_then(|v| v.as_str()) {
|
|
46
|
-
schemas_by_uri.insert(id.to_string(), Arc::clone(&schema));
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
Self {
|
|
52
|
-
schemas_by_name,
|
|
53
|
-
schemas_by_uri,
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/// Create a schema cache from pre-loaded schema values.
|
|
58
|
-
/// Keys in the map should be schema names (e.g. "brand", "material").
|
|
59
|
-
pub fn from_map(schemas: HashMap<String, Value>) -> Self {
|
|
60
|
-
let mut schemas_by_name = HashMap::new();
|
|
61
|
-
let mut schemas_by_uri = HashMap::new();
|
|
62
|
-
|
|
63
|
-
for (name, filename) in SCHEMA_FILES {
|
|
64
|
-
if let Some(schema_value) = schemas.get(*name) {
|
|
65
|
-
let schema = Arc::new(schema_value.clone());
|
|
66
|
-
|
|
67
|
-
schemas_by_name.insert(name.to_string(), Arc::clone(&schema));
|
|
68
|
-
|
|
69
|
-
// Register by filename variants for $ref resolution
|
|
70
|
-
schemas_by_uri.insert(format!("./{}", filename), Arc::clone(&schema));
|
|
71
|
-
schemas_by_uri.insert(filename.to_string(), Arc::clone(&schema));
|
|
72
|
-
|
|
73
|
-
if let Some(id) = schema.get("$id").and_then(|v| v.as_str()) {
|
|
74
|
-
schemas_by_uri.insert(id.to_string(), Arc::clone(&schema));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
Self {
|
|
80
|
-
schemas_by_name,
|
|
81
|
-
schemas_by_uri,
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
pub fn get(&self, schema_name: &str) -> Option<&Value> {
|
|
86
|
-
self.schemas_by_name.get(schema_name).map(|v| v.as_ref())
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
pub fn resolve_ref(&self, uri: &str) -> Option<Value> {
|
|
90
|
-
if let Some(schema) = self.schemas_by_uri.get(uri) {
|
|
91
|
-
return Some((**schema).clone());
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
let stripped = uri.strip_prefix("./").unwrap_or(uri);
|
|
95
|
-
if let Some(schema) = self.schemas_by_uri.get(stripped) {
|
|
96
|
-
return Some((**schema).clone());
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
for (key, schema) in &self.schemas_by_uri {
|
|
100
|
-
if key.ends_with(stripped) {
|
|
101
|
-
return Some((**schema).clone());
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
None
|
|
106
|
-
}
|
|
107
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/validators/folder_name.rs
RENAMED
|
File without changes
|
{ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/validators/gtin.rs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/validators/store_host.rs
RENAMED
|
File without changes
|
{ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/validators/store_id.rs
RENAMED
|
File without changes
|
{ofd_validator-0.5.2 → ofd_validator-0.5.4}/crates/ofd-validator-core/src/validators/url_tracker.rs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|