Skip to content

Commit 2f9e70e

Browse files
authored
feat: implement schema system (#62)
- implement schema system
1 parent 985de05 commit 2f9e70e

File tree

12 files changed

+832
-6
lines changed

12 files changed

+832
-6
lines changed

.dev/00-implementation-plan.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@
2020
1. Create `src/schema/` module
2121
2. Implement RON parsing for `ZervFormat`
2222
3. Add `zerv-default` preset with tier-aware logic
23-
4. Unit tests for schema parsing
23+
4. Implement `create_zerv_version` function - Takes `ZervVars` + schema and produces `Zerv` object
24+
5. Unit tests for schema parsing and version creation
2425

2526
**Files**:
2627

27-
- `src/schema/mod.rs` - Schema parsing
28+
- `src/schema/mod.rs` - Schema parsing and `create_zerv_version` function
2829
- `src/schema/presets.rs` - Built-in schemas
2930

3031
### Step 3: CLI Pipeline (1-2 days)
@@ -48,11 +49,14 @@ pub fn run_version_pipeline(args: VersionArgs) -> Result<String> {
4849
// 2. Convert to ZervVars
4950
let vars = vcs_data_to_zerv_vars(vcs_data)?;
5051

51-
// 3. Apply schema and output format
52+
// 3. Create Zerv version object from vars and schema
53+
let zerv = create_zerv_version(vars, &args.schema, args.schema_ron.as_deref())?;
54+
55+
// 4. Apply output format
5256
match args.output_format.as_deref() {
53-
Some("pep440") => Ok(PEP440::from_zerv(&vars)?.to_string()),
54-
Some("semver") => Ok(SemVer::from_zerv(&vars)?.to_string()),
55-
_ => Ok(vars.to_string()),
57+
Some("pep440") => Ok(PEP440::from(zerv).to_string()),
58+
Some("semver") => Ok(SemVer::from(zerv).to_string()),
59+
_ => Ok(zerv.to_string()),
5660
}
5761
}
5862
```
Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
# Schema System Implementation Plan
2+
3+
## ✅ STATUS: COMPLETED
4+
5+
**All implementation steps have been successfully completed. The schema system is fully functional with 33 passing tests.**
6+
7+
## Overview
8+
9+
Implemented a complete schema system for zerv that supports:
10+
11+
1. **RON Schema Parsing**: Parse custom schemas from RON (Rust Object Notation) strings ✅
12+
2. **Preset Schemas**: Built-in schemas for common versioning patterns ✅
13+
3. **Tier-Aware Logic**: Different schema components based on Git state (tagged/distance/dirty) ✅
14+
4. **Core Integration**: Bridge `ZervVars` to `Zerv` objects via `create_zerv_version` function ✅
15+
16+
## Architecture
17+
18+
```
19+
ZervVars → create_zerv_version() → Zerv
20+
21+
Schema Selection Logic:
22+
- schema_name → Preset Schema
23+
- schema_ron → Custom RON Schema
24+
- Neither → Default (zerv-standard)
25+
```
26+
27+
## ✅ COMPLETED IMPLEMENTATION
28+
29+
### 1. RON Schema Parser (`src/schema/parser.rs`)
30+
31+
**Features:**
32+
33+
- `SchemaConfig` struct for RON deserialization
34+
- `ComponentConfig` enum with tagged union support
35+
- Automatic conversion to `ZervSchema` via `From` traits
36+
- Comprehensive error handling with `ZervError::SchemaParseError`
37+
38+
```rust
39+
#[derive(Debug, Deserialize)]
40+
pub struct SchemaConfig {
41+
pub core: Vec<ComponentConfig>,
42+
pub extra_core: Vec<ComponentConfig>,
43+
pub build: Vec<ComponentConfig>,
44+
}
45+
46+
#[derive(Debug, Deserialize)]
47+
#[serde(tag = "type")]
48+
pub enum ComponentConfig {
49+
String { value: String },
50+
Integer { value: u64 },
51+
VarField { field: String },
52+
VarTimestamp { pattern: String },
53+
}
54+
55+
pub fn parse_ron_schema(ron_str: &str) -> Result<ZervSchema, ZervError>
56+
```
57+
58+
### 2. Preset Schemas
59+
60+
#### Standard Schema (`src/schema/presets/standard.rs`)
61+
62+
**State-Based Versioning Tiers:**
63+
64+
- **Tier 1** (Tagged, clean): `major.minor.patch`
65+
- **Tier 2** (Distance, clean): `major.minor.patch.post<distance>+branch.<commit>`
66+
- **Tier 3** (Dirty): `major.minor.patch.dev<timestamp>+branch.<distance>.<commit>`
67+
68+
**Functions:**
69+
70+
- `zerv_standard_tier_1()` → Basic semantic version
71+
- `zerv_standard_tier_2()` → Adds post-release and build metadata
72+
- `zerv_standard_tier_3()` → Adds development identifier
73+
- `get_standard_schema(vars)` → Tier-aware selection
74+
75+
#### CalVer Schema (`src/schema/presets/calver.rs`)
76+
77+
**Calendar-Based Versioning Tiers:**
78+
79+
- **Tier 1** (Tagged, clean): `YYYY.MM.DD.patch`
80+
- **Tier 2** (Distance, clean): `YYYY.MM.DD.patch.post<distance>+branch.<commit>`
81+
- **Tier 3** (Dirty): `YYYY.MM.DD.patch.dev<timestamp>+branch.<distance>.<commit>`
82+
83+
**Functions:**
84+
85+
- `zerv_calver_tier_1()` → Calendar version with patch
86+
- `zerv_calver_tier_2()` → Adds post-release and build metadata
87+
- `zerv_calver_tier_3()` → Adds development identifier
88+
- `get_calver_schema(vars)` → Tier-aware selection
89+
90+
#### Preset Dispatcher (`src/schema/presets/mod.rs`)
91+
92+
```rust
93+
pub fn get_preset_schema(name: &str, vars: &ZervVars) -> Option<ZervSchema> {
94+
match name {
95+
"zerv-standard" => Some(get_standard_schema(vars)),
96+
"zerv-calver" => Some(get_calver_schema(vars)),
97+
_ => None,
98+
}
99+
}
100+
```
101+
102+
### 3. Main Schema Module (`src/schema/mod.rs`)
103+
104+
**Core Function:**
105+
106+
```rust
107+
pub fn create_zerv_version(
108+
vars: ZervVars,
109+
schema_name: Option<&str>,
110+
schema_ron: Option<&str>,
111+
) -> Result<Zerv, ZervError>
112+
```
113+
114+
**Logic:**
115+
116+
- Validates mutually exclusive schema parameters
117+
- Handles custom RON schemas via `parse_ron_schema`
118+
- Resolves preset schemas via `get_preset_schema`
119+
- Defaults to "zerv-standard" when no schema specified
120+
- Returns `Zerv { schema, vars }` object
121+
122+
### 4. Error Handling (`src/error.rs`)
123+
124+
**Added Error Variants:**
125+
126+
```rust
127+
SchemaParseError(String), // RON parsing failures
128+
UnknownSchema(String), // Invalid preset names
129+
ConflictingSchemas(String), // Both schema_name and schema_ron provided
130+
```
131+
132+
**Standards Compliance:**
133+
134+
- Uses `ZervError` for all custom errors
135+
- Implements `Display`, `Error`, and `PartialEq` traits
136+
- Follows project error handling patterns
137+
138+
### 5. Integration (`src/lib.rs`)
139+
140+
```rust
141+
pub mod schema;
142+
```
143+
144+
### 6. Dependencies (`Cargo.toml`)
145+
146+
```toml
147+
[dependencies]
148+
ron = "^0.8" # RON parsing
149+
serde = { version = "^1.0", features = ["derive"] } # Serialization
150+
151+
[dev-dependencies]
152+
rstest = "^0.26.0" # Parameterized testing
153+
```
154+
155+
## ✅ COMPREHENSIVE TEST COVERAGE
156+
157+
**33 Tests Passing** across all modules:
158+
159+
### Parser Tests (4 tests)
160+
161+
- ✅ Simple schema parsing
162+
- ✅ Complex schema with all component types
163+
- ✅ Invalid RON syntax error handling
164+
- ✅ Component configuration conversion
165+
166+
### Standard Schema Tests (8 tests)
167+
168+
- ✅ Tier determination logic (4 parameterized cases)
169+
- ✅ Schema generation for each tier (3 tests)
170+
- ✅ Integration with `get_standard_schema`
171+
172+
### CalVer Schema Tests (8 tests)
173+
174+
- ✅ Tier determination logic (4 parameterized cases)
175+
- ✅ Schema generation for each tier (3 tests)
176+
- ✅ Integration with `get_calver_schema`
177+
178+
### Preset Dispatcher Tests (3 tests)
179+
180+
- ✅ Standard schema selection
181+
- ✅ CalVer schema selection
182+
- ✅ Unknown schema handling
183+
184+
### Main Module Tests (9 tests)
185+
186+
- ✅ Preset schema integration (6 parameterized cases)
187+
- ✅ Default schema behavior
188+
- ✅ Custom RON schema functionality
189+
- ✅ Error handling (conflicting, unknown, parse errors)
190+
191+
### Core Integration Test (1 test)
192+
193+
- ✅ Empty schema edge case
194+
195+
## Key Architecture Decisions
196+
197+
1. **Functions over Constants**: Schema definitions use functions instead of constants due to Rust's allocation restrictions in const contexts
198+
199+
2. **Correct Field Names**: Uses proper `ZervVars` field names:
200+
- `current_branch` (not `branch`)
201+
- `current_commit_hash` (not `commit`)
202+
203+
3. **Tier-Based Logic**: 3-tier system based on Git repository state:
204+
- **Tier 1**: Clean, tagged state (minimal components)
205+
- **Tier 2**: Clean with distance (adds post-release metadata)
206+
- **Tier 3**: Dirty state (adds development identifiers)
207+
208+
4. **Error Standards Compliance**:
209+
- Uses `ZervError` enum for all custom errors
210+
- Uses `io::Error::other()` instead of deprecated patterns
211+
- Includes context in error messages
212+
213+
5. **Comprehensive Testing**:
214+
- Uses `rstest` for parameterized testing
215+
- Covers all code paths and error conditions
216+
- Tests integration between modules
217+
218+
## Usage Examples
219+
220+
### Default Schema
221+
222+
```rust
223+
let vars = ZervVars { major: Some(1), minor: Some(2), patch: Some(3), ..Default::default() };
224+
let zerv = create_zerv_version(vars, None, None)?; // Uses zerv-standard
225+
```
226+
227+
### Preset Schema
228+
229+
```rust
230+
let zerv = create_zerv_version(vars, Some("zerv-calver"), None)?;
231+
```
232+
233+
### Custom RON Schema
234+
235+
```rust
236+
let ron_schema = r#"
237+
SchemaConfig(
238+
core: [(type: "VarField", field: "major")],
239+
extra_core: [],
240+
build: [(type: "String", value: "custom")]
241+
)
242+
"#;
243+
let zerv = create_zerv_version(vars, None, Some(ron_schema))?;
244+
```
245+
246+
## Next Steps
247+
248+
The schema system is complete and ready for CLI integration. Next implementation phases can focus on:
249+
250+
1. **CLI Commands**: Integrate schema system with `zerv version` and `zerv check` commands
251+
2. **Output Formats**: Connect schemas to PEP440/SemVer output formatters
252+
3. **Template System**: Add custom template support for advanced use cases
253+
254+
**All success criteria met. Implementation ready for production use.**

.github/workflows/ci-test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ jobs:
1919
ZERV_TEST_DOCKER: ${{ matrix.os == 'ubuntu-latest' && 'true' || 'false' }}
2020
steps:
2121
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
22+
with:
23+
fetch-depth: 0
2224

2325
- uses: dtolnay/rust-toolchain@e97e2d8cc328f1b50210efc529dca0028893a2d9 # stable
2426
with:

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;

0 commit comments

Comments
 (0)