Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 101 additions & 5 deletions .dev/23-schema-first-zerv-conversion.md
Original file line number Diff line number Diff line change
Expand Up @@ -706,16 +706,112 @@ All operations return `Result<T, ZervError>`:
- No duplicate components allowed
- Comprehensive error messages for all validation failures

### 🔄 In Progress
### ✅ Step 2: Update PEP440 from_zerv Implementation - COMPLETED

- **Step 2**: `src/version/pep440/from_zerv.rs` - Plan 20 integration
- **Step 3**: `src/version/semver/from_zerv.rs` - Plan 20 integration
- **Step 4**: `src/version/pep440/mod.rs` - Two-tier API
- **Step 5**: `src/version/semver/mod.rs` - Two-tier API
**File**: `src/version/pep440/from_zerv.rs`

**✅ All requirements implemented:**

1. **✅ Schema-driven approach** - Replaced manual resolution with schema structure processing
2. **✅ Plan 20 integration** - Uses `resolve_value()` and `resolve_expanded_values()` methods exclusively
3. **✅ Component categorization** - Uses `is_secondary_component()` for proper placement logic
4. **✅ Sanitization strategy** - Uses `Sanitizer::uint()` for integers and `Sanitizer::pep440_local_str()` for local strings
5. **✅ Code organization** - Extracted processing logic into separate methods for better maintainability

**✅ Implementation details:**

- **Core processing**: `process_core()` - Appends integers to release vector, overflows to local
- **Extra core processing**: `process_extra_core()` - Handles secondary components (Epoch, PreRelease, Post, Dev) with specific logic, non-secondary components go to local
- **Build processing**: `process_build()` - All components go to local segments
- **Helper method**: `add_flattened_to_local()` - Splits dot-separated values and adds to local segments
- **Import optimization**: Added proper imports for PostLabel and DevLabel

**✅ Test verification**: All 47 PEP440 from_zerv tests pass, confirming functionality is preserved

### ✅ Step 3: Update SemVer from_zerv Implementation - COMPLETED

**File**: `src/version/semver/from_zerv.rs`

**✅ All requirements implemented:**

1. **✅ Schema-driven approach** - Replaced manual resolution with schema structure processing
2. **✅ Plan 20 integration** - Uses `resolve_value()` and `resolve_expanded_values()` methods exclusively
3. **✅ Component categorization** - Uses `is_secondary_component()` for proper placement logic
4. **✅ Sanitization strategy** - Uses `Sanitizer::uint()` for integers and `Sanitizer::semver_str()` for strings
5. **✅ Code organization** - Extracted processing logic into separate methods for better maintainability
6. **✅ Custom field handling** - Fixed `Var::Custom` sanitization to apply sanitizer even when no custom data exists

**✅ Implementation details:**

- **Core processing**: `process_core()` - First 3 parsable ints go to major/minor/patch, rest to pre-release
- **Extra core processing**: `process_extra_core()` - Secondary components get labeled with `resolve_expanded_values()`, others go to pre-release
- **Build processing**: `process_build()` - All components go to build metadata
- **Helper methods**: `add_flattened_to_prerelease()` and `add_flattened_to_build()` for dot-separated values
- **Bug fix**: Fixed `Var::Custom` to apply sanitization even when no custom data exists

**✅ Test verification**: All 72 SemVer from_zerv tests pass, confirming functionality is preserved

### ✅ Step 4: Two-Tier API for PEP440 to_zerv - COMPLETED

**File**: `src/version/pep440/to_zerv.rs`

**✅ All requirements implemented:**

1. **✅ Two-tier API structure** - `From<PEP440>` trait uses default schema, `to_zerv_with_schema()` accepts custom schemas
2. **✅ Default schema factory** - `ZervSchema::pep440_default()` method implemented in schema/core.rs
3. **✅ Schema validation** - Only supports default PEP440 schema for now, returns proper error for custom schemas
4. **✅ Field mapping** - Maps PEP440 fields to ZervVars with proper type conversions (u32 → u64)
5. **✅ Pre-release handling** - Uses `PreReleaseVar` struct with label and optional number
6. **✅ Excess release parts** - Modifies schema to add excess parts beyond major.minor.patch to core
7. **✅ Local segments** - Adds local segments to build section of schema
8. **✅ Plan compliance** - Follows plan specification exactly, respects provided schema and modifies for excess parts

**✅ Implementation details:**

