From 79a55b2e561585b9fd6dfbe2c2b25e013f5f6f8e Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 23 Feb 2026 13:06:23 -0300 Subject: [PATCH 1/4] Allow ech as an alias for ess --- .../AppliesTo/ApplicableToJsonConverter.cs | 2 +- .../AppliesTo/ApplicableToYamlConverter.cs | 3 +- ...ApplicableToJsonConverterRoundTripTests.cs | 34 +++++++++++++ .../Applicability/AppliesToFrontMatter.fs | 49 +++++++++++++++++++ tests/authoring/Inline/AppliesToRole.fs | 16 ++++++ 5 files changed, 102 insertions(+), 2 deletions(-) diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs b/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs index c8d987064..650326820 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs @@ -131,7 +131,7 @@ public class ApplicableToJsonConverter : JsonConverter Self = deploymentProps.TryGetValue("self", out var self) ? new AppliesCollection(self.ToArray()) : null, Ece = deploymentProps.TryGetValue("ece", out var ece) ? new AppliesCollection(ece.ToArray()) : null, Eck = deploymentProps.TryGetValue("eck", out var eck) ? new AppliesCollection(eck.ToArray()) : null, - Ess = deploymentProps.TryGetValue("ess", out var ess) ? new AppliesCollection(ess.ToArray()) : null + Ess = deploymentProps.TryGetValue("ess", out var ess) || deploymentProps.TryGetValue("ech", out ess) ? new AppliesCollection(ess.ToArray()) : null }; } diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs b/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs index b22c59ed4..ffdbb8ba9 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs @@ -15,7 +15,7 @@ public class ApplicableToYamlConverter(IReadOnlyCollection productKeys) private readonly string[] _knownKeys = [ "stack", "deployment", "serverless", "product", // Applicability categories - "ece", "eck", "ess", "self", // Deployment options + "ece", "eck", "ess", "ech", "self", // Deployment options ("ech" aliasing to "ess") "elasticsearch", "observability", "security", // Serverless flavors .. productKeys ]; @@ -151,6 +151,7 @@ private static bool TryGetDeployment(Dictionary dictionary, Lis { "ece", a => d.Ece = a }, { "eck", a => d.Eck = a }, { "ess", a => d.Ess = a }, + { "ech", a => d.Ess = a }, { "self", a => d.Self = a } }; diff --git a/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterRoundTripTests.cs b/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterRoundTripTests.cs index 1bf241171..ae2a295f5 100644 --- a/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterRoundTripTests.cs +++ b/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterRoundTripTests.cs @@ -269,6 +269,40 @@ public void RoundTripComplexAllFieldsPopulated() deserialized.ProductApplicability.ApmAgentDotnet.Should().BeEquivalentTo(original.ProductApplicability.ApmAgentDotnet); } + [Fact] + public void EchSubType_DeserializesAsEss() + { + var json = """ + [ + { "type": "deployment", "sub_type": "ech", "lifecycle": "ga", "version": "9.0.0" } + ] + """; + + var deserialized = JsonSerializer.Deserialize(json, _options); + + deserialized.Should().NotBeNull(); + deserialized!.Deployment.Should().NotBeNull(); + deserialized.Deployment!.Ess.Should().NotBeNull(); + deserialized.Deployment.Ess!.First().Lifecycle.Should().Be(ProductLifecycle.GenerallyAvailable); + deserialized.Deployment.Ess!.First().Version.Should().NotBeNull(); + } + + [Fact] + public void EchSubType_SerializesBackAsEss() + { + var json = """ + [ + { "type": "deployment", "sub_type": "ech", "lifecycle": "ga", "version": "9.0.0" } + ] + """; + + var deserialized = JsonSerializer.Deserialize(json, _options); + var reserialized = JsonSerializer.Serialize(deserialized, _options); + + reserialized.Should().Contain("\"sub_type\": \"ess\""); + reserialized.Should().NotContain("\"sub_type\": \"ech\""); + } + [Fact] public void RoundTripAllLifecycles() { diff --git a/tests/authoring/Applicability/AppliesToFrontMatter.fs b/tests/authoring/Applicability/AppliesToFrontMatter.fs index 95483372e..322c2d3ff 100644 --- a/tests/authoring/Applicability/AppliesToFrontMatter.fs +++ b/tests/authoring/Applicability/AppliesToFrontMatter.fs @@ -121,6 +121,55 @@ applies_to: ) )) +type ``parses ech as alias for ess`` () = + static let markdown = frontMatter """ +applies_to: + deployment: + ech: ga 9.0 +""" + [] + let ``apply matches expected`` () = + markdown |> appliesTo (ApplicableTo( + Deployment=DeploymentApplicability( + Ess=AppliesCollection.op_Explicit "ga 9.0" + ) + )) + +type ``parses ech with version alongside other deployment types`` () = + static let markdown = frontMatter """ +applies_to: + deployment: + ech: beta 9.1 + eck: ga 9.0 + ece: removed 9.2.0 + self: unavailable 9.3.0 +""" + [] + let ``apply matches expected`` () = + markdown |> appliesTo (ApplicableTo( + Deployment=DeploymentApplicability( + Ess=AppliesCollection.op_Explicit "beta 9.1", + Eck=AppliesCollection.op_Explicit "ga 9.0", + Ece=AppliesCollection.op_Explicit "removed 9.2.0", + Self=AppliesCollection.op_Explicit "unavailable 9.3.0" + ) + )) + +type ``parses ech at top level`` () = + static let markdown = frontMatter """ +applies_to: + ech: preview 9.1 + stack: ga 9.1 +""" + [] + let ``apply matches expected`` () = + markdown |> appliesTo (ApplicableTo( + Deployment=DeploymentApplicability( + Ess=AppliesCollection.op_Explicit "preview 9.1" + ), + Stack=AppliesCollection.op_Explicit "ga 9.1.0" + )) + type ``parses product coming DEPRECATED`` () = static let markdown = frontMatter """ applies_to: diff --git a/tests/authoring/Inline/AppliesToRole.fs b/tests/authoring/Inline/AppliesToRole.fs index 145efe67b..9d144dbd7 100644 --- a/tests/authoring/Inline/AppliesToRole.fs +++ b/tests/authoring/Inline/AppliesToRole.fs @@ -53,6 +53,22 @@ This is an inline {applies_to}`ess: preview 9.1` element. ) )) +type ``parses nested ech moniker as ess`` () = + static let markdown = Setup.Markdown """ + +This is an inline {applies_to}`ech: preview 9.1` element. +""" + + [] + let ``parses to AppliesDirective`` () = + let directives = markdown |> converts "index.md" |> parses + test <@ directives.Length = 1 @> + directives |> appliesToDirective (ApplicableTo( + Deployment=DeploymentApplicability( + Ess=AppliesCollection.op_Explicit "preview 9.1.0" + ) + )) + type ``parses {preview} shortcut`` () = static let markdown = Setup.Markdown """ From 0a30a9ddf779ef1adc9661a6fe8daf4755dbc6cf Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 23 Feb 2026 13:22:26 -0300 Subject: [PATCH 2/4] Add tests to confirm functionality when both ess and ech are defined --- .../ApplicableToJsonConverterRoundTripTests.cs | 18 ++++++++++++++++++ .../Applicability/AppliesToFrontMatter.fs | 15 +++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterRoundTripTests.cs b/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterRoundTripTests.cs index ae2a295f5..de2cadfff 100644 --- a/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterRoundTripTests.cs +++ b/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterRoundTripTests.cs @@ -303,6 +303,24 @@ public void EchSubType_SerializesBackAsEss() reserialized.Should().NotContain("\"sub_type\": \"ech\""); } + [Fact] + public void BothEssAndEchSubTypes_EssWins() + { + var json = """ + [ + { "type": "deployment", "sub_type": "ess", "lifecycle": "ga", "version": "9.0.0" }, + { "type": "deployment", "sub_type": "ech", "lifecycle": "beta", "version": "9.1.0" } + ] + """; + + var deserialized = JsonSerializer.Deserialize(json, _options); + + deserialized.Should().NotBeNull(); + deserialized!.Deployment.Should().NotBeNull(); + deserialized.Deployment!.Ess.Should().NotBeNull(); + deserialized.Deployment.Ess!.First().Lifecycle.Should().Be(ProductLifecycle.GenerallyAvailable); + } + [Fact] public void RoundTripAllLifecycles() { diff --git a/tests/authoring/Applicability/AppliesToFrontMatter.fs b/tests/authoring/Applicability/AppliesToFrontMatter.fs index 322c2d3ff..7099557a9 100644 --- a/tests/authoring/Applicability/AppliesToFrontMatter.fs +++ b/tests/authoring/Applicability/AppliesToFrontMatter.fs @@ -155,6 +155,21 @@ applies_to: ) )) +type ``both ess and ech defined uses ech value`` () = + static let markdown = frontMatter """ +applies_to: + deployment: + ess: ga 9.0 + ech: beta 9.1 +""" + [] + let ``apply matches expected`` () = + markdown |> appliesTo (ApplicableTo( + Deployment=DeploymentApplicability( + Ess=AppliesCollection.op_Explicit "beta 9.1" + ) + )) + type ``parses ech at top level`` () = static let markdown = frontMatter """ applies_to: From 54ab498230bbe4d3897b4622daac49e07a651515 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 23 Feb 2026 13:57:32 -0300 Subject: [PATCH 3/4] Adjust tests --- .../AppliesTo/ApplicableToJsonConverter.cs | 2 +- ...ApplicableToJsonConverterRoundTripTests.cs | 39 ++++++------------- 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs b/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs index 650326820..a6dba46c4 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicableToJsonConverter.cs @@ -131,7 +131,7 @@ public class ApplicableToJsonConverter : JsonConverter Self = deploymentProps.TryGetValue("self", out var self) ? new AppliesCollection(self.ToArray()) : null, Ece = deploymentProps.TryGetValue("ece", out var ece) ? new AppliesCollection(ece.ToArray()) : null, Eck = deploymentProps.TryGetValue("eck", out var eck) ? new AppliesCollection(eck.ToArray()) : null, - Ess = deploymentProps.TryGetValue("ess", out var ess) || deploymentProps.TryGetValue("ech", out ess) ? new AppliesCollection(ess.ToArray()) : null + Ess = deploymentProps.TryGetValue("ech", out var ess) || deploymentProps.TryGetValue("ess", out ess) ? new AppliesCollection(ess.ToArray()) : null }; } diff --git a/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterRoundTripTests.cs b/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterRoundTripTests.cs index de2cadfff..190dce116 100644 --- a/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterRoundTripTests.cs +++ b/tests/Elastic.Markdown.Tests/AppliesTo/ApplicableToJsonConverterRoundTripTests.cs @@ -270,41 +270,26 @@ public void RoundTripComplexAllFieldsPopulated() } [Fact] - public void EchSubType_DeserializesAsEss() + public void RoundTripDeploymentEssRoundTripsCorrectly() { - var json = """ - [ - { "type": "deployment", "sub_type": "ech", "lifecycle": "ga", "version": "9.0.0" } - ] - """; + var original = new ApplicableTo + { + Deployment = new DeploymentApplicability + { + Ess = new AppliesCollection([new Applicability { Lifecycle = ProductLifecycle.GenerallyAvailable, Version = (VersionSpec)"9.0.0" }]) + } + }; + var json = JsonSerializer.Serialize(original, _options); var deserialized = JsonSerializer.Deserialize(json, _options); deserialized.Should().NotBeNull(); deserialized!.Deployment.Should().NotBeNull(); - deserialized.Deployment!.Ess.Should().NotBeNull(); - deserialized.Deployment.Ess!.First().Lifecycle.Should().Be(ProductLifecycle.GenerallyAvailable); - deserialized.Deployment.Ess!.First().Version.Should().NotBeNull(); - } - - [Fact] - public void EchSubType_SerializesBackAsEss() - { - var json = """ - [ - { "type": "deployment", "sub_type": "ech", "lifecycle": "ga", "version": "9.0.0" } - ] - """; - - var deserialized = JsonSerializer.Deserialize(json, _options); - var reserialized = JsonSerializer.Serialize(deserialized, _options); - - reserialized.Should().Contain("\"sub_type\": \"ess\""); - reserialized.Should().NotContain("\"sub_type\": \"ech\""); + deserialized.Deployment!.Ess.Should().BeEquivalentTo(original.Deployment!.Ess); } [Fact] - public void BothEssAndEchSubTypes_EssWins() + public void BothEssAndEchSubTypes_EchWins() { var json = """ [ @@ -318,7 +303,7 @@ public void BothEssAndEchSubTypes_EssWins() deserialized.Should().NotBeNull(); deserialized!.Deployment.Should().NotBeNull(); deserialized.Deployment!.Ess.Should().NotBeNull(); - deserialized.Deployment.Ess!.First().Lifecycle.Should().Be(ProductLifecycle.GenerallyAvailable); + deserialized.Deployment.Ess!.First().Lifecycle.Should().Be(ProductLifecycle.Beta); } [Fact] From 7c722a1e57afabe5e8fb04b4cda119e64c13a5a7 Mon Sep 17 00:00:00 2001 From: Felipe Cotti Date: Mon, 23 Feb 2026 15:37:44 -0300 Subject: [PATCH 4/4] Emit warning when both ess and ech are defined --- .../AppliesTo/ApplicableToYamlConverter.cs | 5 +++++ tests/authoring/Applicability/AppliesToFrontMatter.fs | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs b/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs index ffdbb8ba9..91fe52b57 100644 --- a/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs +++ b/src/Elastic.Documentation/AppliesTo/ApplicableToYamlConverter.cs @@ -146,6 +146,11 @@ private static bool TryGetDeployment(Dictionary dictionary, Lis var d = new DeploymentApplicability(); var assigned = false; + var hasEss = dictionary.ContainsKey("ess"); + var hasEch = dictionary.ContainsKey("ech"); + if (hasEss && hasEch) + diagnostics.Add((Severity.Warning, "Both 'ess' and 'ech' are defined. Move 'ess' content into 'ech' to avoid information loss.")); + var mapping = new Dictionary> { { "ece", a => d.Ece = a }, diff --git a/tests/authoring/Applicability/AppliesToFrontMatter.fs b/tests/authoring/Applicability/AppliesToFrontMatter.fs index 7099557a9..1b0c0c6f4 100644 --- a/tests/authoring/Applicability/AppliesToFrontMatter.fs +++ b/tests/authoring/Applicability/AppliesToFrontMatter.fs @@ -155,7 +155,7 @@ applies_to: ) )) -type ``both ess and ech defined uses ech value`` () = +type ``both ess and ech defined uses ech value and warns`` () = static let markdown = frontMatter """ applies_to: deployment: @@ -163,13 +163,17 @@ applies_to: ech: beta 9.1 """ [] - let ``apply matches expected`` () = + let ``ech value wins`` () = markdown |> appliesTo (ApplicableTo( Deployment=DeploymentApplicability( Ess=AppliesCollection.op_Explicit "beta 9.1" ) )) + [] + let ``emits warning about both being defined`` () = + markdown |> hasWarning "Both 'ess' and 'ech' are defined" + type ``parses ech at top level`` () = static let markdown = frontMatter """ applies_to: