| | | 1 | | // OpenApiSchemaDiscovery.cs |
| | | 2 | | using Kestrun.Forms; |
| | | 3 | | |
| | | 4 | | namespace Kestrun.OpenApi; |
| | | 5 | | |
| | | 6 | | /// <summary> |
| | | 7 | | /// Helper to discover OpenAPI schema types in loaded assemblies. |
| | | 8 | | /// </summary> |
| | | 9 | | public static class OpenApiSchemaDiscovery |
| | | 10 | | { |
| | | 11 | | /// <summary> |
| | | 12 | | /// Discover all OpenAPI component types from dynamic PowerShell classes. |
| | | 13 | | /// Scans all loaded assemblies for classes decorated with OpenApiModelKindAttribute |
| | | 14 | | /// or having any OpenApi* attributes. |
| | | 15 | | /// </summary> |
| | | 16 | | /// <returns> The discovered OpenAPI component types.</returns> |
| | | 17 | | public static OpenApiComponentSet GetOpenApiTypesAuto() |
| | | 18 | | { |
| | 5 | 19 | | var assemblies = GetRelevantAssemblies(); |
| | 5 | 20 | | return new OpenApiComponentSet |
| | 5 | 21 | | { |
| | 5 | 22 | | SchemaTypes = GetSchemaTypes(assemblies) |
| | 5 | 23 | | }; |
| | | 24 | | } |
| | | 25 | | |
| | | 26 | | private static System.Reflection.Assembly[] GetRelevantAssemblies() |
| | | 27 | | { |
| | 5 | 28 | | return [.. AppDomain.CurrentDomain.GetAssemblies() |
| | 1898 | 29 | | .Where(a => a.FullName is not null && |
| | 1898 | 30 | | (a.FullName.Contains("PowerShell Class Assembly") || |
| | 1898 | 31 | | a.FullName.StartsWith("Kestrun")))]; |
| | | 32 | | } |
| | | 33 | | |
| | | 34 | | private static Type[] GetSchemaTypes(System.Reflection.Assembly[] assemblies) |
| | | 35 | | { |
| | 5 | 36 | | var primitivesAssembly = typeof(OpenApiString).Assembly; |
| | | 37 | | |
| | 5 | 38 | | return [.. assemblies.SelectMany(GetLoadableTypes) |
| | 10995 | 39 | | .Where(t => t.IsClass && !t.IsAbstract && |
| | 10995 | 40 | | t.IsDefined(typeof(OpenApiSchemaComponent), true) && |
| | 10995 | 41 | | !IsFormPayloadBaseType(t) && |
| | 10995 | 42 | | // Exclude built-in OpenApi* primitives from auto-discovered components, |
| | 10995 | 43 | | // but keep user-defined schema components that inherit those primitives. |
| | 10995 | 44 | | !(t.Assembly == primitivesAssembly && typeof(IOpenApiScalar).IsAssignableFrom(t)))]; |
| | | 45 | | } |
| | | 46 | | |
| | | 47 | | /// <summary> |
| | | 48 | | /// Returns all loadable types from an assembly, even when some types fail to load. |
| | | 49 | | /// </summary> |
| | | 50 | | /// <param name="assembly">Assembly to enumerate types from.</param> |
| | | 51 | | /// <returns>Loadable types discovered in the assembly.</returns> |
| | | 52 | | private static IEnumerable<Type> GetLoadableTypes(System.Reflection.Assembly assembly) |
| | | 53 | | { |
| | | 54 | | try |
| | | 55 | | { |
| | 20 | 56 | | return assembly.GetTypes(); |
| | | 57 | | } |
| | 0 | 58 | | catch (System.Reflection.ReflectionTypeLoadException ex) |
| | | 59 | | { |
| | 0 | 60 | | return ex.Types.Where(t => t is not null).Cast<Type>(); |
| | | 61 | | } |
| | 20 | 62 | | } |
| | | 63 | | |
| | | 64 | | private static bool IsFormPayloadBaseType(Type t) |
| | | 65 | | { |
| | | 66 | | // Avoid emitting base form payload schemas unless explicitly referenced. |
| | 160 | 67 | | return t == typeof(KrFormData) |
| | 160 | 68 | | || t == typeof(KrMultipart); |
| | | 69 | | } |
| | | 70 | | } |