- **Simple API**: `From<PEP440>` uses `pep440_default()` schema and expects conversion to work
- **Advanced API**: `to_zerv_with_schema()` validates schema compatibility and handles custom schemas (future)
- **Schema modification**: Properly modifies provided schema for excess release parts and local segments
- **Error handling**: Returns `ZervError::NotImplemented` for unsupported custom schemas
- **Type safety**: All numeric conversions properly handle u32 → u64 casting
- **Lint compliance**: Fixed clippy warnings by using struct initialization instead of field reassignment

**✅ Test verification**: All 45 PEP440 to_zerv tests pass, including round-trip conversions

### ✅ Step 5: Two-Tier API for SemVer to_zerv - COMPLETED

**File**: `src/version/semver/to_zerv.rs`

**✅ All requirements implemented:**

1. **✅ Two-tier API structure** - `From<SemVer>` trait uses default schema, `to_zerv_with_schema()` accepts custom schemas
2. **✅ Default schema factory** - `ZervSchema::semver_default()` method implemented in schema/core.rs
3. **✅ Schema validation** - Only supports default SemVer schema for now, returns proper error for custom schemas
4. **✅ Field mapping** - Maps SemVer fields to ZervVars with proper type handling (u64 → u64)
5. **✅ Pre-release processing** - Uses existing `PreReleaseProcessor` to handle complex pre-release patterns
6. **✅ Build metadata handling** - Adds build metadata components to schema build section
7. **✅ Schema modification** - Properly modifies provided schema for pre-release and build components
8. **✅ Plan compliance** - Follows plan specification exactly, respects provided schema and modifies for additional components

**✅ Implementation details:**

- **Simple API**: `From<SemVer>` uses `semver_default()` schema and expects conversion to work
- **Advanced API**: `to_zerv_with_schema()` validates schema compatibility and handles custom schemas (future)
- **Schema modification**: Uses validated setters to add extra_core and build components
- **Error handling**: Returns `ZervError::NotImplemented` for unsupported custom schemas
- **Type safety**: No unnecessary casts since SemVer fields are already u64
- **Lint compliance**: Fixed clippy warnings by removing unnecessary type casts

**✅ Test verification**: All 69 SemVer to_zerv tests pass, including round-trip conversions

### 🔄 Next Steps

- **Step 6**: Update remaining test files and modules for new validated API

### ⚠️ Breaking Changes Expected

- ✅ Test files - Updated for getter access and new validation rules
- ✅ **Schema factory methods** - Both PEP440 and SemVer default schemas implemented
- ✅ **Two-tier APIs** - Both PEP440 and SemVer conversion APIs completed
- ⚠️ **Remaining modules** - CLI and other modules still need updates for private fields
- Modules with complex dependencies may be temporarily commented out

Expand Down
199 changes: 199 additions & 0 deletions .dev/24-semver-to-zerv-cleanup-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# SemVer to Zerv Conversion - Clean Implementation

