From 65d88a7b0afad8976b9fa061d66cbf33d8ec178a Mon Sep 17 00:00:00 2001 From: Dieter Verfaillie Date: Sun, 10 Jan 2021 21:34:58 +0100 Subject: [PATCH] deterministically serialize number and string JSON output Ever since the introduction of the JsonOutputFormatter way back in 2014 [1] all fields where serialized as JSON string. Except for values looking like an int which where then serialized as JSON numbers. This had some strange unpredictable side effects like PreReleaseNumber being an empty JSON string instead of null or a "2009069" ShortSha being formatted as a JSON number. This change ensures all fields are serialized as JSON string except Major, Minor, Patch, PreReleaseNumber, WeightedPreReleaseNumber, CommitsSinceVersionSource and UncommittedChanges which are now always serialized as JSON number. Deserialisation remains the same as before for all fields so we continue to accept both JSON string and JSON number formatted values. Fixes #1688 and fixes #2304. [1] https://github.com/GitTools/GitVersion/commit/f2daf607a1b6593bf4ce42626e7ee49eb19c3bc4#diff-fde1a8ff593c6ac2ad558a6f9bb512e0350db91343b185f9b2a00d1d6e848bc3 --- ...sDeliveryModeForFeatureBranch.approved.txt | 2 +- ...hWithCustomAssemblyInfoFormat.approved.txt | 2 +- ...ntinuousDeliveryModeForStable.approved.txt | 2 +- ...usDeploymentModeForPreRelease.approved.txt | 2 +- ...inuousDeploymentModeForStable.approved.txt | 2 +- ...ableWhenCurrentCommitIsTagged.approved.txt | 2 +- src/GitVersionCore/Model/VersionVariables.cs | 145 +++++++++++++++++- 7 files changed, 143 insertions(+), 14 deletions(-) diff --git a/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeliveryModeForFeatureBranch.approved.txt b/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeliveryModeForFeatureBranch.approved.txt index bb5ccebdd7..0490ce34e1 100644 --- a/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeliveryModeForFeatureBranch.approved.txt +++ b/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeliveryModeForFeatureBranch.approved.txt @@ -6,7 +6,7 @@ "PreReleaseTagWithDash": "", "PreReleaseLabel": "", "PreReleaseLabelWithDash": "", - "PreReleaseNumber": "", + "PreReleaseNumber": null, "WeightedPreReleaseNumber": 0, "BuildMetaData": 5, "BuildMetaDataPadded": "0005", diff --git a/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeliveryModeForFeatureBranchWithCustomAssemblyInfoFormat.approved.txt b/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeliveryModeForFeatureBranchWithCustomAssemblyInfoFormat.approved.txt index 7b8572ee2d..f5f7edf738 100644 --- a/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeliveryModeForFeatureBranchWithCustomAssemblyInfoFormat.approved.txt +++ b/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeliveryModeForFeatureBranchWithCustomAssemblyInfoFormat.approved.txt @@ -6,7 +6,7 @@ "PreReleaseTagWithDash": "", "PreReleaseLabel": "", "PreReleaseLabelWithDash": "", - "PreReleaseNumber": "", + "PreReleaseNumber": null, "WeightedPreReleaseNumber": 0, "BuildMetaData": 5, "BuildMetaDataPadded": "0005", diff --git a/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeliveryModeForStable.approved.txt b/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeliveryModeForStable.approved.txt index 3d1a720853..51a0616f66 100644 --- a/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeliveryModeForStable.approved.txt +++ b/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeliveryModeForStable.approved.txt @@ -6,7 +6,7 @@ "PreReleaseTagWithDash": "", "PreReleaseLabel": "", "PreReleaseLabelWithDash": "", - "PreReleaseNumber": "", + "PreReleaseNumber": null, "WeightedPreReleaseNumber": 0, "BuildMetaData": 5, "BuildMetaDataPadded": "0005", diff --git a/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeploymentModeForPreRelease.approved.txt b/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeploymentModeForPreRelease.approved.txt index ceaade6c96..831febba8e 100644 --- a/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeploymentModeForPreRelease.approved.txt +++ b/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeploymentModeForPreRelease.approved.txt @@ -8,7 +8,7 @@ "PreReleaseLabelWithDash": "-unstable", "PreReleaseNumber": 8, "WeightedPreReleaseNumber": 8, - "BuildMetaData": "", + "BuildMetaData": null, "BuildMetaDataPadded": "", "FullBuildMetaData": "Branch.develop.Sha.commitSha", "MajorMinorPatch": "1.2.3", diff --git a/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeploymentModeForStable.approved.txt b/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeploymentModeForStable.approved.txt index 1988185ae7..7cc02e1bf3 100644 --- a/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeploymentModeForStable.approved.txt +++ b/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeploymentModeForStable.approved.txt @@ -8,7 +8,7 @@ "PreReleaseLabelWithDash": "-ci", "PreReleaseNumber": 5, "WeightedPreReleaseNumber": 5, - "BuildMetaData": "", + "BuildMetaData": null, "BuildMetaDataPadded": "", "FullBuildMetaData": "Branch.develop.Sha.commitSha", "MajorMinorPatch": "1.2.3", diff --git a/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeploymentModeForStableWhenCurrentCommitIsTagged.approved.txt b/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeploymentModeForStableWhenCurrentCommitIsTagged.approved.txt index b61131c38b..11b2530fef 100644 --- a/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeploymentModeForStableWhenCurrentCommitIsTagged.approved.txt +++ b/src/GitVersionCore.Tests/VersionCalculation/Approved/VariableProviderTests.ProvidesVariablesInContinuousDeploymentModeForStableWhenCurrentCommitIsTagged.approved.txt @@ -6,7 +6,7 @@ "PreReleaseTagWithDash": "", "PreReleaseLabel": "", "PreReleaseLabelWithDash": "", - "PreReleaseNumber": "", + "PreReleaseNumber": null, "WeightedPreReleaseNumber": 0, "BuildMetaData": 5, "BuildMetaDataPadded": "0005", diff --git a/src/GitVersionCore/Model/VersionVariables.cs b/src/GitVersionCore/Model/VersionVariables.cs index e085bbbda8..44b1daf194 100644 --- a/src/GitVersionCore/Model/VersionVariables.cs +++ b/src/GitVersionCore/Model/VersionVariables.cs @@ -116,8 +116,8 @@ public VersionVariables(string major, public string VersionSourceSha { get; } public string CommitsSinceVersionSource { get; } public string CommitsSinceVersionSourcePadded { get; } - public string UncommittedChanges { get; } + public string CommitDate { get; set; } [ReflectionIgnore] public static IEnumerable AvailableVariables @@ -132,8 +132,6 @@ public static IEnumerable AvailableVariables } } - public string CommitDate { get; set; } - [ReflectionIgnore] public string FileName { get; set; } @@ -165,7 +163,7 @@ public static VersionVariables FromDictionary(IEnumerable>(json, serializeOptions); return FromDictionary(variablePairs); } @@ -199,19 +197,36 @@ private static bool ContainsKey(string variable) public override string ToString() { - var variables = this.GetProperties().ToDictionary(x => x.Key, x => x.Value); + var variablesType = typeof(VersionVariablesJsonModel); + var variables = new VersionVariablesJsonModel(); + + foreach (KeyValuePair property in this.GetProperties()) + { + variablesType.GetProperty(property.Key).SetValue(variables, property.Value); + } + var serializeOptions = JsonSerializerOptions(); return JsonSerializer.Serialize(variables, serializeOptions); } + private static JsonSerializerOptions JsonDeSerializerOptions() + { + var serializeOptions = new JsonSerializerOptions + { + WriteIndented = true, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, + Converters = { new GitVersionNumberConverter() } + }; + return serializeOptions; + } + private static JsonSerializerOptions JsonSerializerOptions() { var serializeOptions = new JsonSerializerOptions { WriteIndented = true, Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, - Converters = { new GitVersionStringConverter() } }; return serializeOptions; } @@ -242,14 +257,128 @@ public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonS public override void Write(Utf8JsonWriter writer, [CanBeNull] string value, JsonSerializerOptions options) { value ??= string.Empty; - if (NotAPaddedNumber(value) && int.TryParse(value, out var number)) + writer.WriteStringValue(value); + } + + public override bool HandleNull => true; + + private static bool NotAPaddedNumber(string value) => value != null && (value == "0" || !value.StartsWith("0")); + } + + public class GitVersionNumberConverter : JsonConverter + { + public override bool CanConvert(Type typeToConvert) + => typeToConvert == typeof(string); + + public override string Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.Number && typeToConvert == typeof(string)) + return reader.GetString() ?? ""; + + var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan; + if (Utf8Parser.TryParse(span, out long number, out var bytesConsumed) && span.Length == bytesConsumed) + return number.ToString(); + + var data = reader.GetString(); + + throw new InvalidOperationException($"'{data}' is not a correct expected value!") + { + Source = nameof(GitVersionNumberConverter) + }; + } + + public override void Write(Utf8JsonWriter writer, [CanBeNull] string value, JsonSerializerOptions options) + { + if (string.IsNullOrWhiteSpace(value)) + { + writer.WriteNullValue(); + } + else if (int.TryParse(value, out var number)) + { writer.WriteNumberValue(number); + } else - writer.WriteStringValue(value); + { + throw new InvalidOperationException($"'{value}' is not a correct expected value!") + { + Source = nameof(GitVersionStringConverter) + }; + } + } public override bool HandleNull => true; private static bool NotAPaddedNumber(string value) => value != null && (value == "0" || !value.StartsWith("0")); } + + public class VersionVariablesJsonModel + { + [JsonConverter(typeof(GitVersionNumberConverter))] + public string Major { get; set; } + [JsonConverter(typeof(GitVersionNumberConverter))] + public string Minor { get; set; } + [JsonConverter(typeof(GitVersionNumberConverter))] + public string Patch { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string PreReleaseTag { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string PreReleaseTagWithDash { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string PreReleaseLabel { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string PreReleaseLabelWithDash { get; set; } + [JsonConverter(typeof(GitVersionNumberConverter))] + public string PreReleaseNumber { get; set; } + [JsonConverter(typeof(GitVersionNumberConverter))] + public string WeightedPreReleaseNumber { get; set; } + [JsonConverter(typeof(GitVersionNumberConverter))] + public string BuildMetaData { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string BuildMetaDataPadded { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string FullBuildMetaData { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string MajorMinorPatch { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string SemVer { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string LegacySemVer { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string LegacySemVerPadded { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string AssemblySemVer { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string AssemblySemFileVer { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string FullSemVer { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string InformationalVersion { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string BranchName { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string EscapedBranchName { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string Sha { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string ShortSha { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string NuGetVersionV2 { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string NuGetVersion { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string NuGetPreReleaseTagV2 { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string NuGetPreReleaseTag { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string VersionSourceSha { get; set; } + [JsonConverter(typeof(GitVersionNumberConverter))] + public string CommitsSinceVersionSource { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string CommitsSinceVersionSourcePadded { get; set; } + [JsonConverter(typeof(GitVersionNumberConverter))] + public string UncommittedChanges { get; set; } + [JsonConverter(typeof(GitVersionStringConverter))] + public string CommitDate { get; set; } + } }