File: GoApiExportWriter.cs
Web Access
Project: src\src\Aspire.Hosting.CodeGeneration.Go\Aspire.Hosting.CodeGeneration.Go.csproj (Aspire.Hosting.CodeGeneration.Go)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Text.Json;
using System.Text.Json.Nodes;
 
namespace Aspire.Hosting.CodeGeneration.Go;
 
/// <summary>
/// Serializes a <see cref="GoApiModel"/> into the canonical schema version 1 export document.
/// </summary>
/// <remarks>
/// The document is written by hand rather than through reflection-based serialization because the
/// shape is a published contract that documentation sites bind to. Writing it explicitly makes the
/// contract reviewable in one place, keeps property order stable so exports diff cleanly between SDK
/// versions, and drops empty collections and null strings so a version bump only shows real API
/// changes. The envelope (<c>schemaVersion</c>, <c>generator</c>, <c>package</c>, <c>modules</c>,
/// <c>declarations</c>) matches the TypeScript export so a consumer can read both.
/// </remarks>
internal static class GoApiExportWriter
{
    public static JsonObject Write(GoApiModel model)
    {
        ArgumentNullException.ThrowIfNull(model);
 
        var modules = new JsonArray();
        foreach (var module in model.Modules)
        {
            modules.Add((JsonNode)WriteModule(module));
        }
 
        var declarations = new JsonArray();
        foreach (var declaration in model.Declarations)
        {
            declarations.Add((JsonNode)new JsonObject
            {
                ["id"] = declaration.Id,
                ["owningAssembly"] = declaration.OwningAssemblyName,
                ["content"] = declaration.Content
            });
        }
 
        return new JsonObject
        {
            ["schemaVersion"] = model.SchemaVersion,
            ["language"] = model.Language,
            ["generator"] = new JsonObject
            {
                ["name"] = model.Generator.Name,
                ["version"] = model.Generator.Version
            },
            ["package"] = new JsonObject
            {
                ["name"] = model.Package.Name,
                ["version"] = model.Package.Version,
                ["importPath"] = model.Package.ImportPath
            },
            ["modules"] = modules,
            ["declarations"] = declarations
        };
    }
 
    /// <summary>
    /// Serializes the export document to UTF-8 JSON text.
    /// </summary>
    /// <param name="model">The model to serialize.</param>
    /// <param name="indented">
    /// When <see langword="true"/>, writes human-readable JSON. Machine consumers use the compact
    /// form so the document is a single line on stdout.
    /// </param>
    public static string WriteToJson(GoApiModel model, bool indented = false)
        => Write(model).ToJsonString(new JsonSerializerOptions { WriteIndented = indented });
 
    private static JsonObject WriteModule(GoApiModule module)
    {
        var items = new JsonArray();
        foreach (var item in module.Items)
        {
            items.Add((JsonNode)WriteItem(item));
        }
 
        var json = new JsonObject { ["name"] = module.Name };
        AddIfPresent(json, "summary", module.Summary);
        json["items"] = items;
        return json;
    }
 
    private static JsonObject WriteItem(GoApiItem item)
    {
        var json = new JsonObject
        {
            ["id"] = item.Id,
            ["kind"] = ToKindString(item.Kind),
            ["name"] = item.Name,
            ["typeId"] = item.TypeId,
            ["owningAssembly"] = item.OwningAssemblyName,
            ["declaration"] = item.Declaration
        };
 
        AddIfPresent(json, "summary", item.Summary);
        AddIfPresent(json, "remarks", item.Remarks);
        AddIfTrue(json, "marker", item.IsMarker);
        AddIfPresent(json, "implements", item.Implements);
        AddIfPresent(json, "usedBy", item.UsedBy);
 
        if (item.Members.Count > 0)
        {
            var members = new JsonArray();
            foreach (var member in item.Members)
            {
                members.Add((JsonNode)WriteMember(member));
            }
 
            json["members"] = members;
        }
 
        return json;
    }
 
