diff --git a/.vscode/launch.json b/.vscode/launch.json index e5404b0746..e04fc3d4b1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -266,7 +266,7 @@ "stopAtEntry": false }, { - "name": "Launch Migrate", + "name": "Launch Client Migrate", "type": "coreclr", "request": "launch", "preLaunchTask": "build", @@ -279,6 +279,41 @@ "KIOTA_CONFIG_PREVIEW": "true" } }, + { + "name": "Launch Client Generate", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/src/kiota/bin/Debug/net8.0/kiota.dll", + "args": ["client", "generate"], + "cwd": "${workspaceFolder}/samples/msgraph-mail/dotnet", + "console": "internalConsole", + "stopAtEntry": false, + "env": { + "KIOTA_CONFIG_PREVIEW": "true" + } + }, + { + "name": "Launch Client Edit", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/src/kiota/bin/Debug/net8.0/kiota.dll", + "args": [ + "client", + "edit", + "--client-name", + "GraphClient", + "--additional-data", + "true" + ], + "cwd": "${workspaceFolder}/samples/msgraph-mail/dotnet", + "console": "internalConsole", + "stopAtEntry": false, + "env": { + "KIOTA_CONFIG_PREVIEW": "true" + } + }, { "name": "Launch Login (github - device)", "type": "coreclr", diff --git a/CHANGELOG.md b/CHANGELOG.md index 79f059e888..7d649bdd4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +### Changed + +## [1.12.0] - 2024-03-06 + +### Added + +- Added support for the new kiota config commands under a feature flag. [#3356](https://github.com/microsoft/kiota/issues/3356) - Added the init command as part of the experience revamp of [#3356](https://github.com/microsoft/kiota/issues/3356) - Added uri-form encoded serialization for Python. [#2075](https://github.com/microsoft/kiota/issues/2075) - Added support for multipart form data request body in Python. [#3030](https://github.com/microsoft/kiota/issues/3030) @@ -1272,3 +1279,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 + diff --git a/src/Kiota.Builder/WorkspaceManagement/ApiClientConfiguration.cs b/src/Kiota.Builder/WorkspaceManagement/ApiClientConfiguration.cs index d47759564f..6ef6800a7c 100644 --- a/src/Kiota.Builder/WorkspaceManagement/ApiClientConfiguration.cs +++ b/src/Kiota.Builder/WorkspaceManagement/ApiClientConfiguration.cs @@ -3,7 +3,6 @@ using System.IO; using System.Linq; using Kiota.Builder.Configuration; -using Kiota.Builder.Lock; using Microsoft.OpenApi.ApiManifest; namespace Kiota.Builder.WorkspaceManagement; diff --git a/src/Kiota.Builder/WorkspaceManagement/DescriptionStorageService.cs b/src/Kiota.Builder/WorkspaceManagement/DescriptionStorageService.cs index 1bdd930b69..9a5e6006ce 100644 --- a/src/Kiota.Builder/WorkspaceManagement/DescriptionStorageService.cs +++ b/src/Kiota.Builder/WorkspaceManagement/DescriptionStorageService.cs @@ -59,4 +59,10 @@ public void RemoveDescription(string clientName, string extension = "yml") if (File.Exists(descriptionFilePath)) File.Delete(descriptionFilePath); } + public void Clean() + { + var kiotaDirectoryPath = Path.Combine(TargetDirectory, DescriptionsSubDirectoryRelativePath); + if (Path.Exists(kiotaDirectoryPath)) + Directory.Delete(kiotaDirectoryPath, true); + } } diff --git a/src/Kiota.Builder/WorkspaceManagement/WorkspaceManagementService.cs b/src/Kiota.Builder/WorkspaceManagement/WorkspaceManagementService.cs index 9a68d50f41..a010b2ec2f 100644 --- a/src/Kiota.Builder/WorkspaceManagement/WorkspaceManagementService.cs +++ b/src/Kiota.Builder/WorkspaceManagement/WorkspaceManagementService.cs @@ -143,6 +143,8 @@ public async Task RemoveClientAsync(string clientName, bool cleanOutput = false, manifest?.ApiDependencies.Remove(clientName); await workspaceConfigurationStorageService.UpdateWorkspaceConfigurationAsync(wsConfig, manifest, cancellationToken).ConfigureAwait(false); descriptionStorageService.RemoveDescription(clientName); + if (wsConfig.Clients.Count == 0) + descriptionStorageService.Clean(); } private static readonly JsonSerializerOptions options = new() { @@ -226,15 +228,11 @@ public async Task> MigrateFromLockFileAsync(string clientNam } var (stream, _) = await openApiDocumentDownloadService.LoadStreamAsync(generationConfiguration.OpenAPIFilePath, generationConfiguration, null, false, cancellationToken).ConfigureAwait(false); #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await using var msForOpenAPIDocument = new MemoryStream(); // openapi.net doesn't honour leave open await using var ms = new MemoryStream(); #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task - await stream.CopyToAsync(msForOpenAPIDocument, cancellationToken).ConfigureAwait(false); - msForOpenAPIDocument.Seek(0, SeekOrigin.Begin); - await msForOpenAPIDocument.CopyToAsync(ms, cancellationToken).ConfigureAwait(false); + await stream.CopyToAsync(ms, cancellationToken).ConfigureAwait(false); ms.Seek(0, SeekOrigin.Begin); - msForOpenAPIDocument.Seek(0, SeekOrigin.Begin); - var document = await openApiDocumentDownloadService.GetDocumentFromStreamAsync(msForOpenAPIDocument, generationConfiguration, false, cancellationToken).ConfigureAwait(false); + var document = await openApiDocumentDownloadService.GetDocumentFromStreamAsync(ms, generationConfiguration, false, cancellationToken).ConfigureAwait(false); if (document is null) { Logger.LogError("The client {ClientName} could not be migrated because the OpenAPI document could not be loaded", generationConfiguration.ClientClassName); @@ -242,6 +240,7 @@ public async Task> MigrateFromLockFileAsync(string clientNam continue; } generationConfiguration.ApiRootUrl = document.GetAPIRootUrl(generationConfiguration.OpenAPIFilePath); + ms.Seek(0, SeekOrigin.Begin); await descriptionStorageService.UpdateDescriptionAsync(generationConfiguration.ClientClassName, ms, new Uri(generationConfiguration.OpenAPIFilePath).GetFileExtension(), cancellationToken).ConfigureAwait(false); var clientConfiguration = new ApiClientConfiguration(generationConfiguration); diff --git a/src/kiota/Handlers/BaseKiotaCommandHandler.cs b/src/kiota/Handlers/BaseKiotaCommandHandler.cs index f22034eecf..5908458894 100644 --- a/src/kiota/Handlers/BaseKiotaCommandHandler.cs +++ b/src/kiota/Handlers/BaseKiotaCommandHandler.cs @@ -20,6 +20,12 @@ namespace kiota.Handlers; internal abstract class BaseKiotaCommandHandler : ICommandHandler, IDisposable { + protected static void DefaultSerializersAndDeserializers(GenerationConfiguration generationConfiguration) + { // needed until we have rollup packages + var defaultGenerationConfiguration = new GenerationConfiguration(); + generationConfiguration.Serializers = defaultGenerationConfiguration.Serializers; + generationConfiguration.Deserializers = defaultGenerationConfiguration.Deserializers; + } protected TempFolderCachingAccessTokenProvider GetGitHubDeviceStorageService(ILogger logger) => new() { Logger = logger, diff --git a/src/kiota/Handlers/Client/AddHandler.cs b/src/kiota/Handlers/Client/AddHandler.cs index deb60bb8a4..16cf680451 100644 --- a/src/kiota/Handlers/Client/AddHandler.cs +++ b/src/kiota/Handlers/Client/AddHandler.cs @@ -81,10 +81,10 @@ public override async Task InvokeAsync(InvocationContext context) bool skipGeneration = context.ParseResult.GetValueForOption(SkipGenerationOption); string className = context.ParseResult.GetValueForOption(ClassOption) ?? string.Empty; string namespaceName = context.ParseResult.GetValueForOption(NamespaceOption) ?? string.Empty; - List includePatterns = context.ParseResult.GetValueForOption(IncludePatternsOption) ?? new List(); - List excludePatterns = context.ParseResult.GetValueForOption(ExcludePatternsOption) ?? new List(); - List disabledValidationRules = context.ParseResult.GetValueForOption(DisabledValidationRulesOption) ?? new List(); - List structuredMimeTypes = context.ParseResult.GetValueForOption(StructuredMimeTypesOption) ?? new List(); + List includePatterns = context.ParseResult.GetValueForOption(IncludePatternsOption) ?? []; + List excludePatterns = context.ParseResult.GetValueForOption(ExcludePatternsOption) ?? []; + List disabledValidationRules = context.ParseResult.GetValueForOption(DisabledValidationRulesOption) ?? []; + List structuredMimeTypes = context.ParseResult.GetValueForOption(StructuredMimeTypesOption) ?? []; CancellationToken cancellationToken = context.BindingContext.GetService(typeof(CancellationToken)) is CancellationToken token ? token : CancellationToken.None; AssignIfNotNullOrEmpty(output, (c, s) => c.OutputPath = s); AssignIfNotNullOrEmpty(openapi, (c, s) => c.OpenAPIFilePath = s); diff --git a/src/kiota/Handlers/Client/EditHandler.cs b/src/kiota/Handlers/Client/EditHandler.cs new file mode 100644 index 0000000000..c989d270d0 --- /dev/null +++ b/src/kiota/Handlers/Client/EditHandler.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.Invocation; +using System.IO; +using System.Linq; +using System.Text.Json; +using System.Threading; +using System.Threading.Tasks; +using Kiota.Builder; +using Kiota.Builder.Configuration; +using Kiota.Builder.Extensions; +using Kiota.Builder.WorkspaceManagement; +using Microsoft.Extensions.Logging; + +namespace kiota.Handlers.Client; + +internal class EditHandler : BaseKiotaCommandHandler +{ + public required Option ClassOption + { + get; init; + } + public required Option BackingStoreOption + { + get; init; + } + public required Option OutputOption + { + get; init; + } + public required Option LanguageOption + { + get; init; + } + public required Option DescriptionOption + { + get; init; + } + public required Option NamespaceOption + { + get; init; + } + public required Option AdditionalDataOption + { + get; init; + } + public required Option> DisabledValidationRulesOption + { + get; init; + } + public required Option> StructuredMimeTypesOption + { + get; init; + } + public required Option ExcludeBackwardCompatibleOption + { + get; + set; + } + public required Option> IncludePatternsOption + { + get; init; + } + public required Option> ExcludePatternsOption + { + get; init; + } + public required Option SkipGenerationOption + { + get; init; + } + + public override async Task InvokeAsync(InvocationContext context) + { + string output = context.ParseResult.GetValueForOption(OutputOption) ?? string.Empty; + GenerationLanguage? language = context.ParseResult.GetValueForOption(LanguageOption); + string openapi = context.ParseResult.GetValueForOption(DescriptionOption) ?? string.Empty; + bool? backingStore = context.ParseResult.GetValueForOption(BackingStoreOption); + bool? excludeBackwardCompatible = context.ParseResult.GetValueForOption(ExcludeBackwardCompatibleOption); + bool? includeAdditionalData = context.ParseResult.GetValueForOption(AdditionalDataOption); + bool skipGeneration = context.ParseResult.GetValueForOption(SkipGenerationOption); + string className = context.ParseResult.GetValueForOption(ClassOption) ?? string.Empty; + string namespaceName = context.ParseResult.GetValueForOption(NamespaceOption) ?? string.Empty; + List? includePatterns = context.ParseResult.GetValueForOption(IncludePatternsOption); + List? excludePatterns = context.ParseResult.GetValueForOption(ExcludePatternsOption); + List? disabledValidationRules = context.ParseResult.GetValueForOption(DisabledValidationRulesOption); + List? structuredMimeTypes = context.ParseResult.GetValueForOption(StructuredMimeTypesOption); + CancellationToken cancellationToken = context.BindingContext.GetService(typeof(CancellationToken)) is CancellationToken token ? token : CancellationToken.None; + + Configuration.Generation.SkipGeneration = skipGeneration; + Configuration.Generation.Operation = ClientOperation.Edit; + + var (loggerFactory, logger) = GetLoggerAndFactory(context, Configuration.Generation.OutputPath); + using (loggerFactory) + { + await CheckForNewVersionAsync(logger, cancellationToken).ConfigureAwait(false); + logger.AppendInternalTracing(); + logger.LogTrace("configuration: {configuration}", JsonSerializer.Serialize(Configuration, KiotaConfigurationJsonContext.Default.KiotaConfiguration)); + + try + { + var workspaceStorageService = new WorkspaceConfigurationStorageService(Directory.GetCurrentDirectory()); + var (config, _) = await workspaceStorageService.GetWorkspaceConfigurationAsync(cancellationToken).ConfigureAwait(false); + if (config == null) + { + DisplayError("The workspace configuration is missing, please run the init command first."); + return 1; + } + if (!config.Clients.TryGetValue(className, out var clientConfiguration)) + { + DisplayError($"No client found with the provided name {className}"); + return 1; + } + clientConfiguration.UpdateGenerationConfigurationFromApiClientConfiguration(Configuration.Generation, className); + if (language.HasValue) + Configuration.Generation.Language = language.Value; + if (backingStore.HasValue) + Configuration.Generation.UsesBackingStore = backingStore.Value; + if (excludeBackwardCompatible.HasValue) + 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); + AssignIfNotNullOrEmpty(namespaceName, (c, s) => c.ClientNamespaceName = s); + if (includePatterns is { Count: > 0 }) + Configuration.Generation.IncludePatterns = includePatterns.Select(static x => x.TrimQuotes()).ToHashSet(StringComparer.OrdinalIgnoreCase); + if (excludePatterns is { Count: > 0 }) + Configuration.Generation.ExcludePatterns = excludePatterns.Select(static x => x.TrimQuotes()).ToHashSet(StringComparer.OrdinalIgnoreCase); + if (disabledValidationRules is { Count: > 0 }) + Configuration.Generation.DisabledValidationRules = disabledValidationRules + .Select(static x => x.TrimQuotes()) + .SelectMany(static x => x.Split(',', StringSplitOptions.RemoveEmptyEntries)) + .ToHashSet(StringComparer.OrdinalIgnoreCase); + if (structuredMimeTypes is { Count: > 0 }) + Configuration.Generation.StructuredMimeTypes = new(structuredMimeTypes.SelectMany(static x => x.Split(' ', StringSplitOptions.RemoveEmptyEntries)) + .Select(static x => x.TrimQuotes())); + + DefaultSerializersAndDeserializers(Configuration.Generation); + var builder = new KiotaBuilder(logger, Configuration.Generation, httpClient, true); + var result = await builder.GenerateClientAsync(cancellationToken).ConfigureAwait(false); + if (result) + DisplaySuccess("Generation completed successfully"); + else if (skipGeneration) + { + DisplaySuccess("Generation skipped as --skip-generation was passed"); + DisplayGenerateCommandHint(); + } + else + { + DisplayWarning("Generation skipped as no changes were detected"); + DisplayCleanHint("client generate", "--refresh"); + } + var manifestPath = $"{GetAbsolutePath(WorkspaceConfigurationStorageService.ManifestFileName)}#{Configuration.Generation.ClientClassName}"; + DisplayInfoHint(Configuration.Generation.Language, string.Empty, manifestPath); + DisplayGenerateAdvancedHint(includePatterns ?? [], excludePatterns ?? [], string.Empty, manifestPath, "client edit"); + return 0; + } + catch (Exception ex) + { +#if DEBUG + logger.LogCritical(ex, "error adding the client: {exceptionMessage}", ex.Message); + throw; // so debug tools go straight to the source of the exception when attached +#else + logger.LogCritical("error adding the client: {exceptionMessage}", ex.Message); + return 1; +#endif + } + } + } +} diff --git a/src/kiota/Handlers/Client/GenerateHandler.cs b/src/kiota/Handlers/Client/GenerateHandler.cs index bf8d13a471..bde2dbb499 100644 --- a/src/kiota/Handlers/Client/GenerateHandler.cs +++ b/src/kiota/Handlers/Client/GenerateHandler.cs @@ -58,8 +58,10 @@ public override async Task InvokeAsync(InvocationContext context) var generationConfiguration = new GenerationConfiguration(); var requests = !refresh && manifest is not null && manifest.ApiDependencies.TryGetValue(clientEntry.Key, out var value) ? value.Requests : []; clientEntry.Value.UpdateGenerationConfigurationFromApiClientConfiguration(generationConfiguration, clientEntry.Key, requests); + DefaultSerializersAndDeserializers(generationConfiguration); generationConfiguration.ClearCache = refresh; generationConfiguration.CleanOutput = refresh; + generationConfiguration.Operation = ClientOperation.Generate; var builder = new KiotaBuilder(logger, generationConfiguration, httpClient, true); var result = await builder.GenerateClientAsync(cancellationToken).ConfigureAwait(false); if (result) diff --git a/src/kiota/Handlers/Client/RemoveHandler.cs b/src/kiota/Handlers/Client/RemoveHandler.cs index 693c50f03d..a4e6a190b4 100644 --- a/src/kiota/Handlers/Client/RemoveHandler.cs +++ b/src/kiota/Handlers/Client/RemoveHandler.cs @@ -31,6 +31,7 @@ public override async Task InvokeAsync(InvocationContext context) await CheckForNewVersionAsync(logger, cancellationToken).ConfigureAwait(false); var workspaceManagementService = new WorkspaceManagementService(logger, httpClient, true); await workspaceManagementService.RemoveClientAsync(className, cleanOutput, cancellationToken).ConfigureAwait(false); + DisplaySuccess($"Client {className} removed successfully!"); return 0; } catch (Exception ex) diff --git a/src/kiota/Handlers/KiotaGenerateCommandHandler.cs b/src/kiota/Handlers/KiotaGenerateCommandHandler.cs index 084bff1b97..d552a0ccb4 100644 --- a/src/kiota/Handlers/KiotaGenerateCommandHandler.cs +++ b/src/kiota/Handlers/KiotaGenerateCommandHandler.cs @@ -77,13 +77,13 @@ public override async Task InvokeAsync(InvocationContext context) bool includeAdditionalData = context.ParseResult.GetValueForOption(AdditionalDataOption); string className = context.ParseResult.GetValueForOption(ClassOption) ?? string.Empty; string namespaceName = context.ParseResult.GetValueForOption(NamespaceOption) ?? string.Empty; - List serializer = context.ParseResult.GetValueForOption(SerializerOption) ?? new List(); - List deserializer = context.ParseResult.GetValueForOption(DeserializerOption) ?? new List(); - List includePatterns = context.ParseResult.GetValueForOption(IncludePatternsOption) ?? new List(); - List excludePatterns = context.ParseResult.GetValueForOption(ExcludePatternsOption) ?? new List(); - List disabledValidationRules = context.ParseResult.GetValueForOption(DisabledValidationRulesOption) ?? new List(); + List serializer = context.ParseResult.GetValueForOption(SerializerOption) ?? []; + List deserializer = context.ParseResult.GetValueForOption(DeserializerOption) ?? []; + List includePatterns = context.ParseResult.GetValueForOption(IncludePatternsOption) ?? []; + List excludePatterns = context.ParseResult.GetValueForOption(ExcludePatternsOption) ?? []; + List disabledValidationRules = context.ParseResult.GetValueForOption(DisabledValidationRulesOption) ?? []; bool cleanOutput = context.ParseResult.GetValueForOption(CleanOutputOption); - List structuredMimeTypes = context.ParseResult.GetValueForOption(StructuredMimeTypesOption) ?? new List(); + List structuredMimeTypes = context.ParseResult.GetValueForOption(StructuredMimeTypesOption) ?? []; CancellationToken cancellationToken = context.BindingContext.GetService(typeof(CancellationToken)) is CancellationToken token ? token : CancellationToken.None; AssignIfNotNullOrEmpty(output, (c, s) => c.OutputPath = s); AssignIfNotNullOrEmpty(openapi, (c, s) => c.OpenAPIFilePath = s); diff --git a/src/kiota/KiotaClientCommands.cs b/src/kiota/KiotaClientCommands.cs index 0511a3bec9..ba3b54fff9 100644 --- a/src/kiota/KiotaClientCommands.cs +++ b/src/kiota/KiotaClientCommands.cs @@ -35,18 +35,17 @@ public static Command GetAddCommand() var languageOption = KiotaHost.GetLanguageOption(); var outputOption = KiotaHost.GetOutputPathOption(defaultConfiguration.OutputPath); var descriptionOption = KiotaHost.GetDescriptionOption(defaultConfiguration.OpenAPIFilePath, true); - var namespaceOption = KiotaHost.GetNamespaceOption(defaultConfiguration); + var namespaceOption = KiotaHost.GetNamespaceOption(defaultConfiguration.ClientNamespaceName); var logLevelOption = KiotaHost.GetLogLevelOption(); - var backingStoreOption = KiotaHost.GetBackingStoreOption(defaultConfiguration); - var excludeBackwardCompatible = KiotaHost.GetExcludeBackwardCompatibleOption(defaultConfiguration); - var additionalDataOption = KiotaHost.GetAdditionalDataOption(defaultConfiguration); - var structuredMimeTypesOption = KiotaHost.GetStructuredMimeTypesOption(defaultConfiguration); + var backingStoreOption = KiotaHost.GetBackingStoreOption(defaultConfiguration.UsesBackingStore); + var excludeBackwardCompatible = KiotaHost.GetExcludeBackwardCompatibleOption(defaultConfiguration.ExcludeBackwardCompatible); + var additionalDataOption = KiotaHost.GetAdditionalDataOption(defaultConfiguration.IncludeAdditionalData); + var structuredMimeTypesOption = KiotaHost.GetStructuredMimeTypesOption([.. defaultConfiguration.StructuredMimeTypes]); var (includePatterns, excludePatterns) = KiotaHost.GetIncludeAndExcludeOptions(defaultConfiguration.IncludePatterns, defaultConfiguration.ExcludePatterns); var dvrOption = KiotaHost.GetDisableValidationRulesOption(); var skipGenerationOption = GetSkipGenerationOption(); var clientNameOption = GetClientNameOption(); - var command = new Command("add", "Adds a new client to the Kiota configuration"){ descriptionOption, outputOption, @@ -104,8 +103,53 @@ public static Command GetRemoveCommand() } public static Command GetEditCommand() { - var command = new Command("edit", "Edits a client from the Kiota configuration"); - //TODO map the handler + var languageOption = KiotaHost.GetOptionalLanguageOption(); + var outputOption = KiotaHost.GetOutputPathOption(string.Empty); + var descriptionOption = KiotaHost.GetDescriptionOption(string.Empty); + var namespaceOption = KiotaHost.GetNamespaceOption(string.Empty); + var logLevelOption = KiotaHost.GetLogLevelOption(); + var backingStoreOption = KiotaHost.GetOptionalBackingStoreOption(); + var excludeBackwardCompatible = KiotaHost.GetOptionalExcludeBackwardCompatibleOption(); + var additionalDataOption = KiotaHost.GetOptionalAdditionalDataOption(); + var structuredMimeTypesOption = KiotaHost.GetStructuredMimeTypesOption([]); + var (includePatterns, excludePatterns) = KiotaHost.GetIncludeAndExcludeOptions([], []); + var dvrOption = KiotaHost.GetDisableValidationRulesOption(); + var skipGenerationOption = GetSkipGenerationOption(); + var clientNameOption = GetClientNameOption(); + + var command = new Command("edit", "Edits a client from the Kiota configuration") { + descriptionOption, + outputOption, + languageOption, + clientNameOption, + namespaceOption, + logLevelOption, + backingStoreOption, + excludeBackwardCompatible, + additionalDataOption, + structuredMimeTypesOption, + includePatterns, + excludePatterns, + dvrOption, + skipGenerationOption, + }; + command.Handler = new EditHandler + { + DescriptionOption = descriptionOption, + OutputOption = outputOption, + LanguageOption = languageOption, + ClassOption = clientNameOption, + NamespaceOption = namespaceOption, + LogLevelOption = logLevelOption, + BackingStoreOption = backingStoreOption, + ExcludeBackwardCompatibleOption = excludeBackwardCompatible, + AdditionalDataOption = additionalDataOption, + StructuredMimeTypesOption = structuredMimeTypesOption, + IncludePatternsOption = includePatterns, + ExcludePatternsOption = excludePatterns, + DisabledValidationRulesOption = dvrOption, + SkipGenerationOption = skipGenerationOption, + }; return command; } public static Command GetGenerateCommand() diff --git a/src/kiota/KiotaHost.cs b/src/kiota/KiotaHost.cs index 703b05ed47..ba985ba0dd 100644 --- a/src/kiota/KiotaHost.cs +++ b/src/kiota/KiotaHost.cs @@ -281,7 +281,7 @@ internal static Option GetOutputPathOption(string defaultValue) internal static Option> GetDisableValidationRulesOption() { var parameterName = "--disable-validation-rules"; - var option = new Option>(parameterName, () => new List(), "The OpenAPI description validation rules to disable. Accepts multiple values."); + var option = new Option>(parameterName, () => [], "The OpenAPI description validation rules to disable. Accepts multiple values."); option.AddAlias("--dvr"); var validationRules = new[] { nameof(DivergentResponseSchema), @@ -336,37 +336,62 @@ internal static Option GetLanguageOption() AddEnumValidator(languageOption, "language"); return languageOption; } - internal static Option GetNamespaceOption(GenerationConfiguration defaultConfiguration) + internal static Option GetOptionalLanguageOption() { - var namespaceOption = new Option("--namespace-name", () => defaultConfiguration.ClientNamespaceName, "The namespace to use for the core client class specified with the --class-name option."); + var languageOption = new Option("--language", "The target language for the generated code files."); + languageOption.AddAlias("-l"); + AddEnumValidator(languageOption, "language"); + return languageOption; + } + internal static Option GetNamespaceOption(string defaultNamespaceName) + { + var namespaceOption = new Option("--namespace-name", () => defaultNamespaceName, "The namespace to use for the core client class specified with the --class-name option."); namespaceOption.AddAlias("-n"); namespaceOption.ArgumentHelpName = "name"; - AddStringRegexValidator(namespaceOption, namespaceNameRegex(), "namespace name"); + AddStringRegexValidator(namespaceOption, namespaceNameRegex(), "namespace name", string.IsNullOrEmpty(defaultNamespaceName)); return namespaceOption; } - internal static Option GetBackingStoreOption(GenerationConfiguration defaultConfiguration) + internal static Option GetBackingStoreOption(bool defaultValue = false) + { + var backingStoreOption = new Option("--backing-store", () => defaultValue, "Enables backing store for models."); + backingStoreOption.AddAlias("-b"); + return backingStoreOption; + } + internal static Option GetOptionalBackingStoreOption() { - var backingStoreOption = new Option("--backing-store", () => defaultConfiguration.UsesBackingStore, "Enables backing store for models."); + var backingStoreOption = new Option("--backing-store", "Enables backing store for models."); backingStoreOption.AddAlias("-b"); return backingStoreOption; } - internal static Option GetExcludeBackwardCompatibleOption(GenerationConfiguration defaultConfiguration) + internal static Option GetExcludeBackwardCompatibleOption(bool defaultValue = false) { - var excludeBackwardCompatible = new Option("--exclude-backward-compatible", () => defaultConfiguration.ExcludeBackwardCompatible, "Excludes backward compatible and obsolete assets from the generated result. Should be used for new clients."); + var excludeBackwardCompatible = new Option("--exclude-backward-compatible", () => defaultValue, "Excludes backward compatible and obsolete assets from the generated result. Should be used for new clients."); excludeBackwardCompatible.AddAlias("--ebc"); return excludeBackwardCompatible; } - internal static Option GetAdditionalDataOption(GenerationConfiguration defaultConfiguration) + internal static Option GetOptionalExcludeBackwardCompatibleOption() + { + var excludeBackwardCompatible = new Option("--exclude-backward-compatible", "Excludes backward compatible and obsolete assets from the generated result. Should be used for new clients."); + excludeBackwardCompatible.AddAlias("--ebc"); + return excludeBackwardCompatible; + } + internal static Option GetAdditionalDataOption(bool defaultValue = true) + { + var additionalDataOption = new Option("--additional-data", () => defaultValue, "Will include the 'AdditionalData' property for models."); + additionalDataOption.AddAlias("--ad"); + return additionalDataOption; + } + internal static Option GetOptionalAdditionalDataOption() { - var additionalDataOption = new Option("--additional-data", () => defaultConfiguration.IncludeAdditionalData, "Will include the 'AdditionalData' property for models."); + var additionalDataOption = new Option("--additional-data", "Will include the 'AdditionalData' property for models."); additionalDataOption.AddAlias("--ad"); return additionalDataOption; } - internal static Option> GetStructuredMimeTypesOption(GenerationConfiguration defaultConfiguration) + internal static Option> GetStructuredMimeTypesOption(List defaultValue) { var structuredMimeTypesOption = new Option>( "--structured-mime-types", - () => [.. defaultConfiguration.StructuredMimeTypes], + () => defaultValue, "The MIME types with optional priorities as defined in RFC9110 Accept header to use for structured data model generation. Accepts multiple values."); structuredMimeTypesOption.AddAlias("-m"); return structuredMimeTypesOption; @@ -386,15 +411,15 @@ private static Command GetGenerateCommand() classOption.ArgumentHelpName = "name"; AddStringRegexValidator(classOption, classNameRegex(), "class name"); - var namespaceOption = GetNamespaceOption(defaultConfiguration); + var namespaceOption = GetNamespaceOption(defaultConfiguration.ClientNamespaceName); var logLevelOption = GetLogLevelOption(); - var backingStoreOption = GetBackingStoreOption(defaultConfiguration); + var backingStoreOption = GetBackingStoreOption(defaultConfiguration.UsesBackingStore); - var excludeBackwardCompatible = GetExcludeBackwardCompatibleOption(defaultConfiguration); + var excludeBackwardCompatible = GetExcludeBackwardCompatibleOption(defaultConfiguration.ExcludeBackwardCompatible); - var additionalDataOption = GetAdditionalDataOption(defaultConfiguration); + var additionalDataOption = GetAdditionalDataOption(defaultConfiguration.IncludeAdditionalData); var serializerOption = new Option>( "--serializer", @@ -412,7 +437,7 @@ private static Command GetGenerateCommand() var cleanOutputOption = GetCleanOutputOption(defaultConfiguration.CleanOutput); - var structuredMimeTypesOption = GetStructuredMimeTypesOption(defaultConfiguration); + var structuredMimeTypesOption = GetStructuredMimeTypesOption([.. defaultConfiguration.StructuredMimeTypes]); var (includePatterns, excludePatterns) = GetIncludeAndExcludeOptions(defaultConfiguration.IncludePatterns, defaultConfiguration.ExcludePatterns); @@ -517,11 +542,12 @@ private static Option GetClearCacheOption(bool defaultValue) clearCacheOption.AddAlias("--cc"); return clearCacheOption; } - private static void AddStringRegexValidator(Option option, Regex validator, string parameterName) + private static void AddStringRegexValidator(Option option, Regex validator, string parameterName, bool allowEmpty = false) { option.AddValidator(input => { var value = input.GetValueForOption(option); + if (string.IsNullOrEmpty(value) && allowEmpty) return; if (string.IsNullOrEmpty(value) || !validator.IsMatch(value)) input.ErrorMessage = $"{value} is not a valid {parameterName} for the client, the {parameterName} must conform to {validator}";