Skip to content

Commit 0d3d3c8

Browse files
committed
feat: implement schema system
1 parent 575a64e commit 0d3d3c8

File tree

10 files changed

+737
-419
lines changed

10 files changed

+737
-419
lines changed

.dev/01-schema-system-implementation.md

Lines changed: 171 additions & 419 deletions
Large diffs are not rendered by default.

Cargo.lock

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ chrono = "^0.4.41"
3333
clap = { version = "^4.4", features = ["derive"] }
3434
libc = "^0.2"
3535
regex = "^1.11"
36+
ron = "^0.8"
3637
serde = { version = "^1.0", features = ["derive"] }
3738
tempfile = { version = "^3.0", optional = true }
3839

src/error.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,12 @@ pub enum ZervError {
1717
Io(io::Error),
1818
/// Regex error
1919
Regex(String),
20+
/// Schema parsing error
21+
SchemaParseError(String),
22+
/// Unknown schema name
23+
UnknownSchema(String),
24+
/// Conflicting schema parameters
25+
ConflictingSchemas(String),
2026
}
2127

2228
impl std::fmt::Display for ZervError {
@@ -29,6 +35,9 @@ impl std::fmt::Display for ZervError {
2935
ZervError::CommandFailed(msg) => write!(f, "Command execution failed: {msg}"),
3036
ZervError::Io(err) => write!(f, "IO error: {err}"),
3137
ZervError::Regex(msg) => write!(f, "Regex error: {msg}"),
38+
ZervError::SchemaParseError(msg) => write!(f, "Schema parse error: {msg}"),
39+
ZervError::UnknownSchema(name) => write!(f, "Unknown schema: {name}"),
40+
ZervError::ConflictingSchemas(msg) => write!(f, "Conflicting schemas: {msg}"),
3241
}
3342
}
3443
}
@@ -60,6 +69,9 @@ impl PartialEq for ZervError {
6069
a.kind() == b.kind() && a.to_string() == b.to_string()
6170
}
6271
(ZervError::Regex(a), ZervError::Regex(b)) => a == b,
72+
(ZervError::SchemaParseError(a), ZervError::SchemaParseError(b)) => a == b,
73+
(ZervError::UnknownSchema(a), ZervError::UnknownSchema(b)) => a == b,
74+
(ZervError::ConflictingSchemas(a), ZervError::ConflictingSchemas(b)) => a == b,
6375
_ => false,
6476
}
6577
}

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod cli;
22
pub mod config;
33
pub mod error;
44
pub mod pipeline;
5+
pub mod schema;
56
#[cfg(any(test, feature = "test-utils"))]
67
pub mod test_utils;
78
pub mod vcs;