    private static JsonObject WriteMember(GoApiMember member)
    {
        var json = new JsonObject
        {
            ["id"] = member.Id,
            ["kind"] = ToKindString(member.Kind),
            ["name"] = member.Name,
            ["declaration"] = member.Declaration
        };
 
        AddIfPresent(json, "capabilityId", member.CapabilityId);
        AddIfPresent(json, "targetTypeId", member.TargetTypeId);
        AddIfPresent(json, "capabilityKind", member.CapabilityKind);
        AddIfPresent(json, "owningAssembly", member.OwningAssemblyName);
        AddIfPresent(json, "returnType", member.ReturnType);
        if (member.ErrorModel is { } errorModel)
        {
            json["errorModel"] = ToErrorModelString(errorModel);
        }
 
        AddIfPresent(json, "summary", member.Summary);
        AddIfPresent(json, "remarks", member.Remarks);
        AddIfPresent(json, "returns", member.Returns);
        AddIfPresent(json, "jsonName", member.JsonName);
        AddIfPresent(json, "value", member.Value);
        AddIfTrue(json, "returnsReceiver", member.ReturnsReceiver);
        AddIfTrue(json, "returnsBuilder", member.ReturnsBuilder);
        AddIfTrue(json, "synthesized", member.IsSynthesized);
        AddIfTrue(json, "experimental", member.IsExperimental);
        if (member.DeprecationMessage is not null)
        {
            json["deprecated"] = member.DeprecationMessage;
        }
 
        if (member.Parameters.Count > 0)
        {
            json["parameters"] = WriteParameters(member.Parameters);
        }
 
        if (member.Options is { } options)
        {
            var optionsJson = new JsonObject { ["typeName"] = options.TypeName };
            AddIfTrue(optionsJson, "direct", options.IsDirect);
            if (options.Fields.Count > 0)
            {
                optionsJson["fields"] = WriteParameters(options.Fields);
            }
 
            json["options"] = optionsJson;
        }
 
        return json;
    }
 
    private static JsonArray WriteParameters(IReadOnlyList<GoApiParameter> parameters)
    {
        var array = new JsonArray();
        foreach (var parameter in parameters)
        {
            var json = new JsonObject
            {
                ["name"] = parameter.Name,
                ["type"] = parameter.DeclaredType,
                ["optional"] = parameter.IsOptional
            };
 
            AddIfTrue(json, "callback", parameter.IsCallback);
            AddIfPresent(json, "jsonName", parameter.JsonName);
            AddIfPresent(json, "summary", parameter.Summary);
            AddIfPresent(json, "allowedTypes", parameter.AllowedTypes);
            array.Add((JsonNode)json);
        }
 
        return array;
    }
 
    private static string ToKindString(GoApiItemKind kind) => kind switch
    {
        GoApiItemKind.Interface => "interface",
        GoApiItemKind.Augmentation => "augmentation",
        GoApiItemKind.Dto => "dto",
        GoApiItemKind.Options => "options",
        GoApiItemKind.Enum => "enum",
        GoApiItemKind.Variable => "variable",
        GoApiItemKind.Function => "function",
        GoApiItemKind.Type => "type",
        _ => throw new ArgumentOutOfRangeException(nameof(kind), kind, "Unknown API item kind.")
    };
 
    private static string ToKindString(GoApiMemberKind kind) => kind switch
    {
        GoApiMemberKind.Method => "method",
        GoApiMemberKind.Field => "field",
        GoApiMemberKind.Constant => "constant",
        _ => throw new ArgumentOutOfRangeException(nameof(kind), kind, "Unknown API member kind.")
    };
 
    private static string ToErrorModelString(GoApiErrorModel errorModel) => errorModel switch
    {
        GoApiErrorModel.None => "none",
        GoApiErrorModel.Returned => "returned",
        GoApiErrorModel.Deferred => "deferred",
        _ => throw new ArgumentOutOfRangeException(nameof(errorModel), errorModel, "Unknown error model.")
    };
 
    private static void AddIfPresent(JsonObject json, string name, string? value)
    {
        if (!string.IsNullOrEmpty(value))
        {
            json[name] = value;
        }
    }
 
    private static void AddIfTrue(JsonObject json, string name, bool value)
    {
        if (value)
        {
            json[name] = true;
        }
    }
 
    private static void AddIfPresent(JsonObject json, string name, IReadOnlyList<string> values)
    {
        if (values.Count == 0)
        {
            return;
        }
 
        var array = new JsonArray();
        foreach (var value in values)
        {
            array.Add((JsonNode)JsonValue.Create(value));
        }
 
        json[name] = array;
    }
}