diff --git a/src/Kiota.Builder/Configuration/GenerationConfiguration.cs b/src/Kiota.Builder/Configuration/GenerationConfiguration.cs index e82f1079d2..4a00402113 100644 --- a/src/Kiota.Builder/Configuration/GenerationConfiguration.cs +++ b/src/Kiota.Builder/Configuration/GenerationConfiguration.cs @@ -150,6 +150,8 @@ public bool NoWorkspace get; set; } + public HashSet Overlays { get; set; } = new(0, StringComparer.OrdinalIgnoreCase); + public int MaxDegreeOfParallelism { get; set; } = -1; public object Clone() { @@ -184,6 +186,7 @@ public object Clone() DisableSSLValidation = DisableSSLValidation, ExportPublicApi = ExportPublicApi, PluginAuthInformation = PluginAuthInformation, + Overlays = new(Overlays, StringComparer.OrdinalIgnoreCase), }; } private static readonly StringIEnumerableDeepComparer comparer = new(); diff --git a/src/Kiota.Builder/Kiota.Builder.csproj b/src/Kiota.Builder/Kiota.Builder.csproj index 0d0a5c3118..9986f4d0ca 100644 --- a/src/Kiota.Builder/Kiota.Builder.csproj +++ b/src/Kiota.Builder/Kiota.Builder.csproj @@ -37,6 +37,7 @@ + diff --git a/src/Kiota.Builder/Lock/KiotaLock.cs b/src/Kiota.Builder/Lock/KiotaLock.cs index 671def487b..7943be9d83 100644 --- a/src/Kiota.Builder/Lock/KiotaLock.cs +++ b/src/Kiota.Builder/Lock/KiotaLock.cs @@ -98,6 +98,11 @@ public bool DisableSSLValidation /// The OpenAPI validation rules to disable during the generation. /// public HashSet DisabledValidationRules { get; set; } = new(StringComparer.OrdinalIgnoreCase); + /// + /// The overlays used for this client. + /// + public HashSet Overlays { get; set; } = new(StringComparer.OrdinalIgnoreCase); + #pragma warning restore CA2227 /// /// Updates the passed configuration with the values from the lock file. @@ -123,6 +128,7 @@ public void UpdateGenerationConfigurationFromLock(GenerationConfiguration config config.OpenAPIFilePath = DescriptionLocation; config.DisabledValidationRules = DisabledValidationRules.ToHashSet(StringComparer.OrdinalIgnoreCase); config.DisableSSLValidation = DisableSSLValidation; + config.Overlays = Overlays; } /// /// Initializes a new instance of the class. @@ -152,5 +158,6 @@ public KiotaLock(GenerationConfiguration config) DescriptionLocation = config.OpenAPIFilePath; DisabledValidationRules = config.DisabledValidationRules.ToHashSet(StringComparer.OrdinalIgnoreCase); DisableSSLValidation = config.DisableSSLValidation; + Overlays = config.Overlays; } } diff --git a/src/Kiota.Builder/OpenApiDocumentDownloadService.cs b/src/Kiota.Builder/OpenApiDocumentDownloadService.cs index 44e19e1a4d..9f678faab1 100644 --- a/src/Kiota.Builder/OpenApiDocumentDownloadService.cs +++ b/src/Kiota.Builder/OpenApiDocumentDownloadService.cs @@ -1,11 +1,14 @@ using System; using System.Diagnostics; using System.IO; +using System.Linq; using System.Net.Http; using System.Security; using System.Threading; using System.Threading.Tasks; using AsyncKeyedLock; +using BinkyLabs.OpenApi.Overlays; +using BinkyLabs.OpenApi.Overlays.Reader; using Kiota.Builder.Caching; using Kiota.Builder.Configuration; using Kiota.Builder.Extensions; @@ -96,7 +99,7 @@ ex is SecurityException || return (input, isDescriptionFromWorkspaceCopy); } - internal async Task GetDocumentWithResultFromStreamAsync(Stream input, GenerationConfiguration config, bool generating = false, CancellationToken cancellationToken = default) + internal async Task GetDocumentWithResultFromStreamAsync(Stream input, GenerationConfiguration config, bool generating = false, CancellationToken cancellationToken = default) { var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -122,9 +125,9 @@ ex is SecurityException || if (addPluginsExtensions) settings.AddPluginsExtensions();// Add all extensions for plugins + var rawUri = config.OpenAPIFilePath.TrimEnd(KiotaBuilder.ForwardSlash); try { - var rawUri = config.OpenAPIFilePath.TrimEnd(KiotaBuilder.ForwardSlash); var lastSlashIndex = rawUri.LastIndexOf(KiotaBuilder.ForwardSlash); if (lastSlashIndex < 0) lastSlashIndex = rawUri.Length - 1; @@ -137,7 +140,95 @@ ex is SecurityException || { // couldn't parse the URL, it's probably a local file } - var readResult = await OpenApiDocument.LoadAsync(input, settings: settings, cancellationToken: cancellationToken).ConfigureAwait(false); + + Microsoft.OpenApi.Reader.ReadResult readResult = new Microsoft.OpenApi.Reader.ReadResult() + { + Diagnostic = new OpenApiDiagnostic() + { + Errors = [], + Warnings = [], + } + }; + + if (config.Overlays.Count != 0) + { + + var overlaysSettings = new OverlayReaderSettings + { + OpenApiSettings = settings + }; + + var cachingProvider = new DocumentCachingProvider(HttpClient, Logger) + { + ClearCache = config.ClearCache, + }; + + OverlayDocument? overlayCombined = null; + foreach (var overlay in config.Overlays) + { + Uri? overlayUri = null; + if (Uri.TryCreate(overlay, UriKind.Absolute, out var absoluteUri)) + { + overlayUri = absoluteUri; + } + else if (Uri.TryCreate(overlay, UriKind.Relative, out var relativeUri)) + { + overlayUri = relativeUri; + } + + if (overlayUri is null) + { + throw new InvalidOperationException($"The overlay '{overlay}' is not a valid URI."); + } + + BinkyLabs.OpenApi.Overlays.ReadResult? overlayToCombineResult = null; + if (overlayUri.IsAbsoluteUri && overlayUri.Scheme is "http" or "https") + { + var fileName = overlay is string name && !string.IsNullOrEmpty(name) ? name : "overlay.yml"; + var inputOverlay = await cachingProvider.GetDocumentAsync(overlayUri, "generation", fileName, cancellationToken: cancellationToken).ConfigureAwait(false); + + overlayToCombineResult = await OverlayDocument.LoadFromStreamAsync(inputOverlay, null, overlaysSettings, cancellationToken).ConfigureAwait(false); + } + else + { + overlayToCombineResult = await OverlayDocument.LoadFromUrlAsync(overlay, overlaysSettings, cancellationToken).ConfigureAwait(false); + } + + if (overlayToCombineResult is null || overlayToCombineResult.Document is null) + { + throw new InvalidOperationException($"Could not read the overlay document at {overlayUri}. Please ensure the overlay is valid and accessible."); + } + + if (overlayToCombineResult.Diagnostic is not null) + { + readResult.Diagnostic.Errors.AddRange(overlayToCombineResult.Diagnostic.Errors ?? []); + readResult.Diagnostic.Warnings.AddRange(overlayToCombineResult.Diagnostic.Warnings ?? []); + } + + overlayCombined = overlayCombined is null + ? overlayToCombineResult.Document + : overlayCombined.CombineWith(overlayToCombineResult.Document); + } + + if (overlayCombined is not null) + { + var (document, overlaysDiagnostics, documentDiagnostics) = await overlayCombined.ApplyToDocumentStreamAsync(input, settings.BaseUrl ?? new Uri("file://" + rawUri), null, overlaysSettings, cancellationToken: cancellationToken) + .ConfigureAwait(false); + + readResult.Diagnostic.Errors.AddRange(documentDiagnostics?.Errors ?? []); + readResult.Diagnostic.Warnings.AddRange(documentDiagnostics?.Warnings ?? []); + readResult.Diagnostic.Errors.AddRange(overlaysDiagnostics?.Errors ?? []); + readResult.Diagnostic.Warnings.AddRange(overlaysDiagnostics?.Warnings ?? []); + + readResult.Document = document; + } + } + else + { + readResult = await OpenApiDocument.LoadAsync(input, settings: settings, cancellationToken: cancellationToken).ConfigureAwait(false); + } + + stopwatch.Stop(); if (generatingMode && readResult.Diagnostic?.Warnings is { Count: > 0 }) foreach (var warning in readResult.Diagnostic.Warnings) diff --git a/src/kiota/Handlers/Client/AddHandler.cs b/src/kiota/Handlers/Client/AddHandler.cs index e790296586..58366d2580 100644 --- a/src/kiota/Handlers/Client/AddHandler.cs +++ b/src/kiota/Handlers/Client/AddHandler.cs @@ -80,6 +80,10 @@ public required Option SkipGenerationOption { get; init; } + public required Option> OverlaysOption + { + get; init; + } public override async Task InvokeAsync(InvocationContext context) { @@ -101,6 +105,7 @@ public override async Task InvokeAsync(InvocationContext context) List? excludePatterns0 = context.ParseResult.GetValueForOption(ExcludePatternsOption); List? disabledValidationRules0 = context.ParseResult.GetValueForOption(DisabledValidationRulesOption); List? structuredMimeTypes0 = context.ParseResult.GetValueForOption(StructuredMimeTypesOption); + List? overlays0 = context.ParseResult.GetValueForOption(OverlaysOption); var logLevel = context.ParseResult.FindResultFor(LogLevelOption)?.GetValueOrDefault() as LogLevel?; CancellationToken cancellationToken = context.BindingContext.GetService(typeof(CancellationToken)) is CancellationToken token ? token : CancellationToken.None; @@ -125,6 +130,7 @@ public override async Task InvokeAsync(InvocationContext context) List excludePatterns = excludePatterns0.OrEmpty(); List disabledValidationRules = disabledValidationRules0.OrEmpty(); List structuredMimeTypes = structuredMimeTypes0.OrEmpty(); + List overlays = overlays0.OrEmpty(); AssignIfNotNullOrEmpty(output, (c, s) => c.OutputPath = s); AssignIfNotNullOrEmpty(openapi, (c, s) => c.OpenAPIFilePath = s); AssignIfNotNullOrEmpty(className, (c, s) => c.ClientClassName = s); @@ -132,6 +138,7 @@ public override async Task InvokeAsync(InvocationContext context) Configuration.Generation.UsesBackingStore = backingStore; Configuration.Generation.ExcludeBackwardCompatible = excludeBackwardCompatible; Configuration.Generation.IncludeAdditionalData = includeAdditionalData; + Configuration.Generation.Language = language; WarnUsingPreviewLanguage(language); Configuration.Generation.TypeAccessModifier = typeAccessModifier; @@ -150,6 +157,12 @@ public override async Task InvokeAsync(InvocationContext context) Configuration.Generation.StructuredMimeTypes = new(structuredMimeTypes.SelectMany(static x => x.Split(' ', StringSplitOptions.RemoveEmptyEntries)) .Select(static x => x.TrimQuotes())); + if (overlays.Count != 0) + Configuration.Generation.Overlays = overlays + .Select(static x => x.TrimQuotes()) + .SelectMany(static x => x.Split(',', StringSplitOptions.RemoveEmptyEntries)) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + Configuration.Generation.OpenAPIFilePath = GetAbsolutePath(Configuration.Generation.OpenAPIFilePath); Configuration.Generation.OutputPath = NormalizeSlashesInPath(GetAbsolutePath(Configuration.Generation.OutputPath)); Configuration.Generation.ApiManifestPath = NormalizeSlashesInPath(GetAbsolutePath(Configuration.Generation.ApiManifestPath)); diff --git a/src/kiota/Handlers/Client/EditHandler.cs b/src/kiota/Handlers/Client/EditHandler.cs index ddd2cef292..4ed7a24b28 100644 --- a/src/kiota/Handlers/Client/EditHandler.cs +++ b/src/kiota/Handlers/Client/EditHandler.cs @@ -80,6 +80,10 @@ public required Option SkipGenerationOption { get; init; } + public required Option> OverlaysOption + { + get; init; + } public override async Task InvokeAsync(InvocationContext context) { @@ -101,6 +105,8 @@ public override async Task InvokeAsync(InvocationContext context) List? excludePatterns = context.ParseResult.GetValueForOption(ExcludePatternsOption); List? disabledValidationRules = context.ParseResult.GetValueForOption(DisabledValidationRulesOption); List? structuredMimeTypes = context.ParseResult.GetValueForOption(StructuredMimeTypesOption); + List? overlays = context.ParseResult.GetValueForOption(OverlaysOption); + var logLevel = context.ParseResult.FindResultFor(LogLevelOption)?.GetValueOrDefault() as LogLevel?; CancellationToken cancellationToken = context.BindingContext.GetService(typeof(CancellationToken)) is CancellationToken token ? token : CancellationToken.None; @@ -159,6 +165,7 @@ public override async Task InvokeAsync(InvocationContext context) Configuration.Generation.ExcludeBackwardCompatible = excludeBackwardCompatible.Value; if (includeAdditionalData.HasValue) Configuration.Generation.IncludeAdditionalData = includeAdditionalData.Value; + AssignIfNotNullOrEmpty(output, (c, s) => c.OutputPath = s); AssignIfNotNullOrEmpty(openapi, (c, s) => c.OpenAPIFilePath = s); AssignIfNotNullOrEmpty(className, (c, s) => c.ClientClassName = s); @@ -176,6 +183,14 @@ public override async Task InvokeAsync(InvocationContext context) Configuration.Generation.StructuredMimeTypes = new(structuredMimeTypes.SelectMany(static x => x.Split(' ', StringSplitOptions.RemoveEmptyEntries)) .Select(static x => x.TrimQuotes())); + if (overlays is { Count: > 0 }) + Configuration.Generation.Overlays = overlays.Select(static x => x.TrimQuotes()) + .SelectMany(static x => x.Split(',', StringSplitOptions.RemoveEmptyEntries)) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + + + DefaultSerializersAndDeserializers(Configuration.Generation); var builder = new KiotaBuilder(logger, Configuration.Generation, httpClient, true); var result = await builder.GenerateClientAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/kiota/Handlers/KiotaGenerateCommandHandler.cs b/src/kiota/Handlers/KiotaGenerateCommandHandler.cs index 57cbf15686..00595cc752 100644 --- a/src/kiota/Handlers/KiotaGenerateCommandHandler.cs +++ b/src/kiota/Handlers/KiotaGenerateCommandHandler.cs @@ -73,6 +73,11 @@ public required Option> StructuredMimeTypesOption { get; init; } + + public required Option> OverlaysOption + { + get; init; + } public override async Task InvokeAsync(InvocationContext context) { // Span start time @@ -97,6 +102,7 @@ public override async Task InvokeAsync(InvocationContext context) List? includePatterns0 = context.ParseResult.GetValueForOption(IncludePatternsOption); List? excludePatterns0 = context.ParseResult.GetValueForOption(ExcludePatternsOption); List? disabledValidationRules0 = context.ParseResult.GetValueForOption(DisabledValidationRulesOption); + List? overlays0 = context.ParseResult.GetValueForOption(OverlaysOption); bool cleanOutput = context.ParseResult.GetValueForOption(CleanOutputOption); List? structuredMimeTypes0 = context.ParseResult.GetValueForOption(StructuredMimeTypesOption); var logLevel = context.ParseResult.FindResultFor(LogLevelOption)?.GetValueOrDefault() as LogLevel?; @@ -123,6 +129,7 @@ public override async Task InvokeAsync(InvocationContext context) List excludePatterns = excludePatterns0.OrEmpty(); List disabledValidationRules = disabledValidationRules0.OrEmpty(); List structuredMimeTypes = structuredMimeTypes0.OrEmpty(); + List overlays = overlays0.OrEmpty(); AssignIfNotNullOrEmpty(output, (c, s) => c.OutputPath = s); AssignIfNotNullOrEmpty(openapi, (c, s) => c.OpenAPIFilePath = s); AssignIfNotNullOrEmpty(manifest, (c, s) => c.ApiManifestPath = s); @@ -151,6 +158,12 @@ public override async Task InvokeAsync(InvocationContext context) Configuration.Generation.StructuredMimeTypes = new(structuredMimeTypes.SelectMany(static x => x.Split(' ', StringSplitOptions.RemoveEmptyEntries)) .Select(static x => x.TrimQuotes())); + if (overlays.Count != 0) + Configuration.Generation.Overlays = overlays + .Select(static x => x.TrimQuotes()) + .SelectMany(static x => x.Split(',', StringSplitOptions.RemoveEmptyEntries)) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + Configuration.Generation.OpenAPIFilePath = GetAbsolutePath(Configuration.Generation.OpenAPIFilePath); Configuration.Generation.OutputPath = NormalizeSlashesInPath(GetAbsolutePath(Configuration.Generation.OutputPath)); Configuration.Generation.ApiManifestPath = NormalizeSlashesInPath(GetAbsolutePath(Configuration.Generation.ApiManifestPath)); diff --git a/src/kiota/Handlers/Plugin/AddHandler.cs b/src/kiota/Handlers/Plugin/AddHandler.cs index 0914da3c6a..287b9e79b6 100644 --- a/src/kiota/Handlers/Plugin/AddHandler.cs +++ b/src/kiota/Handlers/Plugin/AddHandler.cs @@ -66,6 +66,11 @@ public required Option NoWorkspaceOption { get; init; } + public required Option> OverlaysOption + { + get; init; + } + public override async Task InvokeAsync(InvocationContext context) { // Span start time @@ -82,6 +87,8 @@ public override async Task InvokeAsync(InvocationContext context) string? className = context.ParseResult.GetValueForOption(ClassOption); List? includePatterns0 = context.ParseResult.GetValueForOption(IncludePatternsOption); List? excludePatterns0 = context.ParseResult.GetValueForOption(ExcludePatternsOption); + List? overlays0 = context.ParseResult.GetValueForOption(OverlaysOption); + var logLevel = context.ParseResult.FindResultFor(LogLevelOption)?.GetValueOrDefault() as LogLevel?; CancellationToken cancellationToken = context.BindingContext.GetService(typeof(CancellationToken)) is CancellationToken token ? token : CancellationToken.None; @@ -108,6 +115,7 @@ public override async Task InvokeAsync(InvocationContext context) Configuration.Generation.SkipGeneration = skipGeneration; Configuration.Generation.NoWorkspace = noWorkspace; Configuration.Generation.Operation = ConsumerOperation.Add; + if (pluginTypes is { Count: > 0 }) Configuration.Generation.PluginTypes = pluginTypes.ToHashSet(); if (pluginAuthType.HasValue && !string.IsNullOrWhiteSpace(pluginAuthRefId)) @@ -116,6 +124,14 @@ public override async Task InvokeAsync(InvocationContext context) Configuration.Generation.IncludePatterns = includePatterns0.Select(static x => x.TrimQuotes()).ToHashSet(StringComparer.OrdinalIgnoreCase); if (excludePatterns0 is { Count: > 0 }) Configuration.Generation.ExcludePatterns = excludePatterns0.Select(static x => x.TrimQuotes()).ToHashSet(StringComparer.OrdinalIgnoreCase); + + if (overlays0 is { Count: > 0 }) + Configuration.Generation.Overlays = overlays0 + .Select(static x => x.TrimQuotes()) + .SelectMany(static x => x.Split(',', StringSplitOptions.RemoveEmptyEntries)) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + Configuration.Generation.OpenAPIFilePath = GetAbsolutePath(Configuration.Generation.OpenAPIFilePath); Configuration.Generation.OutputPath = NormalizeSlashesInPath(GetAbsolutePath(Configuration.Generation.OutputPath)); var (loggerFactory, logger) = GetLoggerAndFactory(context, Configuration.Generation.OutputPath); diff --git a/src/kiota/Handlers/Plugin/EditHandler.cs b/src/kiota/Handlers/Plugin/EditHandler.cs index 6cdc82cb80..ad38fad8cb 100644 --- a/src/kiota/Handlers/Plugin/EditHandler.cs +++ b/src/kiota/Handlers/Plugin/EditHandler.cs @@ -61,6 +61,10 @@ public required Option PluginAuthRefIdOption { get; init; } + public required Option> OverlaysOption + { + get; init; + } public override async Task InvokeAsync(InvocationContext context) { @@ -77,6 +81,9 @@ public override async Task InvokeAsync(InvocationContext context) string? className0 = context.ParseResult.GetValueForOption(ClassOption); List? includePatterns = context.ParseResult.GetValueForOption(IncludePatternsOption); List? excludePatterns = context.ParseResult.GetValueForOption(ExcludePatternsOption); + List? overlays = context.ParseResult.GetValueForOption(OverlaysOption); + + var logLevel = context.ParseResult.FindResultFor(LogLevelOption)?.GetValueOrDefault() as LogLevel?; CancellationToken cancellationToken = context.BindingContext.GetService(typeof(CancellationToken)) is CancellationToken token ? token : CancellationToken.None; @@ -135,8 +142,16 @@ public override async Task InvokeAsync(InvocationContext context) Configuration.Generation.ExcludePatterns = excludePatterns.Select(static x => x.TrimQuotes()).ToHashSet(StringComparer.OrdinalIgnoreCase); if (pluginTypes is { Count: > 0 }) Configuration.Generation.PluginTypes = pluginTypes.ToHashSet(); + if (overlays is { Count: > 0 }) + Configuration.Generation.Overlays = overlays + .Select(static x => x.TrimQuotes()) + .SelectMany(static x => x.Split(',', StringSplitOptions.RemoveEmptyEntries)) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + + Configuration.Generation.OpenAPIFilePath = GetAbsolutePath(Configuration.Generation.OpenAPIFilePath); Configuration.Generation.OutputPath = NormalizeSlashesInPath(GetAbsolutePath(Configuration.Generation.OutputPath)); + DefaultSerializersAndDeserializers(Configuration.Generation); var builder = new KiotaBuilder(logger, Configuration.Generation, httpClient, true); var result = await builder.GeneratePluginAsync(cancellationToken).ConfigureAwait(false); diff --git a/src/kiota/KiotaClientCommands.cs b/src/kiota/KiotaClientCommands.cs index cd912c3b90..58e8c1ca70 100644 --- a/src/kiota/KiotaClientCommands.cs +++ b/src/kiota/KiotaClientCommands.cs @@ -46,6 +46,7 @@ public static Command GetAddCommand() var dvrOption = KiotaHost.GetDisableValidationRulesOption(); var skipGenerationOption = GetSkipGenerationOption(); var clientNameOption = GetClientNameOption(); + var overlaysOption = KiotaHost.GetOverlaysOption(); var command = new Command("add", "Adds a new client to the Kiota configuration"){ descriptionOption, @@ -63,6 +64,7 @@ public static Command GetAddCommand() excludePatterns, dvrOption, skipGenerationOption, + overlaysOption }; command.Handler = new AddHandler { @@ -81,6 +83,7 @@ public static Command GetAddCommand() ExcludePatternsOption = excludePatterns, DisabledValidationRulesOption = dvrOption, SkipGenerationOption = skipGenerationOption, + OverlaysOption = overlaysOption }; return command; @@ -120,6 +123,7 @@ public static Command GetEditCommand() var dvrOption = KiotaHost.GetDisableValidationRulesOption(); var skipGenerationOption = GetSkipGenerationOption(); var clientNameOption = GetClientNameOption(); + var overlaysOption = KiotaHost.GetOverlaysOption(); var command = new Command("edit", "Edits a client from the Kiota configuration") { descriptionOption, @@ -137,6 +141,7 @@ public static Command GetEditCommand() excludePatterns, dvrOption, skipGenerationOption, + overlaysOption }; command.Handler = new EditHandler { @@ -155,6 +160,7 @@ public static Command GetEditCommand() ExcludePatternsOption = excludePatterns, DisabledValidationRulesOption = dvrOption, SkipGenerationOption = skipGenerationOption, + OverlaysOption = overlaysOption }; return command; } diff --git a/src/kiota/KiotaHost.cs b/src/kiota/KiotaHost.cs index 9f50f7502f..c671440f05 100644 --- a/src/kiota/KiotaHost.cs +++ b/src/kiota/KiotaHost.cs @@ -433,6 +433,12 @@ internal static Option> GetStructuredMimeTypesOption(List d structuredMimeTypesOption.AddAlias("-m"); return structuredMimeTypesOption; } + internal static Option> GetOverlaysOption() + { + var overlaysOption = new Option>("--overlays", "Enable overlays for models"); + overlaysOption.AddAlias("--ov"); + return overlaysOption; + } private static Command GetGenerateCommand() { var defaultConfiguration = new GenerationConfiguration(); @@ -484,6 +490,8 @@ private static Command GetGenerateCommand() var clearCacheOption = GetClearCacheOption(defaultConfiguration.ClearCache); + var overlaysOption = GetOverlaysOption(); + var disableSSLValidationOption = GetDisableSSLValidationOption(defaultConfiguration.DisableSSLValidation); var command = new Command("generate", "Generates a REST HTTP API client from an OpenAPI description file.") { @@ -507,6 +515,7 @@ private static Command GetGenerateCommand() dvrOption, clearCacheOption, disableSSLValidationOption, + overlaysOption }; command.Handler = new KiotaGenerateCommandHandler { @@ -530,6 +539,7 @@ private static Command GetGenerateCommand() DisabledValidationRulesOption = dvrOption, ClearCacheOption = clearCacheOption, DisableSSLValidationOption = disableSSLValidationOption, + OverlaysOption = overlaysOption, }; return command; } diff --git a/src/kiota/KiotaPluginCommands.cs b/src/kiota/KiotaPluginCommands.cs index 679e050dee..02ff2da835 100644 --- a/src/kiota/KiotaPluginCommands.cs +++ b/src/kiota/KiotaPluginCommands.cs @@ -83,6 +83,7 @@ public static Command GetAddCommand() var pluginType = GetPluginTypeOption(); var pluginAuthTypeOption = GetPluginAuthenticationTypeOption(); var pluginAuthRefIdOption = GetPluginAuthenticationReferenceIdOption(); + var overlaysOption = KiotaHost.GetOverlaysOption(); var command = new Command("add", "Adds a new plugin to the Kiota configuration"){ descriptionOption, includePatterns, @@ -95,7 +96,7 @@ public static Command GetAddCommand() pluginAuthTypeOption, pluginAuthRefIdOption, noWorkspaceOption, - //TODO overlay when we have support for it in OAI.net + overlaysOption }; command.AddValidator(commandResult => { @@ -114,6 +115,7 @@ public static Command GetAddCommand() SkipGenerationOption = skipGenerationOption, LogLevelOption = logLevelOption, NoWorkspaceOption = noWorkspaceOption, + OverlaysOption = overlaysOption, }; return command; } @@ -128,6 +130,7 @@ public static Command GetEditCommand() var pluginTypes = GetPluginTypeOption(false); var pluginAuthTypeOption = GetPluginAuthenticationTypeOption(); var pluginAuthRefIdOption = GetPluginAuthenticationReferenceIdOption(); + var overlaysOption = KiotaHost.GetOverlaysOption(); var command = new Command("edit", "Edits a plugin configuration and updates the Kiota configuration"){ descriptionOption, includePatterns, @@ -139,7 +142,7 @@ public static Command GetEditCommand() pluginTypes, pluginAuthTypeOption, pluginAuthRefIdOption, - //TODO overlay when we have support for it in OAI.net + overlaysOption }; command.AddValidator(commandResult => { @@ -156,6 +159,7 @@ public static Command GetEditCommand() IncludePatternsOption = includePatterns, ExcludePatternsOption = excludePatterns, SkipGenerationOption = skipGenerationOption, + OverlaysOption = overlaysOption, LogLevelOption = logLevelOption, }; return command; diff --git a/src/kiota/Rpc/IServer.cs b/src/kiota/Rpc/IServer.cs index 7c0110e593..58f9d7cec6 100644 --- a/src/kiota/Rpc/IServer.cs +++ b/src/kiota/Rpc/IServer.cs @@ -10,9 +10,9 @@ internal interface IServer Task SearchAsync(string searchTerm, bool clearCache, CancellationToken cancellationToken); Task ShowAsync(string descriptionPath, string[] includeFilters, string[] excludeFilters, bool clearCache, bool includeKiotaValidationRules, CancellationToken cancellationToken); Task GetManifestDetailsAsync(string manifestPath, string apiIdentifier, bool clearCache, CancellationToken cancellationToken); - Task> GenerateAsync(string openAPIFilePath, string outputPath, GenerationLanguage language, string[] includePatterns, string[] excludePatterns, string clientClassName, string clientNamespaceName, bool usesBackingStore, bool cleanOutput, bool clearCache, bool excludeBackwardCompatible, string[] disabledValidationRules, string[] serializers, string[] deserializers, string[] structuredMimeTypes, bool includeAdditionalData, ConsumerOperation operation, CancellationToken cancellationToken); + Task> GenerateAsync(string openAPIFilePath, string outputPath, GenerationLanguage language, string[] includePatterns, string[] excludePatterns, string clientClassName, string clientNamespaceName, bool usesBackingStore, bool cleanOutput, bool clearCache, bool excludeBackwardCompatible, string[] disabledValidationRules, string[] serializers, string[] deserializers, string[] structuredMimeTypes, bool includeAdditionalData, string[] overlays, ConsumerOperation operation, CancellationToken cancellationToken); Task InfoForDescriptionAsync(string descriptionPath, bool clearCache, CancellationToken cancellationToken); - Task> GeneratePluginAsync(string openAPIFilePath, string outputPath, PluginType[] pluginTypes, string[] includePatterns, string[] excludePatterns, string clientClassName, bool cleanOutput, bool clearCache, string[] disabledValidationRules, bool? noWorkspace, PluginAuthType? pluginAuthType, string pluginAuthRefid, ConsumerOperation operation, CancellationToken cancellationToken); + Task> GeneratePluginAsync(string openAPIFilePath, string outputPath, PluginType[] pluginTypes, string[] includePatterns, string[] excludePatterns, string clientClassName, bool cleanOutput, bool clearCache, string[] disabledValidationRules, bool? noWorkspace, PluginAuthType? pluginAuthType, string? pluginAuthRefid, string[] overlays, ConsumerOperation operation, CancellationToken cancellationToken); Task> MigrateFromLockFileAsync(string lockDirectoryPath, CancellationToken cancellationToken); Task> RemoveClientAsync(string clientName, bool cleanOutput, CancellationToken cancellationToken); Task> RemovePluginAsync(string pluginName, bool cleanOutput, CancellationToken cancellationToken); diff --git a/src/kiota/Rpc/Server.cs b/src/kiota/Rpc/Server.cs index a420f0ab7f..f2abe70bcc 100644 --- a/src/kiota/Rpc/Server.cs +++ b/src/kiota/Rpc/Server.cs @@ -152,7 +152,7 @@ private static string NormalizeOperationNodePath(OpenApiUrlTreeNode node, HttpMe return indexingNormalizationRegex().Replace(name, "{}"); return name; } - public async Task> GenerateAsync(string openAPIFilePath, string outputPath, GenerationLanguage language, string[] includePatterns, string[] excludePatterns, string clientClassName, string clientNamespaceName, bool usesBackingStore, bool cleanOutput, bool clearCache, bool excludeBackwardCompatible, string[] disabledValidationRules, string[] serializers, string[] deserializers, string[] structuredMimeTypes, bool includeAdditionalData, ConsumerOperation operation, CancellationToken cancellationToken) + public async Task> GenerateAsync(string openAPIFilePath, string outputPath, GenerationLanguage language, string[] includePatterns, string[] excludePatterns, string clientClassName, string clientNamespaceName, bool usesBackingStore, bool cleanOutput, bool clearCache, bool excludeBackwardCompatible, string[] disabledValidationRules, string[] serializers, string[] deserializers, string[] structuredMimeTypes, bool includeAdditionalData, string[] overlays, ConsumerOperation operation, CancellationToken cancellationToken) { var logger = new ForwardedLogger(); var configuration = Configuration.Generation; @@ -167,6 +167,7 @@ public async Task> GenerateAsync(string openAPIFilePath, string o configuration.ExcludeBackwardCompatible = excludeBackwardCompatible; configuration.IncludeAdditionalData = includeAdditionalData; configuration.Operation = operation; + if (disabledValidationRules is { Length: > 0 }) configuration.DisabledValidationRules = disabledValidationRules.ToHashSet(StringComparer.OrdinalIgnoreCase); if (serializers is { Length: > 0 }) @@ -175,6 +176,8 @@ public async Task> GenerateAsync(string openAPIFilePath, string o configuration.Deserializers = deserializers.ToHashSet(StringComparer.OrdinalIgnoreCase); if (structuredMimeTypes is { Length: > 0 }) configuration.StructuredMimeTypes = new(structuredMimeTypes); + if (overlays is { Length: > 0 }) + configuration.Overlays = new(overlays); if (IsConfigPreviewEnabled.Value) { configuration.Serializers.Clear(); @@ -200,7 +203,7 @@ public async Task> GenerateAsync(string openAPIFilePath, string o } public async Task> GeneratePluginAsync(string openAPIFilePath, string outputPath, PluginType[] pluginTypes, string[] includePatterns, string[] excludePatterns, string clientClassName, bool cleanOutput, bool clearCache, string[] disabledValidationRules, - bool? noWorkspace, PluginAuthType? pluginAuthType, string? pluginAuthRefid, ConsumerOperation operation, CancellationToken cancellationToken) + bool? noWorkspace, PluginAuthType? pluginAuthType, string? pluginAuthRefid, string[] overlays, ConsumerOperation operation, CancellationToken cancellationToken) { var globalLogger = new ForwardedLogger(); var configuration = Configuration.Generation; @@ -221,6 +224,11 @@ public async Task> GeneratePluginAsync(string openAPIFilePath, st configuration.IncludePatterns = includePatterns.Select(static x => x.TrimQuotes()).ToHashSet(StringComparer.OrdinalIgnoreCase); if (excludePatterns is { Length: > 0 }) configuration.ExcludePatterns = excludePatterns.Select(static x => x.TrimQuotes()).ToHashSet(StringComparer.OrdinalIgnoreCase); + + if (overlays is { Length: > 0 }) + Configuration.Generation.Overlays = overlays + .Select(static x => x.TrimQuotes()).ToHashSet(StringComparer.OrdinalIgnoreCase); + configuration.OpenAPIFilePath = GetAbsolutePath(configuration.OpenAPIFilePath); configuration.OutputPath = NormalizeSlashesInPath(GetAbsolutePath(configuration.OutputPath)); if (!string.IsNullOrEmpty(pluginAuthRefid) && pluginAuthType != null) diff --git a/tests/Kiota.Builder.Tests/OpenApiDocumentDownloadServiceTests.cs b/tests/Kiota.Builder.Tests/OpenApiDocumentDownloadServiceTests.cs index aeb9044576..050f8f9649 100644 --- a/tests/Kiota.Builder.Tests/OpenApiDocumentDownloadServiceTests.cs +++ b/tests/Kiota.Builder.Tests/OpenApiDocumentDownloadServiceTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; @@ -11,6 +12,7 @@ namespace Kiota.Builder.Tests.OpenApiExtensions; public sealed class OpenApiDocumentDownloadServiceTests : IDisposable { private readonly HttpClient _httpClient = new(); + private readonly string TempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); private const string DocumentContentWithNoServer = @"openapi: 3.0.0 info: title: Graph Users @@ -33,6 +35,269 @@ public sealed class OpenApiDocumentDownloadServiceTests : IDisposable public void Dispose() { _httpClient.Dispose(); + if (Directory.Exists(TempDirectory)) + { + Directory.Delete(TempDirectory, true); + } + } + + [Fact] + public async Task GetDocumentFromStreamAsyncTest_WithOverlaysYamlInConfigWithRelativePath() + { + // Assert + var yaml = """ + overlay: "1.0.0" + info: + title: "Test Overlay" + version: "2.0.0" + actions: + - target: "$.info" + update: + title: "Updated Title" + description: "Updated Description" + """; + + + var fakeLogger = new FakeLogger(); + + Directory.CreateDirectory(TempDirectory); + var overlaysPath = Path.Combine(TempDirectory, Path.GetRandomFileName() + "overlays.yaml"); + await File.WriteAllTextAsync(overlaysPath, yaml).ConfigureAwait(false); + + var generationConfig = new GenerationConfiguration + { + Overlays = new HashSet() { + overlaysPath + } + }; + + //Act + using var inputDocumentStream = CreateMemoryStreamFromString(DocumentContentWithNoServer); + var documentDownloadService = new OpenApiDocumentDownloadService(_httpClient, fakeLogger); + var document = await documentDownloadService.GetDocumentFromStreamAsync(inputDocumentStream, generationConfig); + + // Assert + Assert.NotNull(document); + Assert.Equal("Updated Title", document.Info.Title); + Assert.Equal("Updated Description", document.Info.Description); + } + + [Fact] + public async Task GetDocumentFromStreamAsyncTest_With2OverlaysYamlInConfigWithRelativePath() + { + // Assert + var yaml = """ + overlay: "1.0.0" + info: + title: "Test Overlay" + version: "2.0.0" + actions: + - target: "$.info" + update: + title: "Updated Title" + """; + + var yaml2 = """ + overlay: "1.0.0" + info: + title: "Test Overlay" + version: "2.0.0" + actions: + - target: "$.info" + update: + description: "Updated Description" + """; + + + var fakeLogger = new FakeLogger(); + + Directory.CreateDirectory(TempDirectory); + var overlaysPath = Path.Combine(TempDirectory, Path.GetRandomFileName() + "overlays.yaml"); + var overlaysPath2 = Path.Combine(TempDirectory, Path.GetRandomFileName() + "overlays.yaml"); + await File.WriteAllTextAsync(overlaysPath, yaml); + await File.WriteAllTextAsync(overlaysPath2, yaml2); + + var generationConfig = new GenerationConfiguration + { + Overlays = new HashSet() { + overlaysPath, + overlaysPath2 + } + }; + + //Act + using var inputDocumentStream = CreateMemoryStreamFromString(DocumentContentWithNoServer); + var documentDownloadService = new OpenApiDocumentDownloadService(_httpClient, fakeLogger); + var document = await documentDownloadService.GetDocumentFromStreamAsync(inputDocumentStream, generationConfig); + + // Assert + Assert.NotNull(document); + Assert.Equal("Updated Title", document.Info.Title); + Assert.Equal("Updated Description", document.Info.Description); + } + + [Fact] + public async Task GetDocumentFromStreamAsyncTest_WithOverlaysYamlInConfigAbsolutePath() + { + // Assert + var yaml = """ + overlay: "1.0.0" + info: + title: "Test Overlay" + version: "2.0.0" + actions: + - target: "$.info" + update: + title: "Updated Title" + description: "Updated Description" + """; + + + var fakeLogger = new FakeLogger(); + + + Directory.CreateDirectory(TempDirectory); + var overlaysPath = Path.Combine(TempDirectory, Path.GetRandomFileName() + "overlays.yaml"); + await File.WriteAllTextAsync(overlaysPath, yaml); + + var generationConfig = new GenerationConfiguration + { + Overlays = new HashSet() { + overlaysPath + } + }; + + //Act + using var inputDocumentStream = CreateMemoryStreamFromString(DocumentContentWithNoServer); + var documentDownloadService = new OpenApiDocumentDownloadService(_httpClient, fakeLogger); + var document = await documentDownloadService.GetDocumentFromStreamAsync(inputDocumentStream, generationConfig); + + + // Assert + Assert.NotNull(document); + Assert.Equal("Updated Title", document.Info.Title); + Assert.Equal("Updated Description", document.Info.Description); + } + + [Fact] + public async Task GetDocumentFromStreamAsyncTest_WithInvalidUpdatePropertyInOverlays() + { + // Assert + var json = """ + overlay: "1.0.0" + info: + title: "Test Overlay" + version: "2.0.0" + actions: + - target: "$.info" + update: + randomProperty: "Updated RandomProperty" + description: "Updated Description" + """; + + + var fakeLogger = new FakeLogger(); + + + Directory.CreateDirectory(TempDirectory); + var overlaysPath = Path.Combine(TempDirectory, Path.GetRandomFileName() + "overlays.yaml"); + await File.WriteAllTextAsync(overlaysPath, json); + + var generationConfig = new GenerationConfiguration + { + Overlays = new HashSet() { + overlaysPath + } + }; + + //Act + using var inputDocumentStream = CreateMemoryStreamFromString(DocumentContentWithNoServer); + var documentDownloadService = new OpenApiDocumentDownloadService(_httpClient, fakeLogger); + var document = await documentDownloadService.GetDocumentFromStreamAsync(inputDocumentStream, generationConfig); + + + // Assert + Assert.NotNull(document); + Assert.Equal("Updated Description", document.Info.Description); + var diagError = fakeLogger.LogEntries + .Where(l => l.message.StartsWith("OpenAPI error:")); + Assert.Single(diagError); + } + + [Fact] + public async Task GetDocumentFromStreamAsyncTest_WithOverlaysJsonInConfig() + { + var json = + """ + { + "openapi": "3.1.0", + "info": { + "title": "Test Overlay", + "version": "2.0.0", + "description": "Description API" + }, + "paths": { + "/test": { + "get": { + "summary": "Test endpoint", + "responses": { + "200": { + "description": "OK" + } + } + } + } + } + } + """; + + // Assert + var jsonOverlays = """ + { + "overlay": "1.0.0", + "info": { + "title": "Test Overlay", + "version": "2.0.0" + }, + "extends": "x-extends", + "actions": [ + { + "target": "$.info", + "update": { + "title": "Updated Title YES" + } + }, + { + "target": "$.info.description", + "remove": true + } + ], + + } + """; + + var fakeLogger = new FakeLogger(); + + Directory.CreateDirectory(TempDirectory); + var overlaysPath = Path.Combine(TempDirectory, Path.GetRandomFileName() + "overlays.yaml"); + await File.WriteAllTextAsync(overlaysPath, jsonOverlays); + + var generationConfig = new GenerationConfiguration + { + Overlays = new HashSet() { + overlaysPath + } + }; + + //Act + using var inputDocumentStream = CreateMemoryStreamFromString(json); + var documentDownloadService = new OpenApiDocumentDownloadService(_httpClient, fakeLogger); + var document = await documentDownloadService.GetDocumentFromStreamAsync(inputDocumentStream, generationConfig); + + // Assert + Assert.NotNull(document); + Assert.Equal("Updated Title YES", document.Info.Title); + Assert.Null(document.Info.Description); } [Fact] @@ -56,6 +321,7 @@ public async Task GetDocumentFromStreamAsyncTest_IncludeKiotaValidationRulesInCo Assert.Single(logEntryForNoServerRule); } + [Fact] public async Task GetDocumentFromStreamAsyncTest_No_IncludeKiotaValidationRulesInConfig() { diff --git a/vscode/packages/npm-package/lib/generateClient.ts b/vscode/packages/npm-package/lib/generateClient.ts index 3bf65071f4..049f86f75d 100644 --- a/vscode/packages/npm-package/lib/generateClient.ts +++ b/vscode/packages/npm-package/lib/generateClient.ts @@ -5,25 +5,27 @@ import connectToKiota from "../connect"; import { KiotaGenerationLanguage, KiotaResult } from "../types"; export interface ClientGenerationOptions { - openAPIFilePath: string; - clientClassName: string; - clientNamespaceName: string; - language: KiotaGenerationLanguage; - outputPath: string; - operation: ConsumerOperation; - workingDirectory: string; + openAPIFilePath: string; + clientClassName: string; + clientNamespaceName: string; + language: KiotaGenerationLanguage; + outputPath: string; + operation: ConsumerOperation; + workingDirectory: string; + + deserializers?: string[]; + disabledValidationRules?: string[]; + excludeBackwardCompatible?: boolean; + excludePatterns?: string[]; + includeAdditionalData?: boolean; + includePatterns?: string[]; + clearCache?: boolean; + cleanOutput?: boolean; + serializers?: string[]; + structuredMimeTypes?: string[]; + usesBackingStore?: boolean; + overlays?: string[]; - deserializers?: string[]; - disabledValidationRules?: string[]; - excludeBackwardCompatible?: boolean; - excludePatterns?: string[]; - includeAdditionalData?: boolean; - includePatterns?: string[]; - clearCache?: boolean; - cleanOutput?: boolean; - serializers?: string[]; - structuredMimeTypes?: string[]; - usesBackingStore?: boolean; } /** @@ -50,52 +52,54 @@ export interface ClientGenerationOptions { * @param {string[]} [clientGenerationOptions.serializers] - The list of serializers to use. * @param {string[]} [clientGenerationOptions.structuredMimeTypes] - The list of structured MIME types to support. * @param {boolean} [clientGenerationOptions.usesBackingStore] - Whether the generated client uses a backing store. - + * @param {string[]} [clientGenerationOptions.overlays] - Whether the generated client uses overlays. + * * @returns {Promise} A promise that resolves to a KiotaResult if successful, or undefined if not. * @throws {Error} If an error occurs during the client generation process. */ export async function generateClient(clientGenerationOptions: ClientGenerationOptions): Promise { - const result = await connectToKiota(async (connection) => { - const request = new rpc.RequestType1( - "Generate" - ); + const result = await connectToKiota(async (connection) => { + const request = new rpc.RequestType1( + "Generate" + ); - return await connection.sendRequest( - request, - { - openAPIFilePath: clientGenerationOptions.openAPIFilePath, - clientClassName: clientGenerationOptions.clientClassName, - clientNamespaceName: clientGenerationOptions.clientNamespaceName, - language: clientGenerationOptions.language, - outputPath: clientGenerationOptions.outputPath, - operation: clientGenerationOptions.operation, + return await connection.sendRequest( + request, + { + openAPIFilePath: clientGenerationOptions.openAPIFilePath, + clientClassName: clientGenerationOptions.clientClassName, + clientNamespaceName: clientGenerationOptions.clientNamespaceName, + language: clientGenerationOptions.language, + outputPath: clientGenerationOptions.outputPath, + operation: clientGenerationOptions.operation, - deserializers: clientGenerationOptions.deserializers ?? [], - disabledValidationRules: clientGenerationOptions.disabledValidationRules ?? [], - excludeBackwardCompatible: clientGenerationOptions.excludeBackwardCompatible ?? false, - excludePatterns: clientGenerationOptions.excludePatterns ?? [], - includeAdditionalData: clientGenerationOptions.includeAdditionalData ?? false, - cleanOutput: clientGenerationOptions.cleanOutput ?? false, - clearCache: clientGenerationOptions.clearCache ?? false, - includePatterns: clientGenerationOptions.includePatterns ?? [], - serializers: clientGenerationOptions.serializers ?? [], - structuredMimeTypes: clientGenerationOptions.structuredMimeTypes ?? [], - usesBackingStore: clientGenerationOptions.usesBackingStore ?? false, - } as GenerationConfiguration, - ); - }, clientGenerationOptions.workingDirectory); + deserializers: clientGenerationOptions.deserializers ?? [], + disabledValidationRules: clientGenerationOptions.disabledValidationRules ?? [], + excludeBackwardCompatible: clientGenerationOptions.excludeBackwardCompatible ?? false, + excludePatterns: clientGenerationOptions.excludePatterns ?? [], + includeAdditionalData: clientGenerationOptions.includeAdditionalData ?? false, + cleanOutput: clientGenerationOptions.cleanOutput ?? false, + clearCache: clientGenerationOptions.clearCache ?? false, + includePatterns: clientGenerationOptions.includePatterns ?? [], + serializers: clientGenerationOptions.serializers ?? [], + structuredMimeTypes: clientGenerationOptions.structuredMimeTypes ?? [], + usesBackingStore: clientGenerationOptions.usesBackingStore ?? false, + overlays: clientGenerationOptions.overlays ?? [], + } as GenerationConfiguration, + ); + }, clientGenerationOptions.workingDirectory); - if (result instanceof Error) { - throw result; - } + if (result instanceof Error) { + throw result; + } - if (result) { - return { - isSuccess: checkForSuccess(result as KiotaLogEntry[]), - logs: result - }; - } + if (result) { + return { + isSuccess: checkForSuccess(result as KiotaLogEntry[]), + logs: result + }; + } - return undefined; + return undefined; }; diff --git a/vscode/packages/npm-package/lib/generatePlugin.ts b/vscode/packages/npm-package/lib/generatePlugin.ts index fcb44b505f..fc4382da21 100644 --- a/vscode/packages/npm-package/lib/generatePlugin.ts +++ b/vscode/packages/npm-package/lib/generatePlugin.ts @@ -6,21 +6,22 @@ import { KiotaPluginType, GeneratePluginResult } from "../types"; import * as path from "path"; export interface PluginGenerationOptions { - descriptionPath: string; - outputPath: string; - pluginName: string; - operation: ConsumerOperation; - workingDirectory: string; + descriptionPath: string; + outputPath: string; + pluginName: string; + operation: ConsumerOperation; + workingDirectory: string; - pluginType?: KiotaPluginType; - includePatterns?: string[]; - excludePatterns?: string[]; - clearCache?: boolean; - cleanOutput?: boolean; - disabledValidationRules?: string[]; - noWorkspace?: boolean; - pluginAuthType?: PluginAuthType | null; - pluginAuthRefid?: string; + pluginType?: KiotaPluginType; + includePatterns?: string[]; + excludePatterns?: string[]; + clearCache?: boolean; + cleanOutput?: boolean; + disabledValidationRules?: string[]; + noWorkspace?: boolean; + pluginAuthType?: PluginAuthType | null; + pluginAuthRefid?: string; + overlays?: string[]; } /** @@ -41,6 +42,7 @@ export interface PluginGenerationOptions { * @param {boolean} [pluginGenerationOptions.noWorkspace] - Whether to generate without a workspace. * @param {PluginAuthType | null} [pluginGenerationOptions.pluginAuthType] - The authentication type for the plugin, if any. * @param {string} [pluginGenerationOptions.pluginAuthRefid] - The reference ID for the plugin authentication, if any. + * @param {string[]} [pluginGenerationOptions.overlays] - List of overlays to use in the generation process. * @returns {Promise} A promise that resolves to a KiotaResult if successful, or undefined if not. * @throws {Error} If an error occurs during the generation process. * @@ -49,50 +51,51 @@ export interface PluginGenerationOptions { */ export async function generatePlugin(pluginGenerationOptions: PluginGenerationOptions ): Promise { - const pluginType = pluginGenerationOptions.pluginType ?? KiotaPluginType.ApiPlugin; - const result = await connectToKiota(async (connection) => { - const request = new rpc.RequestType1( - "GeneratePlugin" - ); - return await connection.sendRequest( - request, - { - openAPIFilePath: pluginGenerationOptions.descriptionPath, - outputPath: pluginGenerationOptions.outputPath, - operation: pluginGenerationOptions.operation, - clientClassName: pluginGenerationOptions.pluginName, + const pluginType = pluginGenerationOptions.pluginType ?? KiotaPluginType.ApiPlugin; + const result = await connectToKiota(async (connection) => { + const request = new rpc.RequestType1( + "GeneratePlugin" + ); + return await connection.sendRequest( + request, + { + openAPIFilePath: pluginGenerationOptions.descriptionPath, + outputPath: pluginGenerationOptions.outputPath, + operation: pluginGenerationOptions.operation, + clientClassName: pluginGenerationOptions.pluginName, - pluginTypes: [pluginType], - cleanOutput: pluginGenerationOptions.cleanOutput ?? false, - clearCache: pluginGenerationOptions.clearCache ?? false, - disabledValidationRules: pluginGenerationOptions.disabledValidationRules ?? [], - excludePatterns: pluginGenerationOptions.excludePatterns ?? [], - includePatterns: pluginGenerationOptions.includePatterns ?? [], - noWorkspace: pluginGenerationOptions.noWorkspace ?? null, - pluginAuthType: pluginGenerationOptions.pluginAuthType ?? null, - pluginAuthRefid: pluginGenerationOptions.pluginAuthRefid ?? '', - } as GenerationConfiguration, - ); - }, pluginGenerationOptions.workingDirectory); + pluginTypes: [pluginType], + cleanOutput: pluginGenerationOptions.cleanOutput ?? false, + clearCache: pluginGenerationOptions.clearCache ?? false, + disabledValidationRules: pluginGenerationOptions.disabledValidationRules ?? [], + excludePatterns: pluginGenerationOptions.excludePatterns ?? [], + includePatterns: pluginGenerationOptions.includePatterns ?? [], + noWorkspace: pluginGenerationOptions.noWorkspace ?? null, + pluginAuthType: pluginGenerationOptions.pluginAuthType ?? null, + pluginAuthRefid: pluginGenerationOptions.pluginAuthRefid ?? '', + overlays: pluginGenerationOptions.overlays ?? [], + } as GenerationConfiguration, + ); + }, pluginGenerationOptions.workingDirectory); - if (result instanceof Error) { - throw result; - } + if (result instanceof Error) { + throw result; + } - if (result) { - const outputPath = pluginGenerationOptions.outputPath; - const pluginName = pluginGenerationOptions.pluginName; - const pathOfSpec = path.join(outputPath, `${pluginName.toLowerCase()}-openapi.yml`); - const plugingTypeName = KiotaPluginType[pluginType]; - const pathPluginManifest = path.join(outputPath, `${pluginName.toLowerCase()}-${plugingTypeName.toLowerCase()}.json`); - return { - aiPlugin: pathPluginManifest, - openAPISpec: pathOfSpec, - isSuccess: checkForSuccess(result as KiotaLogEntry[]), - logs: result - }; - } + if (result) { + const outputPath = pluginGenerationOptions.outputPath; + const pluginName = pluginGenerationOptions.pluginName; + const pathOfSpec = path.join(outputPath, `${pluginName.toLowerCase()}-openapi.yml`); + const plugingTypeName = KiotaPluginType[pluginType]; + const pathPluginManifest = path.join(outputPath, `${pluginName.toLowerCase()}-${plugingTypeName.toLowerCase()}.json`); + return { + aiPlugin: pathPluginManifest, + openAPISpec: pathOfSpec, + isSuccess: checkForSuccess(result as KiotaLogEntry[]), + logs: result + }; + } - return undefined; + return undefined; }; diff --git a/vscode/packages/npm-package/types.ts b/vscode/packages/npm-package/types.ts index eaed5534a9..86eaead38a 100644 --- a/vscode/packages/npm-package/types.ts +++ b/vscode/packages/npm-package/types.ts @@ -1,182 +1,182 @@ export enum KiotaGenerationLanguage { - // eslint-disable-next-line @typescript-eslint/naming-convention - CSharp = 0, - // eslint-disable-next-line @typescript-eslint/naming-convention - Java = 1, - // eslint-disable-next-line @typescript-eslint/naming-convention - TypeScript = 2, - // eslint-disable-next-line @typescript-eslint/naming-convention - PHP = 3, - // eslint-disable-next-line @typescript-eslint/naming-convention - Python = 4, - // eslint-disable-next-line @typescript-eslint/naming-convention - Go = 5, - // eslint-disable-next-line @typescript-eslint/naming-convention - Ruby = 6, - // eslint-disable-next-line @typescript-eslint/naming-convention - Dart = 7, - // eslint-disable-next-line @typescript-eslint/naming-convention - HTTP = 8, + // eslint-disable-next-line @typescript-eslint/naming-convention + CSharp = 0, + // eslint-disable-next-line @typescript-eslint/naming-convention + Java = 1, + // eslint-disable-next-line @typescript-eslint/naming-convention + TypeScript = 2, + // eslint-disable-next-line @typescript-eslint/naming-convention + PHP = 3, + // eslint-disable-next-line @typescript-eslint/naming-convention + Python = 4, + // eslint-disable-next-line @typescript-eslint/naming-convention + Go = 5, + // eslint-disable-next-line @typescript-eslint/naming-convention + Ruby = 6, + // eslint-disable-next-line @typescript-eslint/naming-convention + Dart = 7, + // eslint-disable-next-line @typescript-eslint/naming-convention + HTTP = 8, } export enum KiotaPluginType { - // eslint-disable-next-line @typescript-eslint/naming-convention - OpenAI = 0, - // eslint-disable-next-line @typescript-eslint/naming-convention - ApiManifest = 1, - // eslint-disable-next-line @typescript-eslint/naming-convention - ApiPlugin = 2, + // eslint-disable-next-line @typescript-eslint/naming-convention + OpenAI = 0, + // eslint-disable-next-line @typescript-eslint/naming-convention + ApiManifest = 1, + // eslint-disable-next-line @typescript-eslint/naming-convention + ApiPlugin = 2, } export interface KiotaLogEntry { - level: LogLevel; - message: string; + level: LogLevel; + message: string; } export enum OpenApiAuthType { - None = 0, - ApiKey = 1, - Http = 2, - OAuth2 = 3, - OpenIdConnect = 4, + None = 0, + ApiKey = 1, + Http = 2, + OAuth2 = 3, + OpenIdConnect = 4, } // key is the security scheme name, value is array of scopes export interface SecurityRequirementObject { - [name: string]: string[]; + [name: string]: string[]; } export interface KiotaOpenApiNode { - segment: string; - path: string; - children: KiotaOpenApiNode[]; - operationId?: string; - summary?: string; - description?: string; - selected?: boolean; - isOperation?: boolean; - documentationUrl?: string; - clientNameOrPluginName?: string; - authType?: OpenApiAuthType; - logs?: KiotaLogEntry[]; - servers?: string[]; - security?: SecurityRequirementObject[]; - adaptiveCard?: AdaptiveCardInfo; + segment: string; + path: string; + children: KiotaOpenApiNode[]; + operationId?: string; + summary?: string; + description?: string; + selected?: boolean; + isOperation?: boolean; + documentationUrl?: string; + clientNameOrPluginName?: string; + authType?: OpenApiAuthType; + logs?: KiotaLogEntry[]; + servers?: string[]; + security?: SecurityRequirementObject[]; + adaptiveCard?: AdaptiveCardInfo; } export interface AdaptiveCardInfo { - dataPath: string; - file: string; + dataPath: string; + file: string; } export interface CacheClearableConfiguration { - clearCache: boolean; + clearCache: boolean; } export interface KiotaShowConfiguration extends CacheClearableConfiguration { - includeFilters: string[]; - excludeFilters: string[]; - descriptionPath: string; - includeKiotaValidationRules: boolean; + includeFilters: string[]; + excludeFilters: string[]; + descriptionPath: string; + includeKiotaValidationRules: boolean; } export interface KiotaGetManifestDetailsConfiguration - extends CacheClearableConfiguration { - manifestPath: string; - apiIdentifier: string; + extends CacheClearableConfiguration { + manifestPath: string; + apiIdentifier: string; } export interface KiotaLoggedResult { - logs: KiotaLogEntry[]; + logs: KiotaLogEntry[]; } export enum OpenApiSpecVersion { - // eslint-disable-next-line @typescript-eslint/naming-convention - V2_0 = 0, - // eslint-disable-next-line @typescript-eslint/naming-convention - V3_0 = 1, - // eslint-disable-next-line @typescript-eslint/naming-convention - V3_1 = 2, + // eslint-disable-next-line @typescript-eslint/naming-convention + V2_0 = 0, + // eslint-disable-next-line @typescript-eslint/naming-convention + V3_0 = 1, + // eslint-disable-next-line @typescript-eslint/naming-convention + V3_1 = 2, } export interface KiotaTreeResult extends KiotaLoggedResult { - specVersion: OpenApiSpecVersion; - rootNode?: KiotaOpenApiNode; - apiTitle?: string; - servers?: string[]; - security?: SecurityRequirementObject[]; - securitySchemes?: { [key: string]: SecuritySchemeObject }; + specVersion: OpenApiSpecVersion; + rootNode?: KiotaOpenApiNode; + apiTitle?: string; + servers?: string[]; + security?: SecurityRequirementObject[]; + securitySchemes?: { [key: string]: SecuritySchemeObject }; } export interface KiotaManifestResult extends KiotaLoggedResult { - apiDescriptionPath?: string; - selectedPaths?: string[]; + apiDescriptionPath?: string; + selectedPaths?: string[]; } export interface KiotaSearchResult extends KiotaLoggedResult { - results: Record; + results: Record; } export interface KiotaSearchResultItem { - // eslint-disable-next-line @typescript-eslint/naming-convention - Title: string; - // eslint-disable-next-line @typescript-eslint/naming-convention - Description: string; - // eslint-disable-next-line @typescript-eslint/naming-convention - ServiceUrl?: string; - // eslint-disable-next-line @typescript-eslint/naming-convention - DescriptionUrl?: string; - // eslint-disable-next-line @typescript-eslint/naming-convention - VersionLabels?: string[]; + // eslint-disable-next-line @typescript-eslint/naming-convention + Title: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + Description: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + ServiceUrl?: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + DescriptionUrl?: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + VersionLabels?: string[]; } export enum ConsumerOperation { - // eslint-disable-next-line @typescript-eslint/naming-convention - Add, - // eslint-disable-next-line @typescript-eslint/naming-convention - Edit, - // eslint-disable-next-line @typescript-eslint/naming-convention - Remove, - // eslint-disable-next-line @typescript-eslint/naming-convention - Generate, + // eslint-disable-next-line @typescript-eslint/naming-convention + Add, + // eslint-disable-next-line @typescript-eslint/naming-convention + Edit, + // eslint-disable-next-line @typescript-eslint/naming-convention + Remove, + // eslint-disable-next-line @typescript-eslint/naming-convention + Generate, } export function generationLanguageToString( - language: KiotaGenerationLanguage + language: KiotaGenerationLanguage ): string { - switch (language) { - case KiotaGenerationLanguage.CSharp: - return "CSharp"; - case KiotaGenerationLanguage.Java: - return "Java"; - case KiotaGenerationLanguage.TypeScript: - return "TypeScript"; - case KiotaGenerationLanguage.PHP: - return "PHP"; - case KiotaGenerationLanguage.Python: - return "Python"; - case KiotaGenerationLanguage.Go: - return "Go"; - case KiotaGenerationLanguage.Ruby: - return "Ruby"; - case KiotaGenerationLanguage.Dart: - return "Dart"; - case KiotaGenerationLanguage.HTTP: - return "HTTP"; - default: - throw new Error("unknown language"); - } + switch (language) { + case KiotaGenerationLanguage.CSharp: + return "CSharp"; + case KiotaGenerationLanguage.Java: + return "Java"; + case KiotaGenerationLanguage.TypeScript: + return "TypeScript"; + case KiotaGenerationLanguage.PHP: + return "PHP"; + case KiotaGenerationLanguage.Python: + return "Python"; + case KiotaGenerationLanguage.Go: + return "Go"; + case KiotaGenerationLanguage.Ruby: + return "Ruby"; + case KiotaGenerationLanguage.Dart: + return "Dart"; + case KiotaGenerationLanguage.HTTP: + return "HTTP"; + default: + throw new Error("unknown language"); + } } export const allGenerationLanguages = [ - KiotaGenerationLanguage.CSharp, - KiotaGenerationLanguage.Go, - KiotaGenerationLanguage.Java, - KiotaGenerationLanguage.PHP, - KiotaGenerationLanguage.Python, - KiotaGenerationLanguage.Ruby, - KiotaGenerationLanguage.TypeScript, - KiotaGenerationLanguage.Dart, - KiotaGenerationLanguage.HTTP, + KiotaGenerationLanguage.CSharp, + KiotaGenerationLanguage.Go, + KiotaGenerationLanguage.Java, + KiotaGenerationLanguage.PHP, + KiotaGenerationLanguage.Python, + KiotaGenerationLanguage.Ruby, + KiotaGenerationLanguage.TypeScript, + KiotaGenerationLanguage.Dart, + KiotaGenerationLanguage.HTTP, ]; /** @@ -184,207 +184,208 @@ export const allGenerationLanguages = [ * @see https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.logging.loglevel?view=dotnet-plat-ext-7.0 */ export enum LogLevel { - trace = 0, - debug = 1, - information = 2, - warning = 3, - error = 4, - critical = 5, - none = 6, + trace = 0, + debug = 1, + information = 2, + warning = 3, + error = 4, + critical = 5, + none = 6, } export interface LanguageInformation { - // eslint-disable-next-line @typescript-eslint/naming-convention - MaturityLevel: MaturityLevel; - // eslint-disable-next-line @typescript-eslint/naming-convention - Dependencies: LanguageDependency[]; - // eslint-disable-next-line @typescript-eslint/naming-convention - DependencyInstallCommand: string; - // eslint-disable-next-line @typescript-eslint/naming-convention - ClientNamespaceName: string; - // eslint-disable-next-line @typescript-eslint/naming-convention - ClientClassName: string; - // eslint-disable-next-line @typescript-eslint/naming-convention - StructuredMimeTypes: string[]; + // eslint-disable-next-line @typescript-eslint/naming-convention + MaturityLevel: MaturityLevel; + // eslint-disable-next-line @typescript-eslint/naming-convention + Dependencies: LanguageDependency[]; + // eslint-disable-next-line @typescript-eslint/naming-convention + DependencyInstallCommand: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + ClientNamespaceName: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + ClientClassName: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + StructuredMimeTypes: string[]; } export interface LanguageDependency { - // eslint-disable-next-line @typescript-eslint/naming-convention - Name: string; - // eslint-disable-next-line @typescript-eslint/naming-convention - Version: string; - // eslint-disable-next-line @typescript-eslint/naming-convention - DependencyType: DependencyType; + // eslint-disable-next-line @typescript-eslint/naming-convention + Name: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + Version: string; + // eslint-disable-next-line @typescript-eslint/naming-convention + DependencyType: DependencyType; } export enum MaturityLevel { - experimental = 0, - preview = 1, - stable = 2, + experimental = 0, + preview = 1, + stable = 2, } export enum DependencyType { - abstractions, - serialization, - authentication, - http, - bundle, - additional, + abstractions, + serialization, + authentication, + http, + bundle, + additional, } export interface ConfigurationFile { - version: string; - clients: Record; - plugins: Record; + version: string; + clients: Record; + plugins: Record; } export interface GenerationConfiguration { - cleanOutput: boolean; - clearCache: boolean; - clientClassName: string; - clientNamespaceName: string; - deserializers: string[]; - disabledValidationRules: string[]; - excludeBackwardCompatible: boolean; - excludePatterns: string[]; - includeAdditionalData: boolean; - includePatterns: string[]; - language: KiotaGenerationLanguage; - openAPIFilePath: string; - outputPath: string; - serializers: string[]; - structuredMimeTypes: string[]; - usesBackingStore: boolean; - pluginTypes: KiotaPluginType[]; - operation: ConsumerOperation; - noWorkspace?: boolean; - pluginAuthRefid?: string; - pluginAuthType?: PluginAuthType | null; + cleanOutput: boolean; + clearCache: boolean; + clientClassName: string; + clientNamespaceName: string; + deserializers: string[]; + disabledValidationRules: string[]; + excludeBackwardCompatible: boolean; + excludePatterns: string[]; + includeAdditionalData: boolean; + includePatterns: string[]; + language: KiotaGenerationLanguage; + openAPIFilePath: string; + outputPath: string; + serializers: string[]; + structuredMimeTypes: string[]; + usesBackingStore: boolean; + pluginTypes: KiotaPluginType[]; + operation: ConsumerOperation; + noWorkspace?: boolean; + pluginAuthRefid?: string; + pluginAuthType?: PluginAuthType | null; + overlays?: string[]; } export enum PluginAuthType { - oAuthPluginVault = "OAuthPluginVault", - apiKeyPluginVault = "ApiKeyPluginVault", + oAuthPluginVault = "OAuthPluginVault", + apiKeyPluginVault = "ApiKeyPluginVault", } export interface WorkspaceObjectProperties { - descriptionLocation: string; - includePatterns: string[]; - excludePatterns: string[]; - outputPath: string; + descriptionLocation: string; + includePatterns: string[]; + excludePatterns: string[]; + outputPath: string; } export interface ClientObjectProperties extends WorkspaceObjectProperties { - language: string; - structuredMimeTypes: string[]; - clientNamespaceName: string; - usesBackingStore: boolean; - includeAdditionalData: boolean; - excludeBackwardCompatible: boolean; - disabledValidationRules: string[]; + language: string; + structuredMimeTypes: string[]; + clientNamespaceName: string; + usesBackingStore: boolean; + includeAdditionalData: boolean; + excludeBackwardCompatible: boolean; + disabledValidationRules: string[]; } export interface PluginObjectProperties extends WorkspaceObjectProperties { - types: string[]; - authType?: PluginAuthType; - authReferenceId?: string; + types: string[]; + authType?: PluginAuthType; + authReferenceId?: string; } export type ClientOrPluginProperties = - | ClientObjectProperties - | PluginObjectProperties; + | ClientObjectProperties + | PluginObjectProperties; export interface LanguagesInformation { - [key: string]: LanguageInformation; + [key: string]: LanguageInformation; } export interface KiotaResult extends KiotaLoggedResult { - isSuccess: boolean; + isSuccess: boolean; } -export interface ValidateOpenApiResult extends KiotaLoggedResult {} +export interface ValidateOpenApiResult extends KiotaLoggedResult { } export interface GeneratePluginResult extends KiotaResult { - aiPlugin: string; - openAPISpec: string; + aiPlugin: string; + openAPISpec: string; } export interface PluginManifestResult extends KiotaResult { - isValid: boolean; - schema_version: string; - name_for_human: string; - functions: PluginFunction[]; - runtime: PluginRuntime[]; + isValid: boolean; + schema_version: string; + name_for_human: string; + functions: PluginFunction[]; + runtime: PluginRuntime[]; } export interface PluginFunction { - name: string; - description: string; + name: string; + description: string; } export interface PluginAuth { - type: string; // None, OAuthPluginVault, ApiKeyPluginVault - reference_id?: string; + type: string; // None, OAuthPluginVault, ApiKeyPluginVault + reference_id?: string; } export interface PluginRuntime { - type: string; - auth: PluginAuth; - run_for_functions: string[]; + type: string; + auth: PluginAuth; + run_for_functions: string[]; } export type SecuritySchemeObject = - | HttpSecurityScheme - | ApiKeySecurityScheme - | OAuth2SecurityScheme - | OpenIdSecurityScheme; + | HttpSecurityScheme + | ApiKeySecurityScheme + | OAuth2SecurityScheme + | OpenIdSecurityScheme; export interface AuthReferenceId { - referenceId: string; + referenceId: string; } export interface HttpSecurityScheme extends AuthReferenceId { - type: "http"; - description?: string; - scheme: string; - bearerFormat?: string; + type: "http"; + description?: string; + scheme: string; + bearerFormat?: string; } export interface ApiKeySecurityScheme extends AuthReferenceId { - type: "apiKey"; - description?: string; - name: string; - in: string; + type: "apiKey"; + description?: string; + name: string; + in: string; } export interface OAuth2SecurityScheme extends AuthReferenceId { - type: "oauth2"; - description?: string; - flows: { - implicit?: { - authorizationUrl: string; - refreshUrl?: string; - scopes: { [scope: string]: string }; + type: "oauth2"; + description?: string; + flows: { + implicit?: { + authorizationUrl: string; + refreshUrl?: string; + scopes: { [scope: string]: string }; + }; + password?: { + tokenUrl: string; + refreshUrl?: string; + scopes: { [scope: string]: string }; + }; + clientCredentials?: { + tokenUrl: string; + refreshUrl?: string; + scopes: { [scope: string]: string }; + }; + authorizationCode?: { + authorizationUrl: string; + tokenUrl: string; + refreshUrl?: string; + scopes: { [scope: string]: string }; + }; }; - password?: { - tokenUrl: string; - refreshUrl?: string; - scopes: { [scope: string]: string }; - }; - clientCredentials?: { - tokenUrl: string; - refreshUrl?: string; - scopes: { [scope: string]: string }; - }; - authorizationCode?: { - authorizationUrl: string; - tokenUrl: string; - refreshUrl?: string; - scopes: { [scope: string]: string }; - }; - }; } export interface OpenIdSecurityScheme extends AuthReferenceId { - type: "openIdConnect"; - description?: string; - openIdConnectUrl: string; + type: "openIdConnect"; + description?: string; + openIdConnectUrl: string; }