Skip to content

(GH-538) Define newtypes for versions and version requirements#1350

Open
michaeltlombardi wants to merge 6 commits intoPowerShell:mainfrom
michaeltlombardi:gh-538/main/type_version
Open

(GH-538) Define newtypes for versions and version requirements#1350
michaeltlombardi wants to merge 6 commits intoPowerShell:mainfrom
michaeltlombardi:gh-538/main/type_version

Conversation

@michaeltlombardi
Copy link
Collaborator

@michaeltlombardi michaeltlombardi commented Jan 12, 2026

PR Summary

This change follows the Rust "parse, don't validate pattern" by defining the following newtypes:

  • SemanticVersion as a wrapper around semver::Version to enable a fully controlled JSON Schema for semantic versions.
  • SemanticVersionReq as a wrapper around semver::VersionReq both to enable providing a full JSON Schema for semantic version requirements and to apply stricter standards for parsing the type in the context of DSC.
  • ResourceVersion as an enum with the Semantic and Arbitrary variants to support DSC resources with semantic versions (preferred and recommended for resource authors) and arbitrary strings (required for compatibility purposes to enable versions like 2026-02).
  • ResourceVersionReq as an enum with the Semantic and Arbitrary variants to support defining version pinning for DSC resources with semantic and arbitrary version strings.

Each newtype implements comparison and conversion traits to make using the types more ergonomic. The types are also defined with extensive reference documentation and integration tests to improve confidence and clarity.

Important notes for behavior:

  1. When comparing instances of ResourceVersion:

    • A semantic version is always greater than an arbitrary string version. This applies both when comparing ResourceVersion::Arbitrary instances to ResourceVersion::Semantic and to SemanticVersion.
    • Arbitrary string version comparisons are case-sensitive. Foo and foo are not equal. Ordering comparisons are lexicographic.
    • You can directly compare instances of ResourceVersion to string slices, String instances, and SemanticVersion instances in addition to other instances of ResourceVersion.
    • The trait implementations support using ==, >, and < operators for easier reading.
  2. When matching a resource version to ResourceVersionReq:

    • A version only matches a requirement when they are of the same variant (arbitrary version and arbitrary requirement, semantic version and semantic requirement).
    • The semantic version requirements uses the underlying logic for semver::VersionReq.
    • Arbitrary version requirements are only met by versions that are equal to the version requirement string. The comparison is case-sensitive.
  3. The SemanticVersionReq is nearly identical to semver::VersionReq except that:

    1. DSC semantic version requirements forbid the inclusion of build metadata.

      Rust allows and ignores them. DSC forbids the inclusion of build metadata to limit confusion and unexpected behavior when specifying a version requirement for a DSC resource or extension.

    2. DSC semantic version requirements must define a major version segment. All other segments are optional.

      Rust technically supports specifying a wildcard-only version requirement (*). DSC forbids specifying this version requirement as it maps to the default version selection and is discouraged when specifying version requirements for production systems.

    3. DSC semantic version requirements only support the asterisk (*) character for wildcards, not x or X.

PR Context

Prior to this change, the version and version requirement fields for DSC used an arbitrary String for both resources and extensions. The generated schema for those types then reports that any string is valid and DSC must check every time it needs to process the version for whether the version is semantic or an arbitrary string.

Implementing strong types for these values enables DSC to parse the input strings once and reuse them as-needed throughout the code. It also ensures that we can provide canonical JSON Schemas for these fields.

This comment was marked as outdated.

@michaeltlombardi michaeltlombardi force-pushed the gh-538/main/type_version branch 2 times, most recently from 170bfe1 to 5955b07 Compare January 13, 2026 20:13
@michaeltlombardi michaeltlombardi force-pushed the gh-538/main/type_version branch from 5955b07 to 96257ea Compare February 5, 2026 21:38
@michaeltlombardi michaeltlombardi changed the title (GH-538) Define TypeVersion as newtype (GH-538) Define newtypes for versions and version requirements Feb 5, 2026

This comment was marked as outdated.

This comment was marked as outdated.

This comment was marked as outdated.

This comment was marked as outdated.

This comment was marked as outdated.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 17 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@michaeltlombardi michaeltlombardi marked this pull request as ready for review February 5, 2026 23:04
Prior to this change, the `version` fields in various types for
`dsc-lib` used `String`. In order to correctly present the JSON Schema
for a semantic version, the library needs a reusable type.

This change:

- Defines the `SemanticVersion` newtype as a wrapper around the
  `semver::Version` type.
