up
This commit is contained in:
174
src/Policy/StellaOps.PolicyDsl/PolicyCompiler.cs
Normal file
174
src/Policy/StellaOps.PolicyDsl/PolicyCompiler.cs
Normal file
@@ -0,0 +1,174 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Security.Cryptography;
|
||||
using StellaOps.Policy;
|
||||
|
||||
namespace StellaOps.PolicyDsl;
|
||||
|
||||
/// <summary>
|
||||
/// Compiles policy DSL source code into an intermediate representation.
|
||||
/// </summary>
|
||||
public sealed class PolicyCompiler
|
||||
{
|
||||
public PolicyCompilationResult Compile(string source)
|
||||
{
|
||||
if (source is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(source));
|
||||
}
|
||||
|
||||
var parseResult = PolicyParser.Parse(source);
|
||||
if (parseResult.Document is null)
|
||||
{
|
||||
return new PolicyCompilationResult(
|
||||
Success: false,
|
||||
Document: null,
|
||||
Checksum: null,
|
||||
CanonicalRepresentation: ImmutableArray<byte>.Empty,
|
||||
Diagnostics: parseResult.Diagnostics);
|
||||
}
|
||||
|
||||
if (parseResult.Diagnostics.Any(static issue => issue.Severity == PolicyIssueSeverity.Error))
|
||||
{
|
||||
return new PolicyCompilationResult(
|
||||
Success: false,
|
||||
Document: null,
|
||||
Checksum: null,
|
||||
CanonicalRepresentation: ImmutableArray<byte>.Empty,
|
||||
Diagnostics: parseResult.Diagnostics);
|
||||
}
|
||||
|
||||
var irDocument = BuildIntermediateRepresentation(parseResult.Document);
|
||||
var canonical = PolicyIrSerializer.Serialize(irDocument);
|
||||
var checksum = Convert.ToHexString(SHA256.HashData(canonical.AsSpan())).ToLowerInvariant();
|
||||
|
||||
return new PolicyCompilationResult(
|
||||
Success: true,
|
||||
Document: irDocument,
|
||||
Checksum: checksum,
|
||||
CanonicalRepresentation: canonical,
|
||||
Diagnostics: parseResult.Diagnostics);
|
||||
}
|
||||
|
||||
private static PolicyIrDocument BuildIntermediateRepresentation(PolicyDocumentNode node)
|
||||
{
|
||||
var metadata = node.Metadata
|
||||
.OrderBy(static kvp => kvp.Key, StringComparer.Ordinal)
|
||||
.ToImmutableSortedDictionary(static kvp => kvp.Key, kvp => ToIrLiteral(kvp.Value), StringComparer.Ordinal);
|
||||
|
||||
var settings = node.Settings
|
||||
.OrderBy(static kvp => kvp.Key, StringComparer.Ordinal)
|
||||
.ToImmutableSortedDictionary(static kvp => kvp.Key, kvp => ToIrLiteral(kvp.Value), StringComparer.Ordinal);
|
||||
|
||||
var profiles = ImmutableArray.CreateBuilder<PolicyIrProfile>(node.Profiles.Length);
|
||||
foreach (var profile in node.Profiles)
|
||||
{
|
||||
var maps = ImmutableArray.CreateBuilder<PolicyIrProfileMap>();
|
||||
var envs = ImmutableArray.CreateBuilder<PolicyIrProfileEnv>();
|
||||
var scalars = ImmutableArray.CreateBuilder<PolicyIrProfileScalar>();
|
||||
|
||||
foreach (var item in profile.Items)
|
||||
{
|
||||
switch (item)
|
||||
{
|
||||
case PolicyProfileMapNode map:
|
||||
maps.Add(new PolicyIrProfileMap(
|
||||
map.Name,
|
||||
map.Entries
|
||||
.Select(entry => new PolicyIrProfileMapEntry(entry.Source, entry.Weight))
|
||||
.ToImmutableArray()));
|
||||
break;
|
||||
case PolicyProfileEnvNode env:
|
||||
envs.Add(new PolicyIrProfileEnv(
|
||||
env.Name,
|
||||
env.Entries
|
||||
.Select(entry => new PolicyIrProfileEnvEntry(entry.Condition, entry.Weight))
|
||||
.ToImmutableArray()));
|
||||
break;
|
||||
case PolicyProfileScalarNode scalar:
|
||||
scalars.Add(new PolicyIrProfileScalar(scalar.Name, ToIrLiteral(scalar.Value)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
profiles.Add(new PolicyIrProfile(
|
||||
profile.Name,
|
||||
maps.ToImmutable(),
|
||||
envs.ToImmutable(),
|
||||
scalars.ToImmutable()));
|
||||
}
|
||||
|
||||
var rules = ImmutableArray.CreateBuilder<PolicyIrRule>(node.Rules.Length);
|
||||
foreach (var rule in node.Rules)
|
||||
{
|
||||
var thenActions = ImmutableArray.CreateBuilder<PolicyIrAction>(rule.ThenActions.Length);
|
||||
foreach (var action in rule.ThenActions)
|
||||
{
|
||||
var converted = ToIrAction(action);
|
||||
if (converted is not null)
|
||||
{
|
||||
thenActions.Add(converted);
|
||||
}
|
||||
}
|
||||
|
||||
var elseActions = ImmutableArray.CreateBuilder<PolicyIrAction>(rule.ElseActions.Length);
|
||||
foreach (var action in rule.ElseActions)
|
||||
{
|
||||
var converted = ToIrAction(action);
|
||||
if (converted is not null)
|
||||
{
|
||||
elseActions.Add(converted);
|
||||
}
|
||||
}
|
||||
|
||||
rules.Add(new PolicyIrRule(
|
||||
rule.Name,
|
||||
rule.Priority,
|
||||
rule.When,
|
||||
thenActions.ToImmutable(),
|
||||
elseActions.ToImmutable(),
|
||||
rule.Because ?? string.Empty));
|
||||
}
|
||||
|
||||
return new PolicyIrDocument(
|
||||
node.Name,
|
||||
node.Syntax,
|
||||
metadata,
|
||||
profiles.ToImmutable(),
|
||||
settings,
|
||||
rules.ToImmutable());
|
||||
}
|
||||
|
||||
private static PolicyIrLiteral ToIrLiteral(PolicyLiteralValue value) => value switch
|
||||
{
|
||||
PolicyStringLiteral s => new PolicyIrStringLiteral(s.Value),
|
||||
PolicyNumberLiteral n => new PolicyIrNumberLiteral(n.Value),
|
||||
PolicyBooleanLiteral b => new PolicyIrBooleanLiteral(b.Value),
|
||||
PolicyListLiteral list => new PolicyIrListLiteral(list.Items.Select(ToIrLiteral).ToImmutableArray()),
|
||||
_ => new PolicyIrStringLiteral(string.Empty),
|
||||
};
|
||||
|
||||
private static PolicyIrAction? ToIrAction(PolicyActionNode action) => action switch
|
||||
{
|
||||
PolicyAssignmentActionNode assign => new PolicyIrAssignmentAction(assign.Target.Segments, assign.Value),
|
||||
PolicyAnnotateActionNode annotate => new PolicyIrAnnotateAction(annotate.Target.Segments, annotate.Value),
|
||||
PolicyIgnoreActionNode ignore => new PolicyIrIgnoreAction(ignore.Until, ignore.Because),
|
||||
PolicyEscalateActionNode escalate => new PolicyIrEscalateAction(escalate.To, escalate.When),
|
||||
PolicyRequireVexActionNode require => new PolicyIrRequireVexAction(
|
||||
require.Conditions
|
||||
.OrderBy(static kvp => kvp.Key, StringComparer.Ordinal)
|
||||
.ToImmutableSortedDictionary(static kvp => kvp.Key, kvp => kvp.Value, StringComparer.Ordinal)),
|
||||
PolicyWarnActionNode warn => new PolicyIrWarnAction(warn.Message),
|
||||
PolicyDeferActionNode defer => new PolicyIrDeferAction(defer.Until),
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Result of compiling a policy DSL source.
|
||||
/// </summary>
|
||||
public sealed record PolicyCompilationResult(
|
||||
bool Success,
|
||||
PolicyIrDocument? Document,
|
||||
string? Checksum,
|
||||
ImmutableArray<byte> CanonicalRepresentation,
|
||||
ImmutableArray<PolicyIssue> Diagnostics);
|
||||
Reference in New Issue
Block a user