diff --git a/src/OpenApi.Client.Cli/Commands/GenerateCommand.cs b/src/OpenApi.Client.Cli/Commands/GenerateCommand.cs index 9bff326..7e271b6 100644 --- a/src/OpenApi.Client.Cli/Commands/GenerateCommand.cs +++ b/src/OpenApi.Client.Cli/Commands/GenerateCommand.cs @@ -3,8 +3,10 @@ // Copyright (C) Leszek Pomianowski and OpenAPI Client Contributors. // All Rights Reserved. -using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; using OpenApi.Client.Cli.Settings; +using OpenApi.Client.SourceGenerators.Contracts; +using OpenApi.Client.SourceGenerators.Genertion; using OpenApi.Client.SourceGenerators.Schema; using OpenApi.Client.SourceGenerators.Serialization; @@ -22,17 +24,73 @@ public sealed class GenerateCommand : AsyncCommand { private static readonly Regex PathNormalizer = new(@"(\\\\|//)", RegexOptions.Compiled); + private static readonly Regex NamespaceValidator = + new(@"^[_a-zA-Z][_a-zA-Z0-9]*(\.[_a-zA-Z][_a-zA-Z0-9]*)*$", RegexOptions.Compiled); + + private static readonly Regex ClassNameValidator = + new(@"^[_a-zA-Z][_a-zA-Z0-9]*$", RegexOptions.Compiled); + /// public override async Task ExecuteAsync( CommandContext context, GenerateCommandSettings settings ) { + using CancellationTokenSource cancellationTokenSource = new(); string contents = await File.ReadAllTextAsync(settings.File); SerializationResult? serializationResult = new OpenApiSerializer().Deserialize(settings.File, contents); + if (serializationResult.HasErrors) + { + foreach ( + SerializationResultError serializationResultError in serializationResult.Errors + ) + { + AnsiConsole.MarkupLine($"[red]Error: {serializationResultError.Message}[/]"); + } + + return -1; + } + + if (serializationResult.Result is null) + { + AnsiConsole.MarkupLine($"[red]Error: Serialized JSON returned empty API.[/]"); + + return -2; + } + + string? generatedSource = null; + + OpenApiContract contract = OpenApiContractParser.Parse( + settings.Namespace, + settings.ClassName, + Accessibility.Public, + serializationResult.Result + ); + + ClientGenerator generator = new(contract); + GenerationResult generatorResult = generator.Generate(); + + if (generatorResult.HasErrors) + { + foreach (GenerationResultError generatorResultError in generatorResult.Errors) + { + AnsiConsole.MarkupLine($"[red]Error: {generatorResultError.Message}[/]"); + } + + return -1; + } + + generatedSource = generatorResult.Result; + + await File.WriteAllTextAsync( + settings.Output, + generatedSource, + cancellationTokenSource.Token + ); + return 0; } @@ -42,6 +100,29 @@ public override ValidationResult Validate( GenerateCommandSettings settings ) { + if (string.IsNullOrEmpty(settings.ClassName)) + { + settings.ClassName = Path.GetFileNameWithoutExtension(settings.Output); + } + + settings.ClassName = settings.ClassName.Trim(); + + if (!ClassNameValidator.IsMatch(settings.ClassName)) + { + return ValidationResult.Error( + $"The name '{settings.ClassName}' is not valid C# type name." + ); + } + + settings.Namespace = settings.Namespace.Trim(); + + if (!NamespaceValidator.IsMatch(settings.Namespace)) + { + return ValidationResult.Error( + $"The namespace '{settings.Namespace}' is not valid C# namespace." + ); + } + settings.File = PathNormalizer.Replace(settings.File, "/"); settings.File = settings.File.Replace("\\", "/"); @@ -50,6 +131,9 @@ GenerateCommandSettings settings return ValidationResult.Error($"The file '{settings.File}' does not exist."); } + settings.Output = PathNormalizer.Replace(settings.Output, "/"); + settings.Output = settings.Output.Replace("\\", "/"); + return ValidationResult.Success(); } } diff --git a/src/OpenApi.Client.Cli/Settings/GenerateCommandSettings.cs b/src/OpenApi.Client.Cli/Settings/GenerateCommandSettings.cs index f33f218..e89a8ca 100644 --- a/src/OpenApi.Client.Cli/Settings/GenerateCommandSettings.cs +++ b/src/OpenApi.Client.Cli/Settings/GenerateCommandSettings.cs @@ -31,6 +31,20 @@ public sealed class GenerateCommandSettings : CommandSettings [Description("The output directory.")] public string Output { get; set; } = "./"; + /// + /// Gets or sets the name of the generated class. + /// + [CommandOption("-c|--classname ")] + [Description("The name of the generated class..")] + public string ClassName { get; set; } = string.Empty; + + /// + /// Gets or sets the namespace for the generated class. + /// + [CommandOption("-n|--namespace ")] + [Description("The namespace in which the class is to be placed.")] + public string Namespace { get; set; } = "OpenApi"; + /// /// Gets or sets the JSON serializer to use. If not provided, System.Text.Json is used. ///