using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Mvc; using StellaOps.Policy; using StellaOps.Policy.Engine.Services; using System.Collections.Immutable; namespace StellaOps.Policy.Engine.Endpoints; internal static class PolicyCompilationEndpoints { private const string CompileRoute = "/api/policy/policies/{policyId}/versions/{version}:compile"; public static IEndpointRouteBuilder MapPolicyCompilation(this IEndpointRouteBuilder endpoints) { endpoints.MapPost(CompileRoute, CompilePolicy) .WithName("CompilePolicy") .WithSummary("Compile and lint a policy DSL document.") .WithDescription("Compiles a stella-dsl@1 policy document and returns deterministic digest and statistics.") .Produces(StatusCodes.Status200OK) .Produces(StatusCodes.Status400BadRequest) .RequireAuthorization(); // scopes enforced by policy middleware. return endpoints; } private static IResult CompilePolicy( [FromRoute] string policyId, [FromRoute] int version, [FromBody] PolicyCompileRequest request, PolicyCompilationService compilationService) { if (request is null) { return Results.BadRequest(BuildProblem("ERR_POL_001", "Request body missing.", policyId, version)); } var result = compilationService.Compile(request); if (!result.Success) { return Results.BadRequest(BuildProblem("ERR_POL_001", "Policy compilation failed.", policyId, version, result.Diagnostics)); } var response = new PolicyCompileResponse( result.Digest!, result.Statistics ?? new PolicyCompilationStatistics(0, ImmutableDictionary.Empty), ConvertDiagnostics(result.Diagnostics)); return Results.Ok(response); } private static PolicyProblemDetails BuildProblem(string code, string message, string policyId, int version, ImmutableArray? diagnostics = null) { var problem = new PolicyProblemDetails { Code = code, Title = "Policy compilation error", Detail = message, PolicyId = policyId, PolicyVersion = version }; if (diagnostics is { Length: > 0 } diag) { problem.Diagnostics = diag; } return problem; } private static ImmutableArray ConvertDiagnostics(ImmutableArray issues) { if (issues.IsDefaultOrEmpty) { return ImmutableArray.Empty; } var builder = ImmutableArray.CreateBuilder(issues.Length); foreach (var issue in issues) { if (issue.Severity != PolicyIssueSeverity.Warning) { continue; } builder.Add(new PolicyDiagnosticDto(issue.Code, issue.Message, issue.Path)); } return builder.ToImmutable(); } private sealed class PolicyProblemDetails : ProblemDetails { public string Code { get; set; } = "ERR_POL_001"; public string? PolicyId { get; set; } public int PolicyVersion { get; set; } public ImmutableArray Diagnostics { get; set; } = ImmutableArray.Empty; } } internal sealed record PolicyCompileResponse( string Digest, PolicyCompilationStatistics Statistics, ImmutableArray Warnings); internal sealed record PolicyDiagnosticDto(string Code, string Message, string Path);