File: Commands\SettingsSchemaBuilder.cs
Web Access
Project: src\src\Aspire.Cli\Aspire.Cli.Tool.csproj (aspire)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.ComponentModel;
using System.Reflection;
using System.Text.Json.Serialization;
using Aspire.Cli.Configuration;
 
namespace Aspire.Cli.Commands;
 
/// <summary>
/// Builds schema information from AspireJsonConfiguration using reflection.
/// </summary>
internal static class SettingsSchemaBuilder
{
    /// <summary>
    /// Builds a SettingsSchema from AspireJsonConfiguration by inspecting its properties.
    /// </summary>
    /// <param name="excludeLocalOnly">If true, excludes properties marked with <see cref="LocalAspireJsonConfigurationPropertyAttribute"/>.</param>
    public static SettingsSchema BuildSchema(bool excludeLocalOnly)
    {
        var properties = new List<PropertyInfo>();
        var type = typeof(AspireJsonConfiguration);
 
        foreach (var prop in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
        {
            // Skip extension data property as it's for capturing additional properties
            if (prop.GetCustomAttribute<JsonExtensionDataAttribute>() != null)
            {
                continue;
            }
 
            // Skip local-only properties when building global settings schema
            if (excludeLocalOnly && prop.GetCustomAttribute<LocalAspireJsonConfigurationPropertyAttribute>() != null)
            {
                continue;
            }
 
            var jsonPropertyName = prop.GetCustomAttribute<JsonPropertyNameAttribute>()?.Name ?? prop.Name;
            
            // Skip $schema as it's a special JSON Schema property, not a user-editable setting
            if (jsonPropertyName == "$schema")
            {
                continue;
            }
            
            var description = prop.GetCustomAttribute<DescriptionAttribute>()?.Description ?? string.Empty;
            var jsonType = GetJsonType(prop.PropertyType);
            
            // For now, no properties are strictly required (all nullable/optional)
            const bool required = false;
 
            properties.Add(new PropertyInfo(jsonPropertyName, jsonType, description, required));
        }
 
        return new SettingsSchema(properties.OrderBy(p => p.Name).ToList());
    }
 
    /// <summary>
    /// Maps C# types to JSON schema types.
    /// </summary>
    private static string GetJsonType(Type type)
    {
        // Handle nullable types
        var underlyingType = Nullable.GetUnderlyingType(type) ?? type;
 
        if (underlyingType == typeof(string))
        {
            return "string";
        }
        
        if (underlyingType == typeof(bool))
        {
            return "boolean";
        }
        
        if (underlyingType == typeof(int) || underlyingType == typeof(long) || 
            underlyingType == typeof(decimal) || underlyingType == typeof(double) || 
            underlyingType == typeof(float))
        {
            return "number";
        }
        
        if (typeof(System.Collections.IDictionary).IsAssignableFrom(underlyingType))
        {
            return "object";
        }
        
        if (typeof(System.Collections.IEnumerable).IsAssignableFrom(underlyingType) && underlyingType != typeof(string))
        {
            return "array";
        }
        
        return "object";
    }
}