- Implements methods for creating instances of the type from strings
  and version segments, mirroring the implementation for
  `semver::Version`.
- Implements traits for comparing instances of the type to strings and
  `semver::Version`.
- Defines the JSON Schema for the type with a validation pattern and
  VS Code vocabulary keywords for documentation.
- Adds integration tests for the type's behavior.

This change doesn't modify any existing code. Updating types in the
library to use `SemanticVersion` must be done in a follow up change.
Prior to this change, `dsc-lib` used the `semver::VersionReq` type for
pinning versions of resources and extensions. To provide better schema
values for specifying semantic version requirements, we need to Define
a newtype so we can modify and extend the JSON Schema.

This change:

- Defines the `SemanticVersionReq` newtype as a wrapper for
  `semver::VersionReq` and implements traits for conversion and
  comparison.

  The wrapping type has stricter validation requirements over the
  underlying type, given the usage and context for DSC.
- Provides thorough documentation with the understanding that this
  will provide context to the maintainers, to integrating developers
  working with the library, and eventually be hoisted to CLI user
  documentation when discussing how to define version requirements in
  configuration documents.

This change does not modify any code in the library to use the wrapping
type instead of the underlying type. That will be accomplished in a
future changeset.
Prior to this change, the version fields for DSC used an arbitrary
`String` for both resources and extensions. The generated schema for
those types then reports that any string is valid and DSC must check
every time it needs to process the version for whether the version is
semantic or an arbitrary string.

This change follows the Rust "parse, don't validate pattern" by defining
a `ResourceVersion` enum with the `Semantic` and `String` variants. If the
version can be parsed as a semantic version, the type creates it as
an instance of `ResourceVersion::Semantic` and otherwise creates it as
`ResourceVersion::Arbitrary`.

The `ResourceVersion` enum implements several conversion and comparison
traits to make using the newtype more ergonomic. It also defines helper
methods `is_arbitrary()`, `is_semver()` and `matches_semver_req()` for
easier usage.

When comparing an instance of `ResourceVersion`:

- A semantic version is always greater than an arbitrary string version.

  This applies both when comparing `ResourceVersion::Arbitrary`
  instances to `ResourceVersion::Semantic` and to `semver::Version`.

- Arbitrary string version comparisons use Rust's underlying
  [lexicographic comparison logic][01].

  Arbitrary string versions are only equivalent when the strings are
  exactly the same. If the strings differ by case, spacing, or any
  other characters, they are unequal.

  Because ordering is lexicographic, arbitrary string version `Foo` is
  greater than both `foo` and `Bar`.

- You can directly compare instances of `ResourceVersion` to string
  slices and instances of `ResourceVersion`, `SemanticVersion`,
  `String`, and `semver::Version`.

- The trait implementations support using `==`, `>`, and `<` operators
  for easier reading.

The newtype overhauls the JSON Schema for resource versions to help
users get better validation and IntelliSense when authoring manifests.

Finally, this change adds comprehensive integration tests for the
newtype and its implementations as well as documentation for the type
and its public methods.
Prior to this change, the `apiVersion` field for resource instances in
configuration documents used the rust type `Option<String>` to represent
a version pin for a DSC Resource.

This change:

- Defines the `ResourceVersionReq` enum, which can contain either a
  `SemanticVersionReq` or an arbitrary string. Like the
  `ResourceVersion` enum, this enables us to distinguish between
  resources that are semantically versioned and those using an arbitrary
  string version. If the string can be parsed as a semantic version
  requirement the created instance is the `Semantic` variant and
  otherwise `Arbitrary`.
- Implements the `matches` method to compare an instance of
  `ResourceVersion` against a version requirement.

  When both the version and requirement are semantic, it uses the
  underlying implementation for semantic version requirements in
  `semver::VersionReq`.

  When either the version or requirement is semantic and the other is
  arbitrary the match fails.

  When both the version and requirement are arbitrary, the version
  matches the requirement only when the strings are equal. The
  comparison is case sensitive.
- Implements traits for converting and comparing instances of
  `ResourceVersionReq` to string slices and instances of `String` and
  `SemanticVersionReq`.
- Implements the `JsonSchema` and `DscRepoSchema` traits for the
  newtype to enable sharing a canonical JSON Schema for this field.
@michaeltlombardi michaeltlombardi force-pushed the gh-538/main/type_version branch from 0588c30 to 5c29625 Compare February 6, 2026 18:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant