Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
6752bb0
Add schema version validation and log a warning, also copying dup of …
bartizan May 20, 2025
118613a
Add schema version validation for devproxy config
bartizan May 20, 2025
a9665f7
Add schema version validation for plugin section config
bartizan May 20, 2025
1e58d30
Add schema version validation for plugin config
bartizan May 20, 2025
a663ef8
Remove CompareSemVer dup and refactor a bit
bartizan May 20, 2025
6643de6
Merge branch 'main' into 525_warn-on-mismatch-between-app-and-schema-…
waldekmastykarz May 20, 2025
42c7276
Merge branch 'main' into 525_warn-on-mismatch-between-app-and-schema-…
waldekmastykarz May 22, 2025
ff27598
Fix build and adjust format
bartizan May 22, 2025
e61958b
Remove pre-release suffix from scheme version if any
bartizan May 22, 2025
4b87396
Refactor GetVersionString(), rename to NormalizeVersion
bartizan May 22, 2025
f3b5e05
Merge branch 'main' into 525_warn-on-mismatch-between-app-and-schema-…
waldekmastykarz May 22, 2025
0db071c
Merge branch 'main' into 525_warn-on-mismatch-between-app-and-schema-…
waldekmastykarz May 23, 2025
071c7f1
Remove redundant null-coalescing assignment
bartizan May 27, 2025
dad1c2d
Merge branch 'main' into 525_warn-on-mismatch-between-app-and-schema-…
waldekmastykarz May 30, 2025
0adebab
Layout
waldekmastykarz May 30, 2025
5628a97
Fix regression, notify to update a beta version if there is a release…
bartizan May 31, 2025
a04a7a8
Fix to compare normalized scheme versions and avoid false-positive sc…
bartizan May 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions dev-proxy-abstractions/BaseLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ private async Task<bool> ValidateFileContents(string fileContents)
using var document = JsonDocument.Parse(fileContents, ProxyUtils.JsonDocumentOptions);
var root = document.RootElement;

if (!root.TryGetProperty("$schema", out var schemaUrl))
if (!root.TryGetProperty("$schema", out var schemaUrlElement))
{
_logger.LogDebug("Schema reference not found in file {File}. Skipping schema validation", FilePath);
return true;
}

var (IsValid, ValidationErrors) = await ProxyUtils.ValidateJson(fileContents, schemaUrl.GetString(), _logger);
var schemaUrl = schemaUrlElement.GetString() ?? "";
ProxyUtils.ValidateSchemaVersion(schemaUrl, _logger);
var (IsValid, ValidationErrors) = await ProxyUtils.ValidateJson(fileContents, schemaUrl, _logger);

if (!IsValid)
{
_logger.LogError("Schema validation failed for {File} with the following errors: {Errors}", FilePath, string.Join(", ", ValidationErrors));
Expand Down
1 change: 1 addition & 0 deletions dev-proxy-abstractions/BaseProxyPlugin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public virtual async Task RegisterAsync()
return (false, [string.Format(CultureInfo.InvariantCulture, "Configuration section {0} not found in configuration file", configSectionName)]);
}

ProxyUtils.ValidateSchemaVersion(schemaUrl, Logger);
return await ProxyUtils.ValidateJson(configSection.GetRawText(), schemaUrl, Logger);
}
catch (Exception ex)
Expand Down
130 changes: 123 additions & 7 deletions dev-proxy-abstractions/ProxyUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,6 @@ public static string ProductVersion
}
}


return _productVersion;
}
}
Expand Down Expand Up @@ -355,7 +354,7 @@ public static void MergeHeaders(IList<MockResponseHeader> allHeaders, IList<Mock
}

public static JsonSerializerOptions JsonSerializerOptions => jsonSerializerOptions;

public static JsonDocumentOptions JsonDocumentOptions { get; } = new()
{
AllowTrailingCommas = true,
Expand Down Expand Up @@ -425,7 +424,8 @@ public static List<string> GetWildcardPatterns(List<string> urls)
}

// For multiple URLs, find the common prefix
var paths = group.Select(url => {
var paths = group.Select(url =>
{
if (url.Contains('*'))
{
return url;
Expand Down Expand Up @@ -518,10 +518,126 @@ public static string ReplaceVariables(string s, Dictionary<string, string> varia
return s1;
}

public static string GetVersionString(string productVersion)
public static void ValidateSchemaVersion(string schemaUrl, ILogger logger)
{
Comment thread
waldekmastykarz marked this conversation as resolved.
if (string.IsNullOrWhiteSpace(schemaUrl))
{
logger.LogDebug("Schema is empty, skipping schema version validation.");
return;
}

try
{
var uri = new Uri(schemaUrl);
if (uri.Segments.Length > 2)
{
var schemaVersion = uri.Segments[^2]
.TrimStart('v')
.TrimEnd('/');
var currentVersion = NormalizeVersion(ProductVersion);
if (CompareSemVer(currentVersion, schemaVersion) != 0)
{
var currentSchemaUrl = uri.ToString().Replace($"/v{schemaVersion}/", $"/v{currentVersion}/");
logger.LogWarning("The version of schema does not match the installed Dev Proxy version, the expected schema is {schema}", currentSchemaUrl);
}
}
else
{
logger.LogDebug("Invalid schema {schemaUrl}, skipping schema version validation.", schemaUrl);
}
}
catch (Exception ex)
{
logger.LogWarning("Invalid schema {schemaUrl}, skipping schema version validation. Error: {error}", schemaUrl, ex.Message);
}
}

/// <summary>
/// Compares two semantic versions strings.
/// </summary>
/// <param name="a">ver1</param>
/// <param name="b">ver2</param>
/// <returns>
/// Returns 0 if the versions are equal, -1 if a is less than b, and 1 if a is greater than b.
/// An invalid argument is "rounded" to a minimal version.
/// </returns>
public static int CompareSemVer(string? a, string? b)
{
if (string.IsNullOrWhiteSpace(a) && string.IsNullOrWhiteSpace(b))
{
return 0;
}
else if (string.IsNullOrWhiteSpace(a))
{
return -1;
}
else if (string.IsNullOrWhiteSpace(b))
{
return 1;
}

a = a.TrimStart('v');
b = b.TrimStart('v');

var aParts = a.Split('-');
var bParts = b.Split('-');

var aParsed = Version.TryParse(aParts[0], out var aVersion);
var bParsed = Version.TryParse(bParts[0], out var bVersion);
if (!aParsed && !bParsed)
{
return 0;
}
else if (!aParsed)
{
return -1;
}
else if (!bParsed)
{
return 1;
}

var compare = aVersion!.CompareTo(bVersion);
if (compare != 0)
{
// if the versions are different, return the comparison result
return compare;
}

// if the versions are the same, compare the prerelease tags
if (aParts.Length == 1 && bParts.Length == 1)
{
// if both versions are stable, they are equal
return 0;
}
else if (aParts.Length == 1)
{
// if a is stable and b is not, a is greater
return 1;
}
else if (bParts.Length == 1)
{
// if b is stable and a is not, b is greater
return -1;
}
else if (aParts[1] == bParts[1])
{
// if both versions are prerelease and the tags are the same, they are equal
return 0;
}
else
{
// if both versions are prerelease, b is greater
return -1;
}
}

/// <summary>
/// Produces major.minor.patch version dropping a pre-release suffix.
/// </summary>
/// <param name="version">A version looks like "0.28.1", "0.28.1-alpha", "0.28.10-beta.1", "0.28.10-rc.1", or "0.28.0-preview-1", etc.</param>
public static string NormalizeVersion(string version)
Comment thread
waldekmastykarz marked this conversation as resolved.
{
return productVersion.Contains("-beta")
? productVersion.Split("-")[0]
: productVersion;
return version.Split('-', StringSplitOptions.None)[0];
}
}
2 changes: 1 addition & 1 deletion dev-proxy/CommandHandlers/ConfigNewCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class VisualStudioCodeSnippet

public static class ConfigNewCommandHandler
{
private static readonly string snippetsFileUrl = $"https://aka.ms/devproxy/snippets/v{ProxyUtils.GetVersionString(ProxyUtils.ProductVersion)}";
private static readonly string snippetsFileUrl = $"https://aka.ms/devproxy/snippets/v{ProxyUtils.NormalizeVersion(ProxyUtils.ProductVersion)}";
private static readonly string configFileSnippetName = "ConfigFile";

public static async Task CreateConfigFileAsync(string name, ILogger logger)
Expand Down
10 changes: 10 additions & 0 deletions dev-proxy/PluginLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,16 @@ private PluginConfig PluginConfig
{
if (_pluginConfig == null)
{
var schemaUrl = Configuration.GetValue<string>("$schema");
if (string.IsNullOrWhiteSpace(schemaUrl))
{
_logger.LogDebug("No schema URL found in configuration file, skipping schema version validation");
}
else
{
ProxyUtils.ValidateSchemaVersion(schemaUrl, _logger);
}

_pluginConfig = new PluginConfig();

if (isDiscover)
Expand Down
72 changes: 1 addition & 71 deletions dev-proxy/UpdateNotification.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ internal static class UpdateNotification
// -1 = latest release is greater
// 0 = versions are equal
// 1 = current version is greater
if (CompareSemVer(currentVersion, latestReleaseVersion) < 0)
if (ProxyUtils.CompareSemVer(currentVersion, latestReleaseVersion) < 0)
{
return latestRelease;
}
Expand All @@ -60,76 +60,6 @@ internal static class UpdateNotification
}
}

/// <summary>
/// Compares two semantic versions strings.
/// </summary>
/// <param name="a">ver1</param>
/// <param name="b">ver2</param>
/// <returns>Returns 0 if the versions are equal, -1 if a is less than b, and 1 if a is greater than b.</returns>
private static int CompareSemVer(string? a, string? b)
{
if (a == null && b == null)
{
return 0;
}
else if (a == null)
{
return -1;
}
else if (b == null)
{
return 1;
}

if (a.StartsWith('v'))
{
a = a[1..];
}
if (b.StartsWith('v'))
{
b = b[1..];
}

var aParts = a.Split('-');
var bParts = b.Split('-');

var aVersion = new Version(aParts[0]);
var bVersion = new Version(bParts[0]);

var compare = aVersion.CompareTo(bVersion);
if (compare != 0)
{
// if the versions are different, return the comparison result
return compare;
}

// if the versions are the same, compare the prerelease tags
if (aParts.Length == 1 && bParts.Length == 1)
{
// if both versions are stable, they are equal
return 0;
}
else if (aParts.Length == 1)
{
// if a is stable and b is not, a is greater
return 1;
}
else if (bParts.Length == 1)
{
// if b is stable and a is not, b is greater
return -1;
}
else if (aParts[1] == bParts[1])
{
// if both versions are prerelease and the tags are the same, they are equal
return 0;
}
else {
// if both versions are prerelease, b is greater
return -1;
}
}

private static async Task<ReleaseInfo?> GetLatestReleaseAsync(ReleaseType releaseType)
{
var http = new HttpClient();
Expand Down