Skip to content

Commit 58c493e

Browse files
authored
Create json.rs
1 parent 7f39fbe commit 58c493e

1 file changed

Lines changed: 109 additions & 0 deletions

File tree

  • crates/signia-plugins/src/builtin/config
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
//! JSON helpers for built-in configuration.
2+
//!
3+
//! This module provides deterministic JSON parsing, validation, and merging
4+
//! utilities for `BuiltinConfig`.
5+
//!
6+
//! Design goals:
7+
//! - Hosts may load config from files, env, flags, etc.
8+
//! - This module only operates on JSON values and structs.
9+
//! - Deterministic behavior suitable for hashing and reproducibility.
10+
11+
#![cfg(feature = "builtin")]
12+
13+
use anyhow::{anyhow, Result};
14+
use serde_json::Value;
15+
16+
use super::BuiltinConfig;
17+
18+
/// Parse a JSON value into `BuiltinConfig`.
19+
///
20+
/// This performs strict type checking via Serde.
21+
pub fn parse_config_json(v: &Value) -> Result<BuiltinConfig> {
22+
let cfg: BuiltinConfig = serde_json::from_value(v.clone())
23+
.map_err(|e| anyhow!("invalid builtin config json: {e}"))?;
24+
Ok(cfg)
25+
}
26+
27+
/// Merge two configs, with `override_cfg` taking precedence.
28+
///
29+
/// Rules:
30+
/// - Scalars and structs in override replace base.
31+
/// - For vectors, override replaces base entirely.
32+
/// - This is deterministic and explicit (no deep magic).
33+
pub fn merge_configs(base: BuiltinConfig, override_cfg: BuiltinConfig) -> BuiltinConfig {
34+
BuiltinConfig {
35+
repo: merge_repo(base.repo, override_cfg.repo),
36+
dataset: merge_dataset(base.dataset, override_cfg.dataset),
37+
workflow: merge_workflow(base.workflow, override_cfg.workflow),
38+
api: merge_api(base.api, override_cfg.api),
39+
}
40+
}
41+
42+
fn merge_repo(base: super::RepoConfig, o: super::RepoConfig) -> super::RepoConfig {
43+
super::RepoConfig {
44+
max_files: o.max_files,
45+
max_total_bytes: o.max_total_bytes,
46+
max_file_bytes: o.max_file_bytes,
47+
include: if o.include.is_empty() { base.include } else { o.include },
48+
exclude: if o.exclude.is_empty() { base.exclude } else { o.exclude },
49+
allow_binary: o.allow_binary,
50+
}
51+
}
52+
53+
fn merge_dataset(base: super::DatasetConfig, o: super::DatasetConfig) -> super::DatasetConfig {
54+
super::DatasetConfig {
55+
max_files: o.max_files,
56+
max_total_bytes: o.max_total_bytes,
57+
enable_merkle: o.enable_merkle,
58+
}
59+
}
60+
61+
fn merge_workflow(base: super::WorkflowConfig, o: super::WorkflowConfig) -> super::WorkflowConfig {
62+
super::WorkflowConfig {
63+
max_nodes: o.max_nodes,
64+
max_edges: o.max_edges,
65+
enable_yaml: o.enable_yaml,
66+
}
67+
}
68+
69+
fn merge_api(base: super::ApiConfig, o: super::ApiConfig) -> super::ApiConfig {
70+
super::ApiConfig {
71+
enabled: o.enabled,
72+
version: if o.version.is_empty() { base.version } else { o.version },
73+
}
74+
}
75+
76+
/// Convert a config to a canonical JSON representation.
77+
///
78+
/// This is useful for hashing or debugging.
79+
pub fn config_to_canonical_json(cfg: &BuiltinConfig) -> Result<Value> {
80+
let v = serde_json::to_value(cfg)?;
81+
let c = signia_core::determinism::canonical_json::canonicalize_json(&v)?;
82+
Ok(c)
83+
}
84+
85+
#[cfg(test)]
86+
mod tests {
87+
use super::*;
88+
89+
#[test]
90+
fn parse_and_merge_config() {
91+
let base = BuiltinConfig::default();
92+
let override_json = serde_json::json!({
93+
"repo": { "max_files": 10 },
94+
"api": { "enabled": false }
95+
});
96+
let override_cfg = parse_config_json(&override_json).unwrap();
97+
let merged = merge_configs(base, override_cfg);
98+
assert_eq!(merged.repo.max_files, 10);
99+
assert!(!merged.api.enabled);
100+
}
101+
102+
#[test]
103+
fn canonical_json_is_stable() {
104+
let cfg = BuiltinConfig::default();
105+
let a = config_to_canonical_json(&cfg).unwrap();
106+
let b = config_to_canonical_json(&cfg).unwrap();
107+
assert_eq!(a, b);
108+
}
109+
}

0 commit comments

Comments
 (0)