ofd-validator 0.4.0__tar.gz → 0.5.1__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.4.0 → ofd_validator-0.5.1}/Cargo.lock +3 -3
- ofd_validator-0.5.1/Cargo.toml +3 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/PKG-INFO +1 -1
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-core/Cargo.toml +1 -1
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-core/src/orchestrator.rs +216 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-python/Cargo.toml +1 -1
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-python/src/lib.rs +3 -1
- ofd_validator-0.5.1/crates/ofd-validator-python/src/orchestrator.rs +206 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/pyproject.toml +1 -1
- ofd_validator-0.4.0/Cargo.toml +0 -3
- ofd_validator-0.4.0/crates/ofd-validator-python/src/orchestrator.rs +0 -139
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/README.md +0 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-core/src/lib.rs +0 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-core/src/schema_cache.rs +0 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-core/src/types.rs +0 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-core/src/util.rs +0 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-core/src/validators/folder_name.rs +0 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-core/src/validators/gtin.rs +0 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-core/src/validators/json_validator.rs +0 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-core/src/validators/logo_validator.rs +0 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-core/src/validators/missing_files.rs +0 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-core/src/validators/mod.rs +0 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-core/src/validators/store_id.rs +0 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-python/src/types.rs +0 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-python/src/util.rs +0 -0
- {ofd_validator-0.4.0 → ofd_validator-0.5.1}/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.
|
|
808
|
+
version = "0.5.1"
|
|
809
809
|
dependencies = [
|
|
810
810
|
"image",
|
|
811
811
|
"jsonschema",
|
|
@@ -818,7 +818,7 @@ dependencies = [
|
|
|
818
818
|
|
|
819
819
|
[[package]]
|
|
820
820
|
name = "ofd-validator-js"
|
|
821
|
-
version = "0.
|
|
821
|
+
version = "0.5.1"
|
|
822
822
|
dependencies = [
|
|
823
823
|
"napi",
|
|
824
824
|
"napi-build",
|
|
@@ -831,7 +831,7 @@ dependencies = [
|
|
|
831
831
|
|
|
832
832
|
[[package]]
|
|
833
833
|
name = "ofd-validator-python"
|
|
834
|
-
version = "0.
|
|
834
|
+
version = "0.5.1"
|
|
835
835
|
dependencies = [
|
|
836
836
|
"ofd-validator-core",
|
|
837
837
|
"pyo3",
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
use std::collections::HashSet;
|
|
2
|
+
use std::path::Path;
|
|
3
|
+
use serde::Deserialize;
|
|
2
4
|
use serde_json::Value;
|
|
3
5
|
use rayon::prelude::*;
|
|
4
6
|
|
|
@@ -26,6 +28,220 @@ pub struct DataSet {
|
|
|
26
28
|
pub schema_cache: SchemaCache,
|
|
27
29
|
}
|
|
28
30
|
|
|
31
|
+
// --- Types for applying pending changes from the WebUI ---
|
|
32
|
+
|
|
33
|
+
#[derive(Deserialize)]
|
|
34
|
+
struct ChangesPayload {
|
|
35
|
+
changes: Vec<ChangeEntry>,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#[derive(Deserialize)]
|
|
39
|
+
struct ChangeEntry {
|
|
40
|
+
entity: EntityRef,
|
|
41
|
+
operation: String,
|
|
42
|
+
data: Option<Value>,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
#[derive(Deserialize)]
|
|
46
|
+
struct EntityRef {
|
|
47
|
+
path: String,
|
|
48
|
+
#[serde(rename = "type")]
|
|
49
|
+
entity_type: String,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Internal fields added by the WebUI that should be stripped before validation.
|
|
53
|
+
const STRIP_FIELDS: &[&str] = &[
|
|
54
|
+
"brandId", "brand_id", "materialType", "filamentDir", "filament_id", "slug",
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/// Info needed to overlay a change onto the DataSet.
|
|
58
|
+
struct EntityFsInfo {
|
|
59
|
+
json_path: String,
|
|
60
|
+
folder_path: String,
|
|
61
|
+
folder_name: String,
|
|
62
|
+
schema_name: String,
|
|
63
|
+
folder_json_key: String,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Map a WebUI entity path to filesystem paths and metadata.
|
|
67
|
+
fn entity_path_to_fs_info(
|
|
68
|
+
entity_path: &str,
|
|
69
|
+
entity_type: &str,
|
|
70
|
+
data_dir: &Path,
|
|
71
|
+
stores_dir: &Path,
|
|
72
|
+
) -> Option<EntityFsInfo> {
|
|
73
|
+
let parts: Vec<&str> = entity_path.split('/').collect();
|
|
74
|
+
|
|
75
|
+
let (json_path, folder_path, folder_name, folder_json_key) = match parts.as_slice() {
|
|
76
|
+
["stores", slug] => (
|
|
77
|
+
stores_dir.join(slug).join("store.json"),
|
|
78
|
+
stores_dir.join(slug),
|
|
79
|
+
slug.to_string(),
|
|
80
|
+
"id".to_string(),
|
|
81
|
+
),
|
|
82
|
+
["brands", slug] => (
|
|
83
|
+
data_dir.join(slug).join("brand.json"),
|
|
84
|
+
data_dir.join(slug),
|
|
85
|
+
slug.to_string(),
|
|
86
|
+
"id".to_string(),
|
|
87
|
+
),
|
|
88
|
+
["brands", slug, "materials", mat_type] => (
|
|
89
|
+
data_dir.join(slug).join(mat_type).join("material.json"),
|
|
90
|
+
data_dir.join(slug).join(mat_type),
|
|
91
|
+
mat_type.to_string(),
|
|
92
|
+
"material".to_string(),
|
|
93
|
+
),
|
|
94
|
+
["brands", slug, "materials", mat_type, "filaments", name] => (
|
|
95
|
+
data_dir.join(slug).join(mat_type).join(name).join("filament.json"),
|
|
96
|
+
data_dir.join(slug).join(mat_type).join(name),
|
|
97
|
+
name.to_string(),
|
|
98
|
+
"id".to_string(),
|
|
99
|
+
),
|
|
100
|
+
["brands", slug, "materials", mat_type, "filaments", name, "variants", variant] => (
|
|
101
|
+
data_dir.join(slug).join(mat_type).join(name).join(variant).join("variant.json"),
|
|
102
|
+
data_dir.join(slug).join(mat_type).join(name).join(variant),
|
|
103
|
+
variant.to_string(),
|
|
104
|
+
"id".to_string(),
|
|
105
|
+
),
|
|
106
|
+
_ => return None,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
Some(EntityFsInfo {
|
|
110
|
+
json_path: json_path.to_string_lossy().to_string(),
|
|
111
|
+
folder_path: folder_path.to_string_lossy().to_string(),
|
|
112
|
+
folder_name,
|
|
113
|
+
schema_name: entity_type.to_string(),
|
|
114
|
+
folder_json_key,
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Strip internal WebUI tracking fields and empty strings from entity data.
|
|
119
|
+
fn clean_change_data(data: &Value) -> Value {
|
|
120
|
+
if let Value::Object(map) = data {
|
|
121
|
+
let mut clean = serde_json::Map::new();
|
|
122
|
+
for (key, value) in map {
|
|
123
|
+
if STRIP_FIELDS.contains(&key.as_str()) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if value == &Value::String(String::new()) {
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
clean.insert(key.clone(), value.clone());
|
|
130
|
+
}
|
|
131
|
+
Value::Object(clean)
|
|
132
|
+
} else {
|
|
133
|
+
data.clone()
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
impl DataSet {
|
|
138
|
+
/// Apply pending changes from the WebUI onto the in-memory dataset.
|
|
139
|
+
///
|
|
140
|
+
/// `changes_json` should be a JSON string with the format:
|
|
141
|
+
/// `{ "changes": [{ "entity": { "path": "...", "type": "..." }, "operation": "create"|"update"|"delete", "data": {...} }] }`
|
|
142
|
+
pub fn apply_changes(&mut self, changes_json: &str, data_dir: &Path, stores_dir: &Path) {
|
|
143
|
+
let payload: ChangesPayload = match serde_json::from_str(changes_json) {
|
|
144
|
+
Ok(p) => p,
|
|
145
|
+
Err(e) => {
|
|
146
|
+
eprintln!("Warning: failed to parse changes JSON: {}", e);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
for change in &payload.changes {
|
|
152
|
+
let info = match entity_path_to_fs_info(
|
|
153
|
+
&change.entity.path,
|
|
154
|
+
&change.entity.entity_type,
|
|
155
|
+
data_dir,
|
|
156
|
+
stores_dir,
|
|
157
|
+
) {
|
|
158
|
+
Some(i) => i,
|
|
159
|
+
None => continue,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
match change.operation.as_str() {
|
|
163
|
+
"create" | "update" => {
|
|
164
|
+
let data = match &change.data {
|
|
165
|
+
Some(d) => clean_change_data(d),
|
|
166
|
+
None => continue,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
// Update or add json_entries
|
|
170
|
+
if let Some(existing) = self.json_entries.iter_mut().find(|(p, _, _)| *p == info.json_path) {
|
|
171
|
+
existing.2 = data.clone();
|
|
172
|
+
} else {
|
|
173
|
+
self.json_entries.push((
|
|
174
|
+
info.json_path.clone(),
|
|
175
|
+
info.schema_name.clone(),
|
|
176
|
+
data.clone(),
|
|
177
|
+
));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Update or add folder_entries
|
|
181
|
+
if let Some(existing) = self.folder_entries.iter_mut().find(|(p, _, _, _)| *p == info.folder_path) {
|
|
182
|
+
existing.2 = data.clone();
|
|
183
|
+
} else {
|
|
184
|
+
self.folder_entries.push((
|
|
185
|
+
info.folder_path.clone(),
|
|
186
|
+
info.folder_name.clone(),
|
|
187
|
+
data.clone(),
|
|
188
|
+
info.folder_json_key.clone(),
|
|
189
|
+
));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// For stores: update valid_store_ids
|
|
193
|
+
if info.schema_name == "store" {
|
|
194
|
+
if let Some(id) = data.get("id").and_then(|v| v.as_str()) {
|
|
195
|
+
self.valid_store_ids.insert(id.to_string());
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// For variants with sizes: update sizes_entries
|
|
200
|
+
if info.schema_name == "variant" {
|
|
201
|
+
if let Some(sizes) = data.get("sizes") {
|
|
202
|
+
let sizes_path = Path::new(&info.folder_path)
|
|
203
|
+
.join("sizes.json")
|
|
204
|
+
.to_string_lossy()
|
|
205
|
+
.to_string();
|
|
206
|
+
if let Some(existing) = self.sizes_entries.iter_mut().find(|(p, _)| *p == sizes_path) {
|
|
207
|
+
existing.1 = sizes.clone();
|
|
208
|
+
} else {
|
|
209
|
+
self.sizes_entries.push((sizes_path, sizes.clone()));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
"delete" => {
|
|
215
|
+
// Remove from json_entries
|
|
216
|
+
self.json_entries.retain(|(p, _, _)| *p != info.json_path);
|
|
217
|
+
// Remove from folder_entries
|
|
218
|
+
self.folder_entries.retain(|(p, _, _, _)| *p != info.folder_path);
|
|
219
|
+
// Remove logo entries for this folder
|
|
220
|
+
self.logo_entries.retain(|(p, _, _, _)| !p.starts_with(&info.folder_path));
|
|
221
|
+
// Remove sizes entries under this folder
|
|
222
|
+
self.sizes_entries.retain(|(p, _)| !p.starts_with(&info.folder_path));
|
|
223
|
+
|
|
224
|
+
// For stores: remove from valid_store_ids
|
|
225
|
+
if info.schema_name == "store" {
|
|
226
|
+
// Extract the ID from the path (last component)
|
|
227
|
+
if let Some(slug) = change.entity.path.split('/').last() {
|
|
228
|
+
self.valid_store_ids.remove(slug);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Also remove any child entries (e.g., deleting a brand removes its materials/filaments/variants)
|
|
233
|
+
let prefix = format!("{}/", info.folder_path);
|
|
234
|
+
self.json_entries.retain(|(p, _, _)| !p.starts_with(&prefix));
|
|
235
|
+
self.folder_entries.retain(|(p, _, _, _)| !p.starts_with(&prefix));
|
|
236
|
+
self.logo_entries.retain(|(p, _, _, _)| !p.starts_with(&prefix));
|
|
237
|
+
self.sizes_entries.retain(|(p, _)| !p.starts_with(&prefix));
|
|
238
|
+
}
|
|
239
|
+
_ => {}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
29
245
|
#[cfg(feature = "filesystem")]
|
|
30
246
|
impl DataSet {
|
|
31
247
|
/// Build a DataSet by walking the filesystem.
|
|
@@ -6,7 +6,8 @@ mod util;
|
|
|
6
6
|
mod validators;
|
|
7
7
|
|
|
8
8
|
use orchestrator::{
|
|
9
|
-
validate_all, validate_folder_names, validate_json_files,
|
|
9
|
+
validate_all, validate_all_with_changes, validate_folder_names, validate_json_files,
|
|
10
|
+
validate_logo_files,
|
|
10
11
|
};
|
|
11
12
|
use types::{ValidationError, ValidationLevel, ValidationResult};
|
|
12
13
|
use validators::{
|
|
@@ -22,6 +23,7 @@ fn ofd_validator(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
22
23
|
|
|
23
24
|
// Orchestrated batch validators (internally parallel with rayon)
|
|
24
25
|
m.add_function(wrap_pyfunction!(validate_all, m)?)?;
|
|
26
|
+
m.add_function(wrap_pyfunction!(validate_all_with_changes, m)?)?;
|
|
25
27
|
m.add_function(wrap_pyfunction!(validate_json_files, m)?)?;
|
|
26
28
|
m.add_function(wrap_pyfunction!(validate_logo_files, m)?)?;
|
|
27
29
|
m.add_function(wrap_pyfunction!(validate_folder_names, m)?)?;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
use std::path::PathBuf;
|
|
2
|
+
|
|
3
|
+
use pyo3::prelude::*;
|
|
4
|
+
use rayon::ThreadPoolBuilder;
|
|
5
|
+
|
|
6
|
+
use ofd_validator_core as core;
|
|
7
|
+
|
|
8
|
+
use crate::types::ValidationResult;
|
|
9
|
+
use crate::util::log_step;
|
|
10
|
+
|
|
11
|
+
/// Run the closure on a custom Rayon thread pool when `max_workers` is set,
|
|
12
|
+
/// otherwise use the global pool (default Rayon behaviour).
|
|
13
|
+
fn with_thread_pool<F, R>(max_workers: Option<usize>, f: F) -> R
|
|
14
|
+
where
|
|
15
|
+
F: FnOnce() -> R + Send,
|
|
16
|
+
R: Send,
|
|
17
|
+
{
|
|
18
|
+
match max_workers {
|
|
19
|
+
Some(n) if n > 0 => {
|
|
20
|
+
let pool = ThreadPoolBuilder::new()
|
|
21
|
+
.num_threads(n)
|
|
22
|
+
.build()
|
|
23
|
+
.expect("failed to build Rayon thread pool");
|
|
24
|
+
pool.install(f)
|
|
25
|
+
}
|
|
26
|
+
_ => f(),
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#[pyfunction]
|
|
31
|
+
#[pyo3(signature = (data_dir, stores_dir, schemas_dir=None, max_workers=None))]
|
|
32
|
+
pub fn validate_all(
|
|
33
|
+
py: Python<'_>,
|
|
34
|
+
data_dir: &str,
|
|
35
|
+
stores_dir: &str,
|
|
36
|
+
schemas_dir: Option<&str>,
|
|
37
|
+
max_workers: Option<usize>,
|
|
38
|
+
) -> ValidationResult {
|
|
39
|
+
let data_dir = PathBuf::from(data_dir);
|
|
40
|
+
let stores_dir = PathBuf::from(stores_dir);
|
|
41
|
+
let schemas_dir = PathBuf::from(schemas_dir.unwrap_or("schemas"));
|
|
42
|
+
|
|
43
|
+
py.allow_threads(|| {
|
|
44
|
+
with_thread_pool(max_workers, || {
|
|
45
|
+
log_step("Loading dataset", None);
|
|
46
|
+
let dataset = core::DataSet::from_directories(&data_dir, &stores_dir, &schemas_dir);
|
|
47
|
+
|
|
48
|
+
log_step("Checking required files", None);
|
|
49
|
+
log_step("Validating JSON schemas", Some(dataset.json_entries.len()));
|
|
50
|
+
log_step("Validating logos", Some(dataset.logo_entries.len()));
|
|
51
|
+
log_step("Validating folder names", Some(dataset.folder_entries.len()));
|
|
52
|
+
log_step("Validating store IDs", None);
|
|
53
|
+
log_step("Validating GTIN/EAN codes", None);
|
|
54
|
+
|
|
55
|
+
core::validate_dataset(&dataset).into()
|
|
56
|
+
})
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#[pyfunction]
|
|
61
|
+
#[pyo3(signature = (data_dir, stores_dir, changes_json, schemas_dir=None, max_workers=None))]
|
|
62
|
+
pub fn validate_all_with_changes(
|
|
63
|
+
py: Python<'_>,
|
|
64
|
+
data_dir: &str,
|
|
65
|
+
stores_dir: &str,
|
|
66
|
+
changes_json: &str,
|
|
67
|
+
schemas_dir: Option<&str>,
|
|
68
|
+
max_workers: Option<usize>,
|
|
69
|
+
) -> ValidationResult {
|
|
70
|
+
let data_dir = PathBuf::from(data_dir);
|
|
71
|
+
let stores_dir = PathBuf::from(stores_dir);
|
|
72
|
+
let schemas_dir = PathBuf::from(schemas_dir.unwrap_or("schemas"));
|
|
73
|
+
let changes_json = changes_json.to_string();
|
|
74
|
+
|
|
75
|
+
py.allow_threads(|| {
|
|
76
|
+
with_thread_pool(max_workers, || {
|
|
77
|
+
log_step("Loading dataset", None);
|
|
78
|
+
let mut dataset = core::DataSet::from_directories(&data_dir, &stores_dir, &schemas_dir);
|
|
79
|
+
|
|
80
|
+
log_step("Applying pending changes", None);
|
|
81
|
+
dataset.apply_changes(&changes_json, &data_dir, &stores_dir);
|
|
82
|
+
|
|
83
|
+
log_step("Checking required files", None);
|
|
84
|
+
log_step("Validating JSON schemas", Some(dataset.json_entries.len()));
|
|
85
|
+
log_step("Validating logos", Some(dataset.logo_entries.len()));
|
|
86
|
+
log_step("Validating folder names", Some(dataset.folder_entries.len()));
|
|
87
|
+
log_step("Validating store IDs", None);
|
|
88
|
+
log_step("Validating GTIN/EAN codes", None);
|
|
89
|
+
|
|
90
|
+
core::validate_dataset(&dataset).into()
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#[pyfunction]
|
|
96
|
+
#[pyo3(signature = (data_dir, stores_dir, schemas_dir=None, max_workers=None))]
|
|
97
|
+
pub fn validate_json_files(
|
|
98
|
+
py: Python<'_>,
|
|
99
|
+
data_dir: &str,
|
|
100
|
+
stores_dir: &str,
|
|
101
|
+
schemas_dir: Option<&str>,
|
|
102
|
+
max_workers: Option<usize>,
|
|
103
|
+
) -> ValidationResult {
|
|
104
|
+
let data_dir = PathBuf::from(data_dir);
|
|
105
|
+
let stores_dir = PathBuf::from(stores_dir);
|
|
106
|
+
let schemas_dir = PathBuf::from(schemas_dir.unwrap_or("schemas"));
|
|
107
|
+
|
|
108
|
+
py.allow_threads(|| {
|
|
109
|
+
with_thread_pool(max_workers, || {
|
|
110
|
+
let dataset = core::DataSet::from_directories(&data_dir, &stores_dir, &schemas_dir);
|
|
111
|
+
log_step("Validating JSON schemas", Some(dataset.json_entries.len()));
|
|
112
|
+
|
|
113
|
+
use rayon::prelude::*;
|
|
114
|
+
let results: Vec<core::ValidationResult> = dataset.json_entries
|
|
115
|
+
.par_iter()
|
|
116
|
+
.map(|(path, schema_name, data)| {
|
|
117
|
+
core::validators::validate_json(data, schema_name, &dataset.schema_cache, Some(path))
|
|
118
|
+
})
|
|
119
|
+
.collect();
|
|
120
|
+
|
|
121
|
+
let mut result = core::ValidationResult::default();
|
|
122
|
+
for r in results {
|
|
123
|
+
result.merge_from(&r);
|
|
124
|
+
}
|
|
125
|
+
result.into()
|
|
126
|
+
})
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#[pyfunction]
|
|
131
|
+
#[pyo3(signature = (data_dir, stores_dir, max_workers=None))]
|
|
132
|
+
pub fn validate_logo_files(
|
|
133
|
+
py: Python<'_>,
|
|
134
|
+
data_dir: &str,
|
|
135
|
+
stores_dir: &str,
|
|
136
|
+
max_workers: Option<usize>,
|
|
137
|
+
) -> ValidationResult {
|
|
138
|
+
let data_dir = PathBuf::from(data_dir);
|
|
139
|
+
let stores_dir = PathBuf::from(stores_dir);
|
|
140
|
+
let schemas_dir = PathBuf::from("schemas");
|
|
141
|
+
|
|
142
|
+
py.allow_threads(|| {
|
|
143
|
+
with_thread_pool(max_workers, || {
|
|
144
|
+
let dataset = core::DataSet::from_directories(&data_dir, &stores_dir, &schemas_dir);
|
|
145
|
+
log_step("Validating logos", Some(dataset.logo_entries.len()));
|
|
146
|
+
|
|
147
|
+
use rayon::prelude::*;
|
|
148
|
+
let results: Vec<core::ValidationResult> = dataset.logo_entries
|
|
149
|
+
.par_iter()
|
|
150
|
+
.map(|(path, filename, bytes, logo_name)| {
|
|
151
|
+
if bytes.is_empty() {
|
|
152
|
+
let mut r = core::ValidationResult::default();
|
|
153
|
+
r.add(core::ValidationError::error(
|
|
154
|
+
"Logo",
|
|
155
|
+
"Logo file not found",
|
|
156
|
+
Some(path.clone()),
|
|
157
|
+
));
|
|
158
|
+
r
|
|
159
|
+
} else {
|
|
160
|
+
core::validators::validate_logo(bytes, filename, logo_name.as_deref(), Some(path))
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
.collect();
|
|
164
|
+
|
|
165
|
+
let mut result = core::ValidationResult::default();
|
|
166
|
+
for r in results {
|
|
167
|
+
result.merge_from(&r);
|
|
168
|
+
}
|
|
169
|
+
result.into()
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
#[pyfunction]
|
|
175
|
+
#[pyo3(signature = (data_dir, stores_dir, max_workers=None))]
|
|
176
|
+
pub fn validate_folder_names(
|
|
177
|
+
py: Python<'_>,
|
|
178
|
+
data_dir: &str,
|
|
179
|
+
stores_dir: &str,
|
|
180
|
+
max_workers: Option<usize>,
|
|
181
|
+
) -> ValidationResult {
|
|
182
|
+
let data_dir = PathBuf::from(data_dir);
|
|
183
|
+
let stores_dir = PathBuf::from(stores_dir);
|
|
184
|
+
let schemas_dir = PathBuf::from("schemas");
|
|
185
|
+
|
|
186
|
+
py.allow_threads(|| {
|
|
187
|
+
with_thread_pool(max_workers, || {
|
|
188
|
+
let dataset = core::DataSet::from_directories(&data_dir, &stores_dir, &schemas_dir);
|
|
189
|
+
log_step("Validating folder names", Some(dataset.folder_entries.len()));
|
|
190
|
+
|
|
191
|
+
use rayon::prelude::*;
|
|
192
|
+
let results: Vec<core::ValidationResult> = dataset.folder_entries
|
|
193
|
+
.par_iter()
|
|
194
|
+
.map(|(path, folder_name, json_data, json_key)| {
|
|
195
|
+
core::validators::validate_folder_name(folder_name, json_data, json_key, Some(path))
|
|
196
|
+
})
|
|
197
|
+
.collect();
|
|
198
|
+
|
|
199
|
+
let mut result = core::ValidationResult::default();
|
|
200
|
+
for r in results {
|
|
201
|
+
result.merge_from(&r);
|
|
202
|
+
}
|
|
203
|
+
result.into()
|
|
204
|
+
})
|
|
205
|
+
})
|
|
206
|
+
}
|
ofd_validator-0.4.0/Cargo.toml
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
use std::path::PathBuf;
|
|
2
|
-
|
|
3
|
-
use pyo3::prelude::*;
|
|
4
|
-
|
|
5
|
-
use ofd_validator_core as core;
|
|
6
|
-
|
|
7
|
-
use crate::types::ValidationResult;
|
|
8
|
-
use crate::util::log_step;
|
|
9
|
-
|
|
10
|
-
#[pyfunction]
|
|
11
|
-
#[pyo3(signature = (data_dir, stores_dir, schemas_dir=None))]
|
|
12
|
-
pub fn validate_all(
|
|
13
|
-
py: Python<'_>,
|
|
14
|
-
data_dir: &str,
|
|
15
|
-
stores_dir: &str,
|
|
16
|
-
schemas_dir: Option<&str>,
|
|
17
|
-
) -> ValidationResult {
|
|
18
|
-
let data_dir = PathBuf::from(data_dir);
|
|
19
|
-
let stores_dir = PathBuf::from(stores_dir);
|
|
20
|
-
let schemas_dir = PathBuf::from(schemas_dir.unwrap_or("schemas"));
|
|
21
|
-
|
|
22
|
-
py.allow_threads(|| {
|
|
23
|
-
log_step("Loading dataset", None);
|
|
24
|
-
let dataset = core::DataSet::from_directories(&data_dir, &stores_dir, &schemas_dir);
|
|
25
|
-
|
|
26
|
-
log_step("Checking required files", None);
|
|
27
|
-
log_step("Validating JSON schemas", Some(dataset.json_entries.len()));
|
|
28
|
-
log_step("Validating logos", Some(dataset.logo_entries.len()));
|
|
29
|
-
log_step("Validating folder names", Some(dataset.folder_entries.len()));
|
|
30
|
-
log_step("Validating store IDs", None);
|
|
31
|
-
log_step("Validating GTIN/EAN codes", None);
|
|
32
|
-
|
|
33
|
-
core::validate_dataset(&dataset).into()
|
|
34
|
-
})
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
#[pyfunction]
|
|
38
|
-
#[pyo3(signature = (data_dir, stores_dir, schemas_dir=None))]
|
|
39
|
-
pub fn validate_json_files(
|
|
40
|
-
py: Python<'_>,
|
|
41
|
-
data_dir: &str,
|
|
42
|
-
stores_dir: &str,
|
|
43
|
-
schemas_dir: Option<&str>,
|
|
44
|
-
) -> ValidationResult {
|
|
45
|
-
let data_dir = PathBuf::from(data_dir);
|
|
46
|
-
let stores_dir = PathBuf::from(stores_dir);
|
|
47
|
-
let schemas_dir = PathBuf::from(schemas_dir.unwrap_or("schemas"));
|
|
48
|
-
|
|
49
|
-
py.allow_threads(|| {
|
|
50
|
-
let dataset = core::DataSet::from_directories(&data_dir, &stores_dir, &schemas_dir);
|
|
51
|
-
log_step("Validating JSON schemas", Some(dataset.json_entries.len()));
|
|
52
|
-
|
|
53
|
-
use rayon::prelude::*;
|
|
54
|
-
let results: Vec<core::ValidationResult> = dataset.json_entries
|
|
55
|
-
.par_iter()
|
|
56
|
-
.map(|(path, schema_name, data)| {
|
|
57
|
-
core::validators::validate_json(data, schema_name, &dataset.schema_cache, Some(path))
|
|
58
|
-
})
|
|
59
|
-
.collect();
|
|
60
|
-
|
|
61
|
-
let mut result = core::ValidationResult::default();
|
|
62
|
-
for r in results {
|
|
63
|
-
result.merge_from(&r);
|
|
64
|
-
}
|
|
65
|
-
result.into()
|
|
66
|
-
})
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
#[pyfunction]
|
|
70
|
-
#[pyo3(signature = (data_dir, stores_dir))]
|
|
71
|
-
pub fn validate_logo_files(
|
|
72
|
-
py: Python<'_>,
|
|
73
|
-
data_dir: &str,
|
|
74
|
-
stores_dir: &str,
|
|
75
|
-
) -> ValidationResult {
|
|
76
|
-
let data_dir = PathBuf::from(data_dir);
|
|
77
|
-
let stores_dir = PathBuf::from(stores_dir);
|
|
78
|
-
let schemas_dir = PathBuf::from("schemas");
|
|
79
|
-
|
|
80
|
-
py.allow_threads(|| {
|
|
81
|
-
let dataset = core::DataSet::from_directories(&data_dir, &stores_dir, &schemas_dir);
|
|
82
|
-
log_step("Validating logos", Some(dataset.logo_entries.len()));
|
|
83
|
-
|
|
84
|
-
use rayon::prelude::*;
|
|
85
|
-
let results: Vec<core::ValidationResult> = dataset.logo_entries
|
|
86
|
-
.par_iter()
|
|
87
|
-
.map(|(path, filename, bytes, logo_name)| {
|
|
88
|
-
if bytes.is_empty() {
|
|
89
|
-
let mut r = core::ValidationResult::default();
|
|
90
|
-
r.add(core::ValidationError::error(
|
|
91
|
-
"Logo",
|
|
92
|
-
"Logo file not found",
|
|
93
|
-
Some(path.clone()),
|
|
94
|
-
));
|
|
95
|
-
r
|
|
96
|
-
} else {
|
|
97
|
-
core::validators::validate_logo(bytes, filename, logo_name.as_deref(), Some(path))
|
|
98
|
-
}
|
|
99
|
-
})
|
|
100
|
-
.collect();
|
|
101
|
-
|
|
102
|
-
let mut result = core::ValidationResult::default();
|
|
103
|
-
for r in results {
|
|
104
|
-
result.merge_from(&r);
|
|
105
|
-
}
|
|
106
|
-
result.into()
|
|
107
|
-
})
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
#[pyfunction]
|
|
111
|
-
#[pyo3(signature = (data_dir, stores_dir))]
|
|
112
|
-
pub fn validate_folder_names(
|
|
113
|
-
py: Python<'_>,
|
|
114
|
-
data_dir: &str,
|
|
115
|
-
stores_dir: &str,
|
|
116
|
-
) -> ValidationResult {
|
|
117
|
-
let data_dir = PathBuf::from(data_dir);
|
|
118
|
-
let stores_dir = PathBuf::from(stores_dir);
|
|
119
|
-
let schemas_dir = PathBuf::from("schemas");
|
|
120
|
-
|
|
121
|
-
py.allow_threads(|| {
|
|
122
|
-
let dataset = core::DataSet::from_directories(&data_dir, &stores_dir, &schemas_dir);
|
|
123
|
-
log_step("Validating folder names", Some(dataset.folder_entries.len()));
|
|
124
|
-
|
|
125
|
-
use rayon::prelude::*;
|
|
126
|
-
let results: Vec<core::ValidationResult> = dataset.folder_entries
|
|
127
|
-
.par_iter()
|
|
128
|
-
.map(|(path, folder_name, json_data, json_key)| {
|
|
129
|
-
core::validators::validate_folder_name(folder_name, json_data, json_key, Some(path))
|
|
130
|
-
})
|
|
131
|
-
.collect();
|
|
132
|
-
|
|
133
|
-
let mut result = core::ValidationResult::default();
|
|
134
|
-
for r in results {
|
|
135
|
-
result.merge_from(&r);
|
|
136
|
-
}
|
|
137
|
-
result.into()
|
|
138
|
-
})
|
|
139
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{ofd_validator-0.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-core/src/validators/folder_name.rs
RENAMED
|
File without changes
|
{ofd_validator-0.4.0 → ofd_validator-0.5.1}/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.4.0 → ofd_validator-0.5.1}/crates/ofd-validator-core/src/validators/store_id.rs
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|