Skip to content

Commit

Permalink
Merge pull request #4294 from microsoft/feature/edit-client
Browse files Browse the repository at this point in the history
adds kiota client edit command
  • Loading branch information
baywet authored Mar 7, 2024
2 parents 213470a + 3fe6765 commit 1735a2f
Show file tree
Hide file tree
Showing 13 changed files with 343 additions and 44 deletions.
37 changes: 36 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@
"stopAtEntry": false
},
{
"name": "Launch Migrate",
"name": "Launch Client Migrate",
"type": "coreclr",
"request": "launch",
"preLaunchTask": "build",
Expand All @@ -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",
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -1272,3 +1279,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0




Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down Expand Up @@ -226,22 +228,19 @@ public async Task<IEnumerable<string>> 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);
clientsGenerationConfigurations.Remove(generationConfiguration);
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);
Expand Down
6 changes: 6 additions & 0 deletions src/kiota/Handlers/BaseKiotaCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 4 additions & 4 deletions src/kiota/Handlers/Client/AddHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,10 @@ public override async Task<int> 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<string> includePatterns = context.ParseResult.GetValueForOption(IncludePatternsOption) ?? new List<string>();
List<string> excludePatterns = context.ParseResult.GetValueForOption(ExcludePatternsOption) ?? new List<string>();
List<string> disabledValidationRules = context.ParseResult.GetValueForOption(DisabledValidationRulesOption) ?? new List<string>();
List<string> structuredMimeTypes = context.ParseResult.GetValueForOption(StructuredMimeTypesOption) ?? new List<string>();
List<string> includePatterns = context.ParseResult.GetValueForOption(IncludePatternsOption) ?? [];
List<string> excludePatterns = context.ParseResult.GetValueForOption(ExcludePatternsOption) ?? [];
List<string> disabledValidationRules = context.ParseResult.GetValueForOption(DisabledValidationRulesOption) ?? [];
List<string> 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);
Expand Down
173 changes: 173 additions & 0 deletions src/kiota/Handlers/Client/EditHandler.cs
Original file line number Diff line number Diff line change
@@ -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<string> ClassOption
{
get; init;
}
public required Option<bool?> BackingStoreOption
{
get; init;
}
public required Option<string> OutputOption
{
get; init;
}
public required Option<GenerationLanguage?> LanguageOption
{
get; init;
}
public required Option<string> DescriptionOption
{
get; init;
}
public required Option<string> NamespaceOption
{
get; init;
}
public required Option<bool?> AdditionalDataOption
{
get; init;
}
public required Option<List<string>> DisabledValidationRulesOption
{
get; init;
}
public required Option<List<string>> StructuredMimeTypesOption
{
get; init;
}
public required Option<bool?> ExcludeBackwardCompatibleOption
{
get;
set;
}
public required Option<List<string>> IncludePatternsOption
{
get; init;
}
public required Option<List<string>> ExcludePatternsOption
{
get; init;
}
public required Option<bool> SkipGenerationOption
{
get; init;
}

public override async Task<int> 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<string>? includePatterns = context.ParseResult.GetValueForOption(IncludePatternsOption);
List<string>? excludePatterns = context.ParseResult.GetValueForOption(ExcludePatternsOption);
List<string>? disabledValidationRules = context.ParseResult.GetValueForOption(DisabledValidationRulesOption);
List<string>? 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<KiotaBuilder>(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
}
}
}
}
2 changes: 2 additions & 0 deletions src/kiota/Handlers/Client/GenerateHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ public override async Task<int> 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)
Expand Down
1 change: 1 addition & 0 deletions src/kiota/Handlers/Client/RemoveHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public override async Task<int> 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)
Expand Down
Loading

0 comments on commit 1735a2f

Please sign in to comment.