src/schema/mod.rs

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
mod parser;
2+
mod presets;
3+
4+
pub use parser::{ComponentConfig, SchemaConfig, parse_ron_schema};
5+
pub use presets::{
6+
get_calver_schema, get_preset_schema, get_standard_schema, zerv_calver_tier_1,
7+
zerv_calver_tier_2, zerv_calver_tier_3, zerv_standard_tier_1, zerv_standard_tier_2,
8+
zerv_standard_tier_3,
9+
};
10+
11+
use crate::error::ZervError;
12+
use crate::version::zerv::{Zerv, ZervVars};
13+
14+
pub fn create_zerv_version(
15+
vars: ZervVars,
16+
schema_name: Option<&str>,
17+
schema_ron: Option<&str>,
18+
) -> Result<Zerv, ZervError> {
19+
let schema = match (schema_name, schema_ron) {
20+
// Error if both are provided
21+
(Some(_), Some(_)) => {
22+
return Err(ZervError::ConflictingSchemas(
23+
"Cannot specify both schema_name and schema_ron".to_string(),
24+
));
25+
}
26+
27+
// Custom RON schema
28+
(None, Some(ron_str)) => parse_ron_schema(ron_str)?,
29+
30+
// Built-in schema
31+
(Some(name), None) => {
32+
if let Some(schema) = get_preset_schema(name, &vars) {
33+
schema
34+
} else {
35+
return Err(ZervError::UnknownSchema(name.to_string()));
36+
}
37+
}
38+
39+
// Neither provided - use default
40+
(None, None) => get_preset_schema("zerv-standard", &vars).unwrap(),
41+
};
42+
43+
Ok(Zerv { schema, vars })
44+
}
45+
46+
#[cfg(test)]
47+
mod tests {
48+
use super::*;
49+
use crate::version::zerv::{ZervSchema, ZervVars};
50+
use rstest::rstest;
51+
52+
#[rstest]
53+
#[case(
54+
"zerv-standard",
55+
ZervVars {
56+
major: Some(1), minor: Some(2), patch: Some(3),
57+
dirty: Some(false), distance: Some(0),
58+
..Default::default()
59+
},
60+
zerv_standard_tier_1()
61+
)]
62+
#[case(
63+
"zerv-standard",
64+
ZervVars {
65+
major: Some(1), minor: Some(2), patch: Some(3),
66+
dirty: Some(false), distance: Some(5), post: Some(5),
67+
current_branch: Some("main".to_string()), current_commit_hash: Some("abc123".to_string()),
68+
..Default::default()
69+
},
70+
zerv_standard_tier_2()
71+
)]
72+
#[case(
73+
"zerv-standard",
74+
ZervVars {
75+
major: Some(1), minor: Some(2), patch: Some(3),
76+
dirty: Some(true), dev: Some(1234567890),
77+
current_branch: Some("feature".to_string()), current_commit_hash: Some("def456".to_string()),
78+
..Default::default()
79+
},
80+
zerv_standard_tier_3()
81+
)]
82+
#[case(
83+
"zerv-calver",
84+
ZervVars {
85+
patch: Some(1), dirty: Some(false), distance: Some(0),
86+
tag_timestamp: Some(1710547200),
87+
..Default::default()
88+
},
89+
zerv_calver_tier_1()
90+
)]
91+
#[case(
92+
"zerv-calver",
93+
ZervVars {
94+
patch: Some(1), dirty: Some(false), distance: Some(5), post: Some(5),
95+
current_branch: Some("main".to_string()), current_commit_hash: Some("abc123".to_string()),
96+
tag_timestamp: Some(1710547200),
97+
..Default::default()
98+
},
99+
zerv_calver_tier_2()
100+
)]
101+
#[case(
102+
"zerv-calver",
103+
ZervVars {
104+
patch: Some(1), dirty: Some(true), dev: Some(1234567890),
105+
current_branch: Some("feature".to_string()), current_commit_hash: Some("def456".to_string()),
106+
tag_timestamp: Some(1710547200),
107+
..Default::default()
108+
},
109+
zerv_calver_tier_3()
110+
)]
111+
fn test_preset_schemas(
112+
#[case] schema_name: &str,
113+
#[case] vars: ZervVars,
114+
#[case] expected_schema: ZervSchema,
115+
) {
116+
let zerv = create_zerv_version(vars, Some(schema_name), None).unwrap();
117+
assert_eq!(zerv.schema, expected_schema);
118+
}
119+
120+
#[test]
121+
fn test_default_schema() {
122+
let vars = ZervVars {
123+
major: Some(1),
124+
minor: Some(2),
125+
patch: Some(3),
126+
dirty: Some(false),
127+
distance: Some(0),
128+
..Default::default()
129+
};
130+
131+
let zerv = create_zerv_version(vars, None, None).unwrap();
132+
assert_eq!(zerv.schema, zerv_standard_tier_1());
133+
}
134+
135+
#[test]
136+
fn test_custom_ron_schema() {
137+
let vars = ZervVars::default();
138+
let ron_schema = r#"
139+
SchemaConfig(
140+
core: [
141+
(type: "VarField", field: "major"),
142+
(type: "VarField", field: "minor"),
143+
],
144+
extra_core: [],
145+
build: [(type: "String", value: "custom")]
146+
)
147+
"#;
148+
149+
let zerv = create_zerv_version(vars, None, Some(ron_schema)).unwrap();
150+
assert_eq!(zerv.schema.core.len(), 2);
151+
assert_eq!(zerv.schema.build.len(), 1);
152+
}
153+
154+
#[test]
155+
fn test_conflicting_schemas_error() {
156+
let vars = ZervVars::default();
157+
let ron_schema = "SchemaConfig(core: [], extra_core: [], build: [])";
158+
let result = create_zerv_version(vars, Some("zerv-standard"), Some(ron_schema));
159+
assert!(matches!(result, Err(ZervError::ConflictingSchemas(_))));
160+
}
161+
162+
#[test]
163+
fn test_unknown_schema_error() {
164+
let vars = ZervVars::default();
165+
let result = create_zerv_version(vars, Some("unknown"), None);
166+
assert!(matches!(result, Err(ZervError::UnknownSchema(_))));
167+
}
168+
169+
#[test]
170+
fn test_invalid_ron_schema_error() {
171+
let vars = ZervVars::default();
172+
let invalid_ron = "invalid ron syntax";
173+
let result = create_zerv_version(vars, None, Some(invalid_ron));
174+
assert!(matches!(result, Err(ZervError::SchemaParseError(_))));
175+
}
176+
}

0 commit comments

Comments
 (0)