Skip to content

Commit de5baff

Browse files
(GH-538) Define TypeVersion as newtype
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 `TypeVersion` 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 `TypeVersion::Semantic` and otherwise creates it as `TypeVersion::String`. The `TypeVersion` enum implements several conversion and comparison traits to make using the newtype more ergonomic. It also defines helper methods `is_semver()` and `matches_semver_req()` for easier usage. When comparing an instance of `TypeVersion`: - A semantic version is always greater than an arbitrary string version. This applies both when comparing `TypeVersion::String` instances to `TypeVersion::Semantic` and to `semver::Version`. - Arbitrary string version comparisons are case-insensitive. `Foo` and `foo` are equal but `Foo` is greater than `Bar`. - You can directly compare instances of `TypeVersion` to string slices, `String` instances, and `semver::Version` instances in addition to other instances of `TypeVersion`. - The trait implementations support using `==`, `>`, and `<` operators for easier reading. The newtype overhauls the JSON Schema for 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.
1 parent 7f3a2c1 commit de5baff

File tree

10 files changed

+1108
-34
lines changed

10 files changed

+1108
-34
lines changed

Cargo.lock

Lines changed: 39 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: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,10 @@ ipnetwork = { version = "0.21" }
225225
cc = { version = "1.2" }
226226

227227
# test-only dependencies
228-
# dsc-lib-jsonschema
228+
# dsc-lib-jsonschema, dsc-lib
229229
pretty_assertions = { version = "1.4.1" }
230+
# dsc-lib
231+
test-case = { version = "3.3" }
230232

231233
# Workspace crates as dependencies
232234
dsc-lib = { path = "lib/dsc-lib" }

lib/dsc-lib/Cargo.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ serde = { workspace = true }
2828
serde_json = { workspace = true }
2929
serde_yaml = { workspace = true }
3030
thiserror = { workspace = true }
31-
semver = { workspace = true }
31+
semver = { workspace = true, features = ["serde"] }
3232
tokio = { workspace = true, features = [
3333
"io-util",
3434
"macros",
@@ -38,7 +38,7 @@ tokio = { workspace = true, features = [
3838
tracing = { workspace = true }
3939
tracing-indicatif = { workspace = true }
4040
tree-sitter = { workspace = true }
41-
tree-sitter-rust = { workspace = true}
41+
tree-sitter-rust = { workspace = true }
4242
uuid = { workspace = true }
4343
url = { workspace = true }
4444
urlencoding = { workspace = true }
@@ -52,6 +52,10 @@ tree-sitter-dscexpression = { workspace = true }
5252

5353
[dev-dependencies]
5454
serde_yaml = { workspace = true }
55+
# Helps review complex comparisons, like schemas
56+
pretty_assertions = { workspace = true }
57+
# Enables parameterized test cases
58+
test-case = { workspace = true }
5559

5660
[build-dependencies]
5761
cc = { workspace = true }

lib/dsc-lib/locales/en-us.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,7 @@ resourceManifestNotFound = "Resource manifest not found"
733733
schema = "Schema"
734734
schemaNotAvailable = "No Schema found and `validate` is not supported"
735735
securityContext = "Security context"
736+
typeVersionToSemverConversion = "Can't convert arbitrary string `Version` to `semver::Version`"
736737
utf8Conversion = "UTF-8 conversion"
737738
unknown = "Unknown"
738739
validation = "Validation"

lib/dsc-lib/locales/schemas.definitions.yaml

Lines changed: 103 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,106 @@ _version: 2
22
schemas:
33
definitions:
44
resourceType:
5-
title: Fully qualified type name
6-
description: >-
7-
Uniquely identifies a DSC resource or extension.
8-
markdownDescription: |-
9-
The fully qualified type name of a DSC resource or extension uniquely identifies a resource
10-
or extension.
11-
12-
Fully qualified type names use the following syntax:
13-
14-
```yaml
15-
<owner>[.<namespace>...]/<name>
16-
```
17-
18-
Where the type may have zero or more namespace segments for organizing the type. The
19-
`owner`, `namespace`, and `name` segments must consist only of alphanumeric characters and
20-
underscores.
21-
22-
Conventionally, the first character of each segment is capitalized. When a segment
23-
contains a brand or proper name, use the correct casing for that word, like
24-
`TailspinToys/Settings`, not `Tailspintoys/Settings`.
25-
26-
Example fully qualified type names include:
27-
28-
- `Microsoft/OSInfo`
29-
- `Microsoft.SqlServer/Database`
30-
- `Microsoft.Windows.IIS/WebApp`
31-
patternErrorMessage: >-
32-
Invalid type name. Valid resource type names always define an owner and a name separated by
33-
a slash, like `Microsoft/OSInfo`. Type names may optionally include the group, area, and
34-
subarea segments to namespace the resource under the owner, like
35-
`Microsoft.Windows/Registry`.
5+
title:
6+
en-us: Fully qualified type name
7+
description:
8+
en-us: >-
9+
Uniquely identifies a DSC resource or extension.
10+
markdownDescription:
11+
en-us: |-
12+
The fully qualified type name of a DSC resource or extension uniquely identifies a resource
13+
or extension.
14+
15+
Fully qualified type names use the following syntax:
16+
17+
```yaml
18+
<owner>[.<namespace>...]/<name>
19+
```
20+
21+
Where the type may have zero or more namespace segments for organizing the type. The
22+
`owner`, `namespace`, and `name` segments must consist only of alphanumeric characters and
23+
underscores.
24+
25+
Conventionally, the first character of each segment is capitalized. When a segment
26+
contains a brand or proper name, use the correct casing for that word, like
27+
`TailspinToys/Settings`, not `Tailspintoys/Settings`.
28+
29+
Example fully qualified type names include:
30+
31+
- `Microsoft/OSInfo`
32+
- `Microsoft.SqlServer/Database`
33+
- `Microsoft.Windows.IIS/WebApp`
34+
patternErrorMessage:
35+
en-us: >-
36+
Invalid type name. Valid resource type names always define an owner and a name separated
37+
by a slash, like `Microsoft/OSInfo`. Type names may optionally include the group, area,
38+
and subarea segments to namespace the resource under the owner, like
39+
`Microsoft.Windows/Registry`.
40+
41+
semver:
42+
title:
43+
en-us: Semantic version
44+
description:
45+
en-us: |-
46+
A valid semantic version (semver) string.
47+
48+
For reference, see https://semver.org/
49+
markdownDescription:
50+
en-us: |-
51+
A valid semantic version ([semver][01]) string.
52+
53+
This value uses the [suggested regular expression][02] to validate whether the string is
54+
valid semver. This is the same pattern, made multi-line for easier readability:
55+
56+
```regex
57+
^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)
58+
(?:-(
59+
(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)
60+
(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))
61+
*))?
62+
(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$
63+
```
64+
65+
The first line matches the `major.minor.patch` components of the version. The middle
66+
lines match the pre-release components. The last line matches the build metadata
67+
component.
68+
69+
[01]: https://semver.org/
70+
[02]: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
71+
patternErrorMessage:
72+
en-us: |-
73+
Invalid value, must be a semantic version like `<major>.<minor>.<patch>`, such as `1.2.3`.
74+
75+
The value may also include pre-release version information and build metadata.
76+
77+
version:
78+
title:
79+
en-us: Version
80+
description:
81+
en-us: >-
82+
Defines the version for the type as either a semantic version (semver) or arbitrary string.
83+
markdownDescription:
84+
en-us: |-
85+
Defines the version for the type as either a semantic version (semver) or arbitrary string.
86+
87+
If the type adheres to [semantic versioning][01], its manifest should define the version as
88+
a valid semantic version like `1.2.3`. When a resource or extension specifies a semantic
89+
version, DSC uses the latest available version of that resource or extension by default.
90+
91+
Instead of specifying a semantic version, the type can specify any arbitrary string. In
92+
that case, DSC uses simple string sorting to determine the default version to use.
93+
94+
Users can override the default behavior and require a specific version of a resource with
95+
the `something` field.
96+
stringVariant:
97+
title:
98+
en-us: Arbitrary version string
99+
description:
100+
en-us: >-
101+
Defines the version for the type as an arbitrary string.
102+
markdownDescription:
103+
en-us: |-
104+
Defines the version for the type as an arbitrary string. When the version for the type
105+
isn't a valid semantic version, DSC treats the version as a string. This enables
106+
DSC to support non-semantically-versioned types, such as using a release date as the
107+
version.

lib/dsc-lib/src/dscerror.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,9 @@ pub enum DscError {
121121
#[error("semver: {0}")]
122122
SemVer(#[from] semver::Error),
123123

124+
#[error("{t}: '{0}'", t = t!("dscerror.typeVersionToSemverConversion"))]
125+
TypeVersionToSemverConversion(String),
126+
124127
#[error("{t}: {0}", t = t!("dscerror.utf8Conversion"))]
125128
Utf8Conversion(#[from] Utf8Error),
126129

lib/dsc-lib/src/types/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,5 @@
33

44
mod fully_qualified_type_name;
55
pub use fully_qualified_type_name::FullyQualifiedTypeName;
6+
mod type_version;
7+
pub use type_version::TypeVersion;

0 commit comments

Comments
 (0)