diff --git a/docs/_snippets/applies_to-version.md b/docs/_snippets/applies_to-version.md
index f98f758d0..ef6ed62be 100644
--- a/docs/_snippets/applies_to-version.md
+++ b/docs/_snippets/applies_to-version.md
@@ -1,10 +1,65 @@
`applies_to` accepts the following version formats:
-* `Major.Minor`
-* `Major.Minor.Patch`
+### Version specifiers
-Regardless of the version format used in the source file, the version number is always rendered in the `Major.Minor.Patch` format.
+You can use version specifiers to precisely control how versions are interpreted:
+
+| Specifier | Syntax | Description | Example |
+|-----------|--------|-------------|---------|
+| Greater than or equal (default) | `x.x` `x.x+` `x.x.x` `x.x.x+` | Feature available from this version onwards | `ga 9.2+` or `ga 9.2` |
+| Range (inclusive) | `x.x-y.y` `x.x.x-y.y.y` | Feature available only in this version range | `beta 9.0-9.1` |
+| Exact version | `=x.x` `=x.x.x` | Feature available only in this specific version | `preview =9.0` |
+
+Regardless of the version format used in the source file, the version number is always rendered in the `Major.Minor` format in badges.
+
+:::{note}
+The `+` suffix is optional for greater-than-or-equal syntax. Both `ga 9.2` and `ga 9.2+` have the same meaning.
+:::
+
+### Examples
+
+```yaml
+# Greater than or equal (feature available from 9.2 onwards)
+stack: ga 9.2
+stack: ga 9.2+
+
+# Range (feature was in beta from 9.0 to 9.1, then became GA)
+stack: ga 9.2+, beta 9.0-9.1
+
+# Exact version (feature was in preview only in 9.0)
+stack: ga 9.1+, preview =9.0
+```
+
+### Implicit version inference for multiple lifecycles {#implicit-version-inference}
+
+When you specify multiple lifecycles with simple versions (without explicit specifiers), the system automatically infers the version ranges:
+
+**Input:**
+```yaml
+stack: preview 9.0, alpha 9.1, beta 9.2, ga 9.4
+```
+
+**Interpreted as:**
+```yaml
+stack: preview =9.0, alpha =9.1, beta 9.2-9.3, ga 9.4+
+```
+
+The inference rules are:
+1. **Consecutive versions**: If a lifecycle is immediately followed by another in the next minor version, it's treated as an **exact version** (`=x.x`).
+2. **Non-consecutive versions**: If there's a gap between one lifecycle's version and the next lifecycle's version, it becomes a **range** from the start version to one version before the next lifecycle.
+3. **Last lifecycle**: The highest versioned lifecycle is always treated as **greater-than-or-equal** (`x.x+`).
+
+This makes it easy to document features that evolve through multiple lifecycle stages. For example, a feature that goes through preview → beta → GA can be written simply as:
+
+```yaml
+stack: preview 9.0, beta 9.1, ga 9.3
+```
+
+Which is automatically interpreted as:
+```yaml
+stack: preview =9.0, beta 9.1-9.2, ga 9.3+
+```
:::{note}
-**Automatic Version Sorting**: When you specify multiple versions for the same product, the build system automatically sorts them in descending order (highest version first) regardless of the order you write them in the source file. For example, `stack: ga 8.18.6, ga 9.1.2, ga 8.19.2, ga 9.0.6` will be displayed as `stack: ga 9.1.2, ga 9.0.6, ga 8.19.2, ga 8.18.6`. Items without versions (like `ga` without a version or `all`) are sorted last.
+**Automatic Version Sorting**: When you specify multiple versions for the same product, the build system automatically sorts them in descending order (highest version first) regardless of the order you write them in the source file. For example, `stack: ga 9.1, beta 9.0, preview 8.18` will be displayed with the highest priority lifecycle and version first. Items without versions are sorted last.
:::
\ No newline at end of file
diff --git a/docs/syntax/_snippets/inline-level-applies-examples.md b/docs/syntax/_snippets/inline-level-applies-examples.md
index 58f38476c..1fedf4759 100644
--- a/docs/syntax/_snippets/inline-level-applies-examples.md
+++ b/docs/syntax/_snippets/inline-level-applies-examples.md
@@ -55,7 +55,7 @@ This example shows how to use directly a key from the second level of the `appli
::::{tab-item} Output
- {applies_to}`serverless: ga` {applies_to}`stack: ga 9.1.0`
-- {applies_to}`edot_python: preview 1.7.0, ga 1.8.0` {applies_to}`apm_agent_java: beta 1.0.0, ga 1.2.0`
+- {applies_to}`edot_python: preview =1.7.0, ga 1.8.0` {applies_to}`apm_agent_java: beta =1.0.0, ga 1.2.0`
- {applies_to}`stack: ga 9.0` {applies_to}`eck: ga 3.0`
::::
@@ -63,7 +63,7 @@ This example shows how to use directly a key from the second level of the `appli
::::{tab-item} Markdown
```markdown
- {applies_to}`serverless: ga` {applies_to}`stack: ga 9.1.0`
-- {applies_to}`edot_python: preview 1.7.0, ga 1.8.0` {applies_to}`apm_agent_java: beta 1.0.0, ga 1.2.0`
+- {applies_to}`edot_python: preview =1.7.0, ga 1.8.0` {applies_to}`apm_agent_java: beta =1.0.0, ga 1.2.0`
- {applies_to}`stack: ga 9.0` {applies_to}`eck: ga 3.0`
```
::::
diff --git a/docs/syntax/_snippets/multiple-lifecycle-states.md b/docs/syntax/_snippets/multiple-lifecycle-states.md
index bb0bedf40..8c9dcd069 100644
--- a/docs/syntax/_snippets/multiple-lifecycle-states.md
+++ b/docs/syntax/_snippets/multiple-lifecycle-states.md
@@ -1,12 +1,35 @@
-`applies_to` keys accept comma-separated values to specify lifecycle states for multiple product versions. For example:
+`applies_to` keys accept comma-separated values to specify lifecycle states for multiple product versions.
-* A feature is added in 9.1 as tech preview and becomes GA in 9.4:
+When you specify multiple lifecycles with simple versions, the system automatically infers whether each version represents an exact version, a range, or an open-ended range. Refer to [Implicit version inference](/_snippets/applies_to-version.md#implicit-version-inference) for details.
+
+### Examples
+
+* A feature is added in 9.0 as tech preview and becomes GA in 9.1:
+
+ ```yml
+ applies_to:
+ stack: preview 9.0, ga 9.1
+ ```
+
+ The preview is automatically interpreted as `=9.0` (exact), and GA as `9.1+` (open-ended).
+
+* A feature goes through multiple stages before becoming GA:
+
+ ```yml
+ applies_to:
+ stack: preview 9.0, beta 9.1, ga 9.3
+ ```
+
+ Interpreted as: `preview =9.0`, `beta 9.1-9.2`, `ga 9.3+`
+
+* A feature is unavailable for one version, beta for another, preview for a range, then GA:
```yml
applies_to:
- stack: preview 9.1, ga 9.4
+ stack: unavailable 9.0, beta 9.1, preview 9.2, ga 9.4
```
+ Interpreted as: `unavailable =9.0`, `beta =9.1`, `preview 9.2-9.3`, `ga 9.4+`
* A feature is deprecated in ECE 4.0 and is removed in 4.8. At the same time, it has already been removed in {{ech}}:
@@ -15,4 +38,17 @@
deployment:
ece: deprecated 4.0, removed 4.8
ess: removed
+ ```
+
+ The deprecated lifecycle is interpreted as `4.0-4.7` (range until removal).
+
+* Use explicit specifiers when you need precise control:
+
+ ```yml
+ applies_to:
+ # Explicit exact version
+ stack: preview =9.0, ga 9.1+
+
+ # Explicit range
+ stack: beta 9.0-9.1, ga 9.2+
```
\ No newline at end of file
diff --git a/docs/syntax/_snippets/versioned-lifecycle.md b/docs/syntax/_snippets/versioned-lifecycle.md
index ae6af6fee..cfe372e69 100644
--- a/docs/syntax/_snippets/versioned-lifecycle.md
+++ b/docs/syntax/_snippets/versioned-lifecycle.md
@@ -7,6 +7,8 @@
---
```
+ This means the feature is available from version 9.3 onwards (equivalent to `ga 9.3+`).
+
* When a change is introduced as preview or beta, use `preview` or `beta` as value for the corresponding key within the `applies_to`:
```
@@ -16,6 +18,28 @@
---
```
+* When a feature is available only in a specific version range, use the range syntax:
+
+ ```
+ ---
+ applies_to:
+ stack: beta 9.0-9.1, ga 9.2
+ ---
+ ```
+
+ This means the feature was in beta from 9.0 to 9.1, then became GA in 9.2+.
+
+* When a feature was in a specific lifecycle for exactly one version, use the exact syntax:
+
+ ```
+ ---
+ applies_to:
+ stack: preview =9.0, ga 9.1
+ ---
+ ```
+
+ This means the feature was in preview only in 9.0, then became GA in 9.1+.
+
* When a change introduces a deprecation, use `deprecated` as value for the corresponding key within the `applies_to`:
```
@@ -33,4 +57,6 @@
applies_to:
stack: deprecated 9.1, removed 9.4
---
- ```
\ No newline at end of file
+ ```
+
+ With the implicit version inference, this is interpreted as `deprecated 9.1-9.3, removed 9.4+`.
\ No newline at end of file
diff --git a/docs/syntax/applies.md b/docs/syntax/applies.md
index c6621e483..59c33d720 100644
--- a/docs/syntax/applies.md
+++ b/docs/syntax/applies.md
@@ -29,6 +29,41 @@ Where:
- The lifecycle is mandatory.
- The version is optional.
+### Version Syntax
+
+Versions can be specified using several formats to indicate different applicability scenarios:
+
+| Description | Syntax | Example | Badge Display |
+|:------------|:-------|:--------|:--------------|
+| **Greater than or equal to** (default) | `x.x+` `x.x` `x.x.x+` `x.x.x` | `ga 9.1` or `ga 9.1+` | `9.1+` |
+| **Range** (inclusive) | `x.x-y.y` `x.x.x-y.y.y` | `preview 9.0-9.2` | `9.0-9.2` or `9.0+`* |
+| **Exact version** | `=x.x` `=x.x.x` | `beta =9.1` | `9.1` |
+
+\* Range display depends on release status of the second version.
+
+**Important notes:**
+
+- Versions are always displayed as **Major.Minor** (e.g., `9.1`) in badges, regardless of whether you specify patch versions in the source.
+- Each version statement corresponds to the **latest patch** of the specified minor version (e.g., `9.1` represents 9.1.0, 9.1.1, 9.1.6, etc.).
+- When critical patch-level differences exist, use plain text descriptions alongside the badge rather than specifying patch versions.
+
+### Version Validation Rules
+
+The build process enforces the following validation rules:
+
+- **One version per lifecycle**: Each lifecycle (GA, Preview, Beta, etc.) can only have one version declaration.
+ - ✅ `stack: ga 9.2+, beta 9.0-9.1`
+ - ❌ `stack: ga 9.2, ga 9.3`
+- **One "greater than" per key**: Only one lifecycle per product key can use the `+` (greater than or equal to) syntax.
+ - ✅ `stack: ga 9.2+, beta 9.0-9.1`
+ - ❌ `stack: ga 9.2+, beta 9.0+`
+- **Valid range order**: In ranges, the first version must be less than or equal to the second version.
+ - ✅ `stack: preview 9.0-9.2`
+ - ❌ `stack: preview 9.2-9.0`
+- **No version overlaps**: Versions for the same key cannot overlap (ranges are inclusive).
+ - ✅ `stack: ga 9.2+, beta 9.0-9.1`
+ - ❌ `stack: ga 9.2+, beta 9.0-9.2`
+
### Page level
Page level annotations are added in the YAML frontmatter, starting with the `applies_to` key and following the [key-value reference](#key-value-reference). For example:
@@ -134,6 +169,22 @@ Use the following key-value reference to find the appropriate key and value for
## Examples
+### Version Syntax Examples
+
+The following table demonstrates the various version syntax options and their rendered output:
+
+| Source Syntax | Description | Badge Display | Notes |
+|:-------------|:------------|:--------------|:------|
+| `stack: ga 9.1` | Greater than or equal to 9.1 | `Stack│9.1+` | Default behavior, equivalent to `9.1+` |
+| `stack: ga 9.1+` | Explicit greater than or equal to | `Stack│9.1+` | Explicit `+` syntax |
+| `stack: preview 9.0-9.2` | Range from 9.0 to 9.2 (inclusive) | `Stack│Preview 9.0-9.2` | Shows range if 9.2.0 is released |
+| `stack: preview 9.0-9.3` | Range where end is unreleased | `Stack│Preview 9.0+` | Shows `+` if 9.3.0 is not released |
+| `stack: beta =9.1` | Exact version 9.1 only | `Stack│Beta 9.1` | No `+` symbol for exact versions |
+| `stack: ga 9.2+, beta 9.0-9.1` | Multiple lifecycles | `Stack│9.2+` | Only highest priority lifecycle shown |
+| `stack: ga 9.3, beta 9.1+` | Unreleased GA with Preview | `Stack│Beta 9.1+` | Shows Beta when GA unreleased with 2+ lifecycles |
+| `serverless: ga` | No version (base 99999) | `Serverless` | No version badge for unversioned products |
+| `deployment:` ` ece: ga 9.0+` | Nested deployment syntax | `ECE│9.0+` | Deployment products shown separately |
+
### Versioning examples
Versioned products require a `version` tag to be used with the `lifecycle` tag:
@@ -240,22 +291,46 @@ applies_to:
## Look and feel
+### Version Syntax Demonstrations
+
+:::::{dropdown} New version syntax examples
+
+The following examples demonstrate the new version syntax capabilities:
+
+**Greater than or equal to:**
+- {applies_to}`stack: ga 9.1` (implicit `+`)
+- {applies_to}`stack: ga 9.1+` (explicit `+`)
+- {applies_to}`stack: preview 9.0+`
+
+**Ranges:**
+- {applies_to}`stack: preview 9.0-9.2` (range display when both released)
+- {applies_to}`stack: beta 9.1-9.3` (converts to `+` if end unreleased)
+
+**Exact versions:**
+- {applies_to}`stack: beta =9.1` (no `+` symbol)
+- {applies_to}`stack: deprecated =9.0`
+
+**Multiple lifecycles:**
+- {applies_to}`stack: ga 9.2+, beta 9.0-9.1` (shows highest priority)
+
+:::::
+
### Block
:::::{dropdown} Block examples
```{applies_to}
-stack: preview 9.1
+stack: preview 9.1+
serverless: ga
-apm_agent_dotnet: ga 1.0.0
-apm_agent_java: beta 1.0.0
-edot_dotnet: preview 1.0.0
+apm_agent_dotnet: ga 1.0+
+apm_agent_java: beta 1.0+
+edot_dotnet: preview 1.0+
edot_python:
-edot_node: ga 1.0.0
-elasticsearch: preview 9.0.0
-security: removed 9.0.0
-observability: deprecated 9.0.0
+edot_node: ga 1.0+
+elasticsearch: preview 9.0+
+security: removed 9.0
+observability: deprecated 9.0+
```
:::::
diff --git a/docs/testing/req.md b/docs/testing/req.md
index 95907215f..21a16a3ba 100644
--- a/docs/testing/req.md
+++ b/docs/testing/req.md
@@ -8,24 +8,102 @@ mapped_pages:
---
# Requirements
+This page demonstrates various `applies_to` version syntax examples.
+
+## Version specifier examples
+
+### Greater than or equal (default)
+
+```{applies_to}
+stack: ga 9.0
+```
+
+This is equivalent to `ga 9.0+` — the feature is available from version 9.0 onwards.
+
+### Explicit range
+
+```{applies_to}
+stack: beta 9.0-9.1, ga 9.2
+```
+
+The feature was in beta from 9.0 to 9.1 (inclusive), then became GA in 9.2+.
+
+### Exact version
+
+```{applies_to}
+stack: preview =9.0, ga 9.1
+```
+
+The feature was in preview only in version 9.0 (exactly), then became GA in 9.1+.
+
+## Implicit version inference examples
+
+### Simple two-stage lifecycle
+
```{applies_to}
stack: preview 9.0, ga 9.1
```
-1. Select **Create** to create a new policy, or select **Edit** {icon}`pencil` to open an existing policy.
-1. Select **Create** to create a new policy, or select **Edit** {icon}`logo_vulnerability_management` to open an existing policy.
+Interpreted as: `preview =9.0` (exact), `ga 9.1+` (open-ended).
+### Multi-stage lifecycle with consecutive versions
-{applies_to}`stack: preview 9.0` This tutorial is based on Elasticsearch 9.0.
-This tutorial is based on Elasticsearch 9.0. This tutorial is based on Elasticsearch 9.0.
-This tutorial is based on Elasticsearch 9.0.
+```{applies_to}
+stack: preview 9.0, beta 9.1, ga 9.2
+```
-what
+Interpreted as: `preview =9.0`, `beta =9.1`, `ga 9.2+`.
+### Multi-stage lifecycle with gaps
-To follow this tutorial you will need to install the following components:
+```{applies_to}
+stack: unavailable 9.0, beta 9.1, preview 9.2, ga 9.4
+```
+Interpreted as: `unavailable =9.0`, `beta =9.1`, `preview 9.2-9.3` (range to fill the gap), `ga 9.4+`.
+### Three stages with varying gaps
+
+```{applies_to}
+stack: preview 8.0, beta 9.0, ga 9.2
+```
+
+Interpreted as: `preview 8.0-8.19`, `beta 9.0-9.1`, `ga 9.2+`.
+
+## Inline examples
+
+{applies_to}`stack: preview 9.0` This feature is in preview in 9.0.
+
+{applies_to}`stack: beta 9.0-9.1` This feature was in beta from 9.0 to 9.1.
+
+{applies_to}`stack: ga 9.2+` This feature is generally available since 9.2.
+
+{applies_to}`stack: preview =9.0` This feature was in preview only in 9.0 (exact).
+
+## Deprecation and removal examples
+
+```{applies_to}
+stack: deprecated 9.2, removed 9.5
+```
+
+Interpreted as: `deprecated 9.2-9.4`, `removed 9.5+`.
+
+{applies_to}`stack: deprecated 9.0` This feature is deprecated starting in 9.0.
+
+{applies_to}`stack: removed 9.2` This feature was removed in 9.2.
+
+## Mixed deployment examples
+
+```{applies_to}
+stack: ga 9.0
+deployment:
+ ece: ga 4.0
+ eck: beta 3.0, ga 3.1
+```
+
+## Additional content
+
+To follow this tutorial you will need to install the following components:
- An installation of Elasticsearch, based on our hosted [Elastic Cloud](https://www.elastic.co/cloud) service (which includes a free trial period), or a self-hosted service that you run on your own computer. See the Install Elasticsearch section above for installation instructions.
- A [Python](https://python.org) interpreter. Make sure it is a recent version, such as Python 3.8 or newer.
@@ -36,5 +114,4 @@ The tutorial assumes that you have no previous knowledge of Elasticsearch or gen
- The [Flask](https://flask.palletsprojects.com/) web framework for Python.
- The command prompt or terminal application in your operating system.
-
{applies_to}`ece: removed`
diff --git a/src/Elastic.ApiExplorer/Elasticsearch/OpenApiDocumentExporter.cs b/src/Elastic.ApiExplorer/Elasticsearch/OpenApiDocumentExporter.cs
index b2c82a1ec..d94f5ca7c 100644
--- a/src/Elastic.ApiExplorer/Elasticsearch/OpenApiDocumentExporter.cs
+++ b/src/Elastic.ApiExplorer/Elasticsearch/OpenApiDocumentExporter.cs
@@ -209,7 +209,7 @@ private bool ShouldIncludeOperation(OpenApiOperation operation, string product)
return true; // Could not parse version, safe to include
// Get current version for the product
- var versioningSystemId = product == "elasticsearch"
+ var versioningSystemId = product.Equals("elasticsearch", StringComparison.OrdinalIgnoreCase)
? VersioningSystemId.Stack
: VersioningSystemId.Stack; // Both use Stack for now
@@ -294,14 +294,14 @@ private static ProductLifecycle ParseLifecycle(string stateValue)
///
/// Parses the version from "Added in X.Y.Z" pattern in the x-state string.
///
- private static SemVersion? ParseVersion(string stateValue)
+ private static VersionSpec? ParseVersion(string stateValue)
{
var match = AddedInVersionRegex().Match(stateValue);
if (!match.Success)
return null;
var versionString = match.Groups[1].Value;
- return SemVersion.TryParse(versionString, out var version) ? version : null;
+ return VersionSpec.TryParse(versionString, out var version) ? version : null;
}
///
diff --git a/src/Elastic.Documentation/AppliesTo/Applicability.cs b/src/Elastic.Documentation/AppliesTo/Applicability.cs
index b91fdb991..0c8ae9d4a 100644
--- a/src/Elastic.Documentation/AppliesTo/Applicability.cs
+++ b/src/Elastic.Documentation/AppliesTo/Applicability.cs
@@ -37,13 +37,88 @@ public static bool TryParse(string? value, IList<(Severity, string)> diagnostics
if (applications.Count == 0)
return false;
+ // Infer version semantics when multiple items have GreaterThanOrEqual versions
+ applications = InferVersionSemantics(applications);
+
// Sort by version in descending order (the highest version first)
- // Items without versions (AllVersions.Instance) are sorted last
+ // Items without versions (AllVersionsSpec.Instance) are sorted last
var sortedApplications = applications.OrderDescending().ToArray();
availability = new AppliesCollection(sortedApplications);
return true;
}
+ ///
+ /// Infers versioning semantics according to the following ruleset:
+ /// - The highest version keeps GreaterThanOrEqual (e.g., 9.4+)
+ /// - Lower versions become Exact if consecutive, or Range to fill gaps
+ /// - This rule only applies when all versions are at minor level (patch = 0).
+ ///
+ private static List InferVersionSemantics(List applications)
+ {
+ // Get items with actual GreaterThanOrEqual versions (not AllVersionsSpec, not null, not ranges/exact)
+ var gteItems = applications
+ .Where(a => a.Version is { Kind: VersionSpecKind.GreaterThanOrEqual }
+ && a.Version != AllVersionsSpec.Instance)
+ .ToList();
+
+ // If 0 or 1 GTE items, no inference needed
+ if (gteItems.Count <= 1)
+ return applications;
+
+ // Only apply inference when all entries are on patch version 0
+ if (gteItems.Any(a => a.Version!.Min.Patch != 0))
+ return applications;
+
+ // Sort GTE items by version ascending to process from lowest to highest
+ var sortedGteVersions = gteItems
+ .Select(a => a.Version!.Min)
+ .Distinct()
+ .OrderBy(v => v)
+ .ToList();
+
+ if (sortedGteVersions.Count <= 1)
+ return applications;
+
+ var versionMapping = new Dictionary();
+
+ for (var i = 0; i < sortedGteVersions.Count; i++)
+ {
+ var currentVersion = sortedGteVersions[i];
+
+ if (i == sortedGteVersions.Count - 1)
+ {
+ // Highest version keeps GreaterThanOrEqual
+ versionMapping[currentVersion] = VersionSpec.GreaterThanOrEqual(currentVersion);
+ }
+ else
+ {
+ var nextVersion = sortedGteVersions[i + 1];
+
+ // Define an Exact or Range VersionSpec according to the numeric difference between lifecycles
+ if (currentVersion.Major == nextVersion.Major
+ && nextVersion.Minor == currentVersion.Minor + 1)
+ versionMapping[currentVersion] = VersionSpec.Exact(currentVersion);
+ else
+ {
+ var rangeEnd = new SemVersion(nextVersion.Major, nextVersion.Minor - 1, 0);
+ versionMapping[currentVersion] = VersionSpec.Range(currentVersion, rangeEnd);
+ }
+ }
+ }
+
+ // Apply the mapping to create updated applications
+ return applications.Select(a =>
+ {
+ if (a.Version is null || a.Version == AllVersionsSpec.Instance || a is not { Version.Kind: VersionSpecKind.GreaterThanOrEqual })
+ return a;
+
+ if (versionMapping.TryGetValue(a.Version.Min, out var newSpec))
+ return a with { Version = newSpec };
+
+ return a;
+ }).ToList();
+ }
+
public virtual bool Equals(AppliesCollection? other)
{
if ((object)this == other)
@@ -98,12 +173,12 @@ public override string ToString()
public record Applicability : IComparable, IComparable
{
public ProductLifecycle Lifecycle { get; init; }
- public SemVersion? Version { get; init; }
+ public VersionSpec? Version { get; init; }
public static Applicability GenerallyAvailable { get; } = new()
{
Lifecycle = ProductLifecycle.GenerallyAvailable,
- Version = AllVersions.Instance
+ Version = AllVersionsSpec.Instance
};
@@ -126,8 +201,8 @@ public string GetLifeCycleName() =>
///
public int CompareTo(Applicability? other)
{
- var xIsNonVersioned = Version is null || ReferenceEquals(Version, AllVersions.Instance);
- var yIsNonVersioned = other?.Version is null || ReferenceEquals(other.Version, AllVersions.Instance);
+ var xIsNonVersioned = Version is null || ReferenceEquals(Version, AllVersionsSpec.Instance);
+ var yIsNonVersioned = other?.Version is null || ReferenceEquals(other.Version, AllVersionsSpec.Instance);
if (xIsNonVersioned && yIsNonVersioned)
return 0;
@@ -158,7 +233,7 @@ public override string ToString()
_ => throw new ArgumentOutOfRangeException()
};
_ = sb.Append(lifecycle);
- if (Version is not null && Version != AllVersions.Instance)
+ if (Version is not null && Version != AllVersionsSpec.Instance)
_ = sb.Append(' ').Append(Version);
return sb.ToString();
}
@@ -224,10 +299,10 @@ public static bool TryParse(string? value, IList<(Severity, string)> diagnostics
? null
: tokens[1] switch
{
- null => AllVersions.Instance,
- "all" => AllVersions.Instance,
- "" => AllVersions.Instance,
- var t => SemVersionConverter.TryParse(t, out var v) ? v : null
+ null => AllVersionsSpec.Instance,
+ "all" => AllVersionsSpec.Instance,
+ "" => AllVersionsSpec.Instance,
+ var t => VersionSpec.TryParse(t, out var v) ? v : null
};
availability = new Applicability { Version = version, Lifecycle = lifecycle };
return true;
diff --git a/src/Elastic.Documentation/AppliesTo/ApplicabilitySelector.cs b/src/Elastic.Documentation/AppliesTo/ApplicabilitySelector.cs
index cb881fbf6..4dcc98494 100644
--- a/src/Elastic.Documentation/AppliesTo/ApplicabilitySelector.cs
+++ b/src/Elastic.Documentation/AppliesTo/ApplicabilitySelector.cs
@@ -30,25 +30,25 @@ public static Applicability GetPrimaryApplicability(IEnumerable a
};
var availableApplicabilities = applicabilityList
- .Where(a => a.Version is null || a.Version is AllVersions || a.Version <= currentVersion)
+ .Where(a => a.Version is null || a.Version is AllVersionsSpec || a.Version.Min <= currentVersion)
.ToList();
if (availableApplicabilities.Count != 0)
{
return availableApplicabilities
- .OrderByDescending(a => a.Version ?? new SemVersion(0, 0, 0))
+ .OrderByDescending(a => a.Version?.Min ?? new SemVersion(0, 0, 0))
.ThenBy(a => lifecycleOrder.GetValueOrDefault(a.Lifecycle, 999))
.First();
}
var futureApplicabilities = applicabilityList
- .Where(a => a.Version is not null && a.Version is not AllVersions && a.Version > currentVersion)
+ .Where(a => a.Version is not null && a.Version is not AllVersionsSpec && a.Version.Min > currentVersion)
.ToList();
if (futureApplicabilities.Count != 0)
{
return futureApplicabilities
- .OrderBy(a => a.Version!.CompareTo(currentVersion))
+ .OrderBy(a => a.Version!.Min.CompareTo(currentVersion))
.ThenBy(a => lifecycleOrder.GetValueOrDefault(a.Lifecycle, 999))
.First();
}
diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableTo.cs b/src/Elastic.Documentation/AppliesTo/ApplicableTo.cs
index 5889a9964..94ff54d4c 100644
--- a/src/Elastic.Documentation/AppliesTo/ApplicableTo.cs
+++ b/src/Elastic.Documentation/AppliesTo/ApplicableTo.cs
@@ -64,9 +64,11 @@ public record ApplicableTo
Product = AppliesCollection.GenerallyAvailable
};
+ private static readonly VersionSpec DefaultVersion = VersionSpec.TryParse("9.0", out var v) ? v! : AllVersionsSpec.Instance;
+
public static ApplicableTo Default { get; } = new()
{
- Stack = new AppliesCollection([new Applicability { Version = new SemVersion(9, 0, 0), Lifecycle = ProductLifecycle.GenerallyAvailable }]),
+ Stack = new AppliesCollection([new Applicability { Version = DefaultVersion, Lifecycle = ProductLifecycle.GenerallyAvailable }]),
Serverless = ServerlessProjectApplicability.All
};
diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs b/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs
index d3779e525..c8d987064 100644
--- a/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs
+++ b/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs
@@ -44,7 +44,7 @@ public class ApplicableToJsonConverter : JsonConverter
string? type = null;
string? subType = null;
var lifecycle = ProductLifecycle.GenerallyAvailable;
- SemVersion? version = null;
+ VersionSpec? version = null;
while (reader.Read())
{
@@ -72,8 +72,14 @@ public class ApplicableToJsonConverter : JsonConverter
break;
case "version":
var versionStr = reader.GetString();
- if (versionStr != null && SemVersionConverter.TryParse(versionStr, out var v))
- version = v;
+ if (versionStr != null)
+ {
+ // Handle "all" explicitly for AllVersionsSpec
+ if (string.Equals(versionStr.Trim(), "all", StringComparison.OrdinalIgnoreCase))
+ version = AllVersionsSpec.Instance;
+ else if (VersionSpec.TryParse(versionStr, out var v))
+ version = v;
+ }
break;
}
}
diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs b/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs
index 1017207e1..d86e06541 100644
--- a/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs
+++ b/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs
@@ -256,10 +256,100 @@ private static bool TryGetApplicabilityOverTime(Dictionary