```rust
use super::{
BuildMetadata,
PreReleaseIdentifier,
SemVer,
};
use crate::error::ZervError;
use crate::version::zerv::core::PreReleaseLabel;
use crate::version::zerv::{
Component,
PreReleaseVar,
Var,
Zerv,
ZervSchema,
ZervVars,
};

struct PreReleaseProcessor<'a> {
vars: &'a mut ZervVars,
schema: &'a mut ZervSchema,
pending_var: Option<Var>,
}

impl<'a> PreReleaseProcessor<'a> {
fn new(vars: &'a mut ZervVars, schema: &'a mut ZervSchema) -> Self {
Self {
vars,
schema,
pending_var: None,
}
}

fn is_var_set(&self, var: &Var) -> bool {
match var {
Var::PreRelease => self.vars.pre_release.is_some() || self.pending_var == Some(Var::PreRelease),
Var::Epoch => self.vars.epoch.is_some() || self.pending_var == Some(Var::Epoch),
Var::Post => self.vars.post.is_some() || self.pending_var == Some(Var::Post),
Var::Dev => self.vars.dev.is_some() || self.pending_var == Some(Var::Dev),
_ => false,
}
}

fn handle_pending_prerelease(&mut self, identifier: &PreReleaseIdentifier) -> Result<bool, ZervError> {
if let Some(Var::PreRelease) = self.pending_var {
if let PreReleaseIdentifier::String(_) = identifier {
self.schema.push_extra_core(Component::Var(Var::PreRelease))?;
self.pending_var = None;
return Ok(true);
}
}
Ok(false)
}

fn handle_duplicate(&mut self, var: Var, s: &str) -> Result<bool, ZervError> {
if let Some(pending) = self.pending_var {
if let Some(current_var) = Var::try_from_secondary_label(s) {
if current_var == pending || self.is_var_set(&current_var) {
self.schema.push_extra_core(Component::Var(pending))?;
self.pending_var = None;
self.schema.push_extra_core(Component::Str(s.to_string()))?;
return Ok(true);
}
}
}
Ok(false)
}

fn set_var_value(&mut self, var: Var, value: Option<u64>) -> Result<(), ZervError> {
match var {
Var::Epoch => self.vars.epoch = value,
Var::Post => self.vars.post = value,
Var::Dev => self.vars.dev = value,
Var::PreRelease => {
if let Some(ref mut pr) = self.vars.pre_release {
pr.number = value;
}
}
_ => {}
}
self.schema.push_extra_core(Component::Var(var))?;
self.pending_var = None;
Ok(())
}

fn process_string(&mut self, s: &str) -> Result<(), ZervError> {
if let Some(var) = Var::try_from_secondary_label(s) {
if self.is_var_set(&var) {
self.schema.push_extra_core(Component::Str(s.to_string()))?;
} else if var == Var::PreRelease {
if let Some(label) = PreReleaseLabel::try_from_str(s) {
self.vars.pre_release = Some(PreReleaseVar { label, number: None });
self.pending_var = Some(var);
} else {
self.schema.push_extra_core(Component::Str(s.to_string()))?;
}
} else {
self.pending_var = Some(var);
}
} else {
self.schema.push_extra_core(Component::Str(s.to_string()))?;
}
Ok(())
}

fn process_uint(&mut self, n: u64) -> Result<(), ZervError> {
if let Some(var) = self.pending_var {
self.set_var_value(var, Some(n))
} else {
self.schema.push_extra_core(Component::Int(n))
}
}

fn finalize(&mut self) -> Result<(), ZervError> {
if let Some(var) = self.pending_var {
self.schema.push_extra_core(Component::Var(var))?;
}
Ok(())
}
}

impl From<SemVer> for Zerv {
fn from(semver: SemVer) -> Self {
let schema = ZervSchema::semver_default().expect("SemVer default schema should be valid");
semver
.to_zerv_with_schema(&schema)
.expect("SemVer default conversion should work")
}
}

impl SemVer {
pub fn to_zerv_with_schema(&self, schema: &ZervSchema) -> Result<Zerv, ZervError> {
if *schema != ZervSchema::semver_default()? {
return Err(ZervError::NotImplemented(
"Custom schemas not yet implemented for SemVer conversion".to_string(),
));
}

let mut vars = ZervVars {
major: Some(self.major),
minor: Some(self.minor),
patch: Some(self.patch),
..Default::default()
};

let mut schema = schema.clone();
let mut processor = PreReleaseProcessor::new(&mut vars, &mut schema);

// Process pre-release identifiers
if let Some(pre_release) = &self.pre_release {
for identifier in pre_release {
// Handle pending PreRelease var
if processor.handle_pending_prerelease(identifier)? {
continue;
}

// Handle pending var with potential duplicates
if let PreReleaseIdentifier::String(s) = identifier {
if processor.handle_duplicate(processor.pending_var.unwrap_or(Var::Major), s)? {
continue;
}
}

// Process pending var value
if processor.pending_var.is_some() {
let value = match identifier {
PreReleaseIdentifier::UInt(n) => Some(*n),
_ => None,
};
processor.set_var_value(processor.pending_var.unwrap(), value)?;
continue;
}

// Process new identifier
match identifier {
PreReleaseIdentifier::String(s) => processor.process_string(s)?,
PreReleaseIdentifier::UInt(n) => processor.process_uint(*n)?,
}
}
}

processor.finalize()?;

// Handle build metadata
if let Some(build_metadata) = &self.build_metadata {
for metadata in build_metadata {
let component = match metadata {
BuildMetadata::String(s) => Component::Str(s.clone()),
BuildMetadata::UInt(n) => Component::Int(*n),
};
schema.push_build(component)?;
}
}

Ok(Zerv { vars, schema })
}
}
```
2 changes: 1 addition & 1 deletion .github/workflows/ci-pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
python-version: "3.x"

- name: Setup Node.js
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: "latest"

Expand Down
Loading