141 lines
5.4 KiB
C#
141 lines
5.4 KiB
C#
using System.Text.Json;
|
|
|
|
namespace Microsoft.SelfService.Portal.Core.API.JsonDocuments
|
|
{
|
|
public static class ConfigurationDocumentValidator
|
|
{
|
|
public const string CurrentSchemaVersion = "1.0";
|
|
|
|
private static readonly HashSet<string> SupportedSchemaVersions = new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
CurrentSchemaVersion
|
|
};
|
|
|
|
private static readonly HashSet<string> SupportedTemplateTypes = new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
"Environment",
|
|
"Domain",
|
|
"Landscape",
|
|
"Service",
|
|
"Stage",
|
|
"Deployment"
|
|
};
|
|
|
|
public static ConfigurationDocumentValidationResult NormalizeAndValidate(
|
|
string? jsonData,
|
|
ConfigurationDocumentKind kind,
|
|
string? expectedSchemaVersion = null)
|
|
{
|
|
var source = string.IsNullOrWhiteSpace(jsonData) ? "{}" : jsonData;
|
|
|
|
using var document = Parse(source);
|
|
var root = document.RootElement;
|
|
|
|
if (root.ValueKind != JsonValueKind.Object)
|
|
{
|
|
throw new ConfigurationDocumentValidationException("Configuration data must be a JSON object.");
|
|
}
|
|
|
|
return kind switch
|
|
{
|
|
ConfigurationDocumentKind.Template => ValidateTemplate(root, expectedSchemaVersion),
|
|
ConfigurationDocumentKind.DeploymentOverride => ValidateDeploymentOverride(root),
|
|
ConfigurationDocumentKind.Metadata => ValidateMetadata(root),
|
|
_ => throw new ConfigurationDocumentValidationException($"Unsupported configuration document kind [{kind}].")
|
|
};
|
|
}
|
|
|
|
public static string NormalizeObject(string? jsonData)
|
|
{
|
|
return NormalizeAndValidate(jsonData, ConfigurationDocumentKind.Metadata).JsonData;
|
|
}
|
|
|
|
private static ConfigurationDocumentValidationResult ValidateTemplate(JsonElement root, string? expectedSchemaVersion)
|
|
{
|
|
var schemaVersion = GetRequiredString(root, "schemaVersion");
|
|
if (!SupportedSchemaVersions.Contains(schemaVersion))
|
|
{
|
|
throw new ConfigurationDocumentValidationException($"Unsupported schemaVersion [{schemaVersion}].");
|
|
}
|
|
|
|
if (!string.IsNullOrWhiteSpace(expectedSchemaVersion)
|
|
&& !string.Equals(schemaVersion, expectedSchemaVersion, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
throw new ConfigurationDocumentValidationException($"Document schemaVersion [{schemaVersion}] does not match requested schemaVersion [{expectedSchemaVersion}].");
|
|
}
|
|
|
|
var templateType = GetRequiredString(root, "templateType");
|
|
if (!SupportedTemplateTypes.Contains(templateType))
|
|
{
|
|
throw new ConfigurationDocumentValidationException($"Unsupported templateType [{templateType}].");
|
|
}
|
|
|
|
RequireObject(root, "parameters");
|
|
RequireObject(root, "variables");
|
|
RequireObject(root, "resources");
|
|
|
|
return new ConfigurationDocumentValidationResult(root.GetRawText(), schemaVersion);
|
|
}
|
|
|
|
private static ConfigurationDocumentValidationResult ValidateDeploymentOverride(JsonElement root)
|
|
{
|
|
var schemaVersion = TryGetString(root, "schemaVersion") ?? CurrentSchemaVersion;
|
|
if (!SupportedSchemaVersions.Contains(schemaVersion))
|
|
{
|
|
throw new ConfigurationDocumentValidationException($"Unsupported deployment override schemaVersion [{schemaVersion}].");
|
|
}
|
|
|
|
return new ConfigurationDocumentValidationResult(root.GetRawText(), schemaVersion);
|
|
}
|
|
|
|
private static ConfigurationDocumentValidationResult ValidateMetadata(JsonElement root)
|
|
{
|
|
return new ConfigurationDocumentValidationResult(root.GetRawText(), TryGetString(root, "schemaVersion") ?? CurrentSchemaVersion);
|
|
}
|
|
|
|
private static JsonDocument Parse(string jsonData)
|
|
{
|
|
try
|
|
{
|
|
return JsonDocument.Parse(jsonData);
|
|
}
|
|
catch (JsonException ex)
|
|
{
|
|
throw new ConfigurationDocumentValidationException($"Invalid JSON document: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
private static string GetRequiredString(JsonElement root, string propertyName)
|
|
{
|
|
var value = TryGetString(root, propertyName);
|
|
if (string.IsNullOrWhiteSpace(value))
|
|
{
|
|
throw new ConfigurationDocumentValidationException($"Configuration data requires string property [{propertyName}].");
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
private static string? TryGetString(JsonElement root, string propertyName)
|
|
{
|
|
if (!root.TryGetProperty(propertyName, out var property))
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return property.ValueKind == JsonValueKind.String
|
|
? property.GetString()
|
|
: null;
|
|
}
|
|
|
|
private static void RequireObject(JsonElement root, string propertyName)
|
|
{
|
|
if (!root.TryGetProperty(propertyName, out var property) || property.ValueKind != JsonValueKind.Object)
|
|
{
|
|
throw new ConfigurationDocumentValidationException($"Configuration data requires object property [{propertyName}].");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|