Files
Microsoft.SelfService.Porta…/JsonDocuments/ConfigurationDocumentValidator.cs

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}].");
}
}
}
}