Skip to content

Commit

Permalink
feature: parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
mprimeaux committed Nov 25, 2024
1 parent 493216c commit 5397718
Show file tree
Hide file tree
Showing 10 changed files with 667 additions and 273 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,18 @@ Date format: `YYYY-MM-DD`
## [1.1.0] - 2024-10-25

### Added
- **FEATURE:** Added support for a `Parsing` interface to allow for custom version parsing.
- **FEATURE:** Added documentation for the configuration options: `WithStrictAdherence`.

### Changed
- **DEBT:** Added additional functional tests to improve code coverage.
- **DEBT:** Refactored the benchmark tests to correctly measure performance.

### Deprecated
### Removed
### Fixed
- **DEFECT:** Corrected various documentation issues.

### Security

---
Expand Down
73 changes: 34 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,60 +15,57 @@ A Semantic Versioning 2.0.0 compliant parser and utility library written in Go.

The `semver` library offers a comprehensive and efficient solution for working with Semantic Versioning 2.0.0. Key features include:

### Zero Dependencies
- Lightweight implementation with no external dependencies beyond the standard library.
- **Zero Dependencies**
- Lightweight implementation with no external dependencies beyond the standard library.

### Full Compliance
- Parses and validates semantic versions according to the [Semantic Versioning 2.0.0](https://semver.org) specification.
- **Full Compliance**
- Parses and validates semantic versions according to the [Semantic Versioning 2.0.0](https://semver.org) specification.

### Version Parsing and Validation
- Parse semantic version strings into structured `Version` objects.
- Automatically validate version strings for correctness, including:
- **Version Parsing and Validation**
- Parses semantic version strings into structured `Version` objects.
- Automatically validates version strings for correctness, including:
- Major, minor, and patch components.
- Pre-release identifiers (e.g., `alpha`, `beta`, `rc.1`).
- Build metadata (e.g., `build.123`).
- Enforces no leading zeroes in numeric components.

### Version Comparison
- Supports comparison of versions using Semantic Versioning rules:
- **Customizable**
- Define your own parser.
- Strict mode for enforcing strict version format rules.

- **Version Comparison**
- Supports comparison of versions using Semantic Versioning rules:
- `Compare`: Returns -1, 0, or 1 for less than, equal to, or greater than comparisons.
- Convenient helper methods:
- `LessThan`, `LessThanOrEqual`
- `GreaterThan`, `GreaterThanOrEqual`
- `Equal`
- Correctly handles precedence rules for pre-release versions and build metadata.
- Convenient helper methods: `LessThan`, `LessThanOrEqual`, `GreaterThan`, `GreaterThanOrEqual`, `Equal`.
- Correctly handles precedence rules for pre-release versions and build metadata.

### Version Ranges
- Flexible range functionality for evaluating version constraints:
- **Version Ranges**
- Flexible range functionality for evaluating version constraints:
- Define complex version ranges using a familiar syntax (e.g., `">=1.0.0 <2.0.0"`).
- Determine whether a version satisfies a given range.
- Combine multiple ranges for advanced constraints (e.g., `">=1.2.3 || <1.0.0-alpha"`).
- Useful for dependency management, release gating, and compatibility checks.

### Version Construction
- Create `Version` instances programmatically using the `NewVersion` constructor.
- Supports detailed customization of pre-release identifiers and build metadata.
- Useful for dependency management, release gating, and compatibility checks.

### JSON Support
- Seamlessly marshal and unmarshal `Version` objects to and from JSON.
- Works with `encoding/json` for easy integration with APIs and configuration files.
- **JSON Support**
- Seamlessly marshal and unmarshal `Version` objects to and from JSON.
- Works with `encoding/json` for easy integration with APIs and configuration files.

### Database Support
- Compatible with `database/sql`:
- **Database Support**
- Compatible with `database/sql`:
- Implements `driver.Valuer` to store `Version` in databases.
- Implements `sql.Scanner` to retrieve `Version` from databases.

### Encoding and Decoding
- Implements standard Go interfaces:
- **Encoding and Decoding**
- Implements standard Go interfaces:
- `encoding.TextMarshaler` and `encoding.TextUnmarshaler` for text encoding.
- `encoding.BinaryMarshaler` and `encoding.BinaryUnmarshaler` for binary encoding.

### Performance Optimizations
- Efficient parsing and comparison with minimal memory allocations.
- Designed for high performance with concurrent workloads.
- **Performance Optimizations**
- Efficient parsing and comparison with minimal memory allocations.
- Designed for high performance with concurrent workloads.

### Well-Tested
- Comprehensive test coverage, including:
- **Well-Tested**
- Comprehensive test coverage, including:
- Functional tests for all features.
- Benchmarks to validate performance optimizations.
- Detailed tests for range evaluation, parsing, and edge cases.
Expand Down Expand Up @@ -209,13 +206,11 @@ goos: darwin
goarch: arm64
pkg: github.com/sixafter/semver
cpu: Apple M2 Ultra
BenchmarkParseVersionSerial-24 1642676 725.2 ns/op 544 B/op 14 allocs/op
BenchmarkParseVersionConcurrent-24 4059534 303.9 ns/op 544 B/op 14 allocs/op
BenchmarkParseVersionAllocations-24 7236553 162.4 ns/op 144 B/op 4 allocs/op
BenchmarkParseVersionLargeSerial-24 208 5747548 ns/op 4160079 B/op 110000 allocs/op
BenchmarkParseVersionLargeConcurrent-24 656 1874606 ns/op 4160163 B/op 110000 allocs/op
BenchmarkParseVersionSerial-24 9170428 123.1 ns/op 128 B/op 2 allocs/op
BenchmarkParseVersionConcurrent-24 17886765 68.35 ns/op 128 B/op 2 allocs/op
BenchmarkParseVersionAllocations-24 7576131 157.2 ns/op 144 B/op 4 allocs/op
PASS
ok github.com/sixafter/semver 8.274s
ok github.com/sixafter/semver 4.109s
```
</details>

Expand Down
111 changes: 111 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright (c) 2024 Six After, Inc
//
// This source code is licensed under the Apache 2.0 License found in the
// LICENSE file in the root directory of this source tree.

package semver

// ConfigOptions holds the configurable options for the Parser.
// It is used with the Function Options pattern.
type ConfigOptions struct {
Strict bool
}

// Config holds the runtime configuration for the parser.
//
// It is immutable after initialization.
type Config interface {
// StrictAdherence returns a boolean value indicating whether strict adherence is enabled.
// This method is used to determine if the configuration should follow strict rules,
// such as requiring full compliance with the Semantic Versioning specification.
// When enabled, parsing or processing might be more stringent, rejecting inputs that
// do not fully comply with the expected standards.
//
// Returns:
// - bool: true if strict adherence is enabled, false otherwise.
//
// Example usage:
//
// var config Config = NewConfig()
// if config.StrictAdherence() {
// fmt.Println("Strict adherence is enabled.")
// } else {
// fmt.Println("Strict adherence is disabled.")
// }
StrictAdherence() bool
}

// Configuration defines the interface for retrieving parser configuration.
type Configuration interface {
// Config returns the runtime configuration of the parser.
Config() Config
}

type runtimeConfig struct {
strict bool
}

// Option defines a function type for configuring the Parser.
// It allows for flexible and extensible configuration by applying
// various settings to the ConfigOptions during Parser initialization.
type Option func(*ConfigOptions)

// WithStrictAdherence sets the strict adherence value for the configuration.
// This option can be used to enable or disable strict mode, which affects the way
// certain rules are enforced during parsing or processing.
//
// Setting strict adherence to true can be used to enforce more rigid compliance
// with versioning rules or configuration standards. When set to false, the parser
// may allow some flexibility in handling certain inputs.
//
// Parameters:
// - value: A boolean indicating whether strict adherence should be enabled (true) or disabled (false).
//
// Returns:
// - Option: A functional option that can be passed to a configuration function to modify behavior.
//
// Example usage:
//
// parser, err := NewParser(WithStrictAdherence(true))
// if err != nil {
// log.Fatalf("Failed to create parser: %v", err)
// }
//
// // Use the parser with strict adherence enabled
// version, err := parser.Parse("1.0.0")
// if err != nil {
// log.Fatalf("Failed to parse version: %v", err)
// }
// fmt.Printf("Parsed version: %v\n", version)
func WithStrictAdherence(value bool) Option {
return func(o *ConfigOptions) {
o.Strict = value
}
}

// StrictAdherence returns a boolean value indicating whether strict adherence is enabled.
// This method is used to determine if the configuration should follow strict rules,
// such as requiring full compliance with the Semantic Versioning specification.
// When enabled, parsing or processing might be more stringent, rejecting inputs that
// do not fully comply with the expected standards.
//
// Returns:
// - bool: true if strict adherence is enabled, false otherwise.
//
// Example usage:
//
// var config Config = NewConfig()
// if config.StrictAdherence() {
// fmt.Println("Strict adherence is enabled.")
// } else {
// fmt.Println("Strict adherence is disabled.")
// }
func (c *runtimeConfig) StrictAdherence() bool {
return c.strict
}

func buildRuntimeConfig(opts *ConfigOptions) (*runtimeConfig, error) {
return &runtimeConfig{
strict: opts.Strict,
}, nil
}
29 changes: 29 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright (c) 2024 Six After, Inc
//
// This source code is licensed under the Apache 2.0 License found in the
// LICENSE file in the root directory of this source tree.

package semver

import (
"testing"

"github.com/stretchr/testify/assert"
)

// TestGetConfig tests the Config() method of the generator.
func TestGetConfig(t *testing.T) {
t.Parallel()
is := assert.New(t)

gen, err := NewParser(WithStrictAdherence(true))
is.NoError(err, "NewGenerator() should not return an error with the default alphabet")

// Assert that generator implements Configuration interface
config, ok := gen.(Configuration)
is.True(ok, "Parser should implement Configuration interface")

runtimeConfig := config.Config()

is.True(runtimeConfig.StrictAdherence(), "Config.StrictAdherence should be true")
}
104 changes: 104 additions & 0 deletions marshalers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright (c) 2024 Six After, Inc
//
// This source code is licensed under the Apache 2.0 License found in the
// LICENSE file in the root directory of this source tree.

package semver

import (
"encoding/json"
"testing"

"github.com/stretchr/testify/assert"
)

func TestVersionMarshalText(t *testing.T) {
t.Parallel()
v := MustParse("1.2.3-alpha+build.123")
text, err := v.MarshalText()

is := assert.New(t)
is.NoError(err)
is.Equal("1.2.3-alpha+build.123", string(text))
}

func TestVersionUnmarshalText(t *testing.T) {
t.Parallel()
var v Version
err := v.UnmarshalText([]byte("1.2.3-alpha+build.123"))

is := assert.New(t)
is.NoError(err)
is.Equal(MustParse("1.2.3-alpha+build.123"), v)
}

func TestVersionMarshalBinary(t *testing.T) {
t.Parallel()
v := MustParse("1.2.3-beta")
binaryData, err := v.MarshalBinary()

is := assert.New(t)
is.NoError(err)
is.Equal([]byte("1.2.3-beta"), binaryData)
}

func TestVersionUnmarshalBinary(t *testing.T) {
t.Parallel()
var v Version
err := v.UnmarshalBinary([]byte("1.2.3+build.456"))

is := assert.New(t)
is.NoError(err)
is.Equal(MustParse("1.2.3+build.456"), v)
}

func TestVersionMarshalJSON(t *testing.T) {
t.Parallel()
v := MustParse("1.2.3-alpha")
jsonData, err := json.Marshal(v)

is := assert.New(t)
is.NoError(err)
is.JSONEq(`"1.2.3-alpha"`, string(jsonData))
}

func TestVersionUnmarshalJSON(t *testing.T) {
t.Parallel()
var v Version
err := json.Unmarshal([]byte(`"1.2.3-beta+build.789"`), &v)

is := assert.New(t)
is.NoError(err)
is.Equal(MustParse("1.2.3-beta+build.789"), v)
}

func TestVersionValue(t *testing.T) {
t.Parallel()
v := MustParse("1.2.3-alpha")
dbValue, err := v.Value()

is := assert.New(t)
is.NoError(err)
is.Equal("1.2.3-alpha", dbValue)
}

func TestVersionScan(t *testing.T) {
t.Parallel()
var v Version
is := assert.New(t)

// Test with string
err := v.Scan("1.2.3-alpha+build.123")
is.NoError(err)
is.Equal(MustParse("1.2.3-alpha+build.123"), v)

// Test with []byte
err = v.Scan([]byte("1.2.3-beta"))
is.NoError(err)
is.Equal(MustParse("1.2.3-beta"), v)

// Test with unsupported type
err = v.Scan(123)
is.Error(err)
is.EqualError(err, "unsupported type for Version")
}
Loading

0 comments on commit 5397718

Please sign in to comment.