diff --git a/Enterspeed.Cli.sln b/Enterspeed.Cli.sln index 9642540..124e2fa 100644 --- a/Enterspeed.Cli.sln +++ b/Enterspeed.Cli.sln @@ -25,6 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "releaseNotes", "releaseNote releaseNotes\2.0.0.md = releaseNotes\2.0.0.md releaseNotes\2.0.1.md = releaseNotes\2.0.1.md releaseNotes\2.0.2.md = releaseNotes\2.0.2.md + releaseNotes\3.0.0.md = releaseNotes\3.0.0.md EndProjectSection EndProject Global diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c5a10e0..0ab6fc7 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,9 +1,9 @@ variables: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: 1 vmPool: 'windows-latest' - majorVersion: 2 + majorVersion: 3 minorVersion: 0 - patchVersion: 2 + patchVersion: 0 version: $[format('{0}.{1}.{2}', variables.majorVersion, variables.minorVersion, variables.patchVersion)] ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/master') }}: # Versioning: 1.0.0 diff --git a/releaseNotes/3.0.0.md b/releaseNotes/3.0.0.md new file mode 100644 index 0000000..d33631a --- /dev/null +++ b/releaseNotes/3.0.0.md @@ -0,0 +1,6 @@ +### Breaking +- Added support for folders. + The folder structure you have in the Enterspeed app is now reflected on your local disk when using the CLI. + Likewise, if you moved a schema to a folder on your local disk and call a `save` or `import` command the folder structure is updated in the app. + With the introduction of folders, the static `partials` folder is removed and the schema type is now part of the file name ([schemaAlias].[schemaType].[format]). + If you are already working with schemas on disk, you should clear the `schemas` folder and run a new `import` command to get the new folder and file structure. diff --git a/src/Enterspeed.Cli/Api/MappingSchema/UpdateMappingSchemaRequest.cs b/src/Enterspeed.Cli/Api/MappingSchema/UpdateMappingSchemaRequest.cs index 5f3b3c7..d8e7dc3 100644 --- a/src/Enterspeed.Cli/Api/MappingSchema/UpdateMappingSchemaRequest.cs +++ b/src/Enterspeed.Cli/Api/MappingSchema/UpdateMappingSchemaRequest.cs @@ -6,10 +6,8 @@ namespace Enterspeed.Cli.Api.MappingSchema { public class UpdateMappingSchemaRequest : IRequest { + public string Name { get; set; } public string MappingSchemaId { get; set; } - public int Version { get; set; } - public string Format { get; set; } - public object Schema { get; set; } } public class UpdateMappingSchemaResponse @@ -28,10 +26,11 @@ public UpdateMappingSchemaRequestHandler(IEnterspeedClient enterspeedClient) _enterspeedClient = enterspeedClient; } - public async Task Handle(UpdateMappingSchemaRequest updateMappingSchemaRequest, CancellationToken cancellationToken) + public async Task Handle(UpdateMappingSchemaRequest updateMappingSchemaRequest, + CancellationToken cancellationToken) { var request = new RestRequest( - $"tenant/mapping-schemas/{updateMappingSchemaRequest.MappingSchemaId}/version/{updateMappingSchemaRequest.Version}", + $"tenant/mapping-schemas/{updateMappingSchemaRequest.MappingSchemaId}", Method.Put).AddJsonBody(updateMappingSchemaRequest); var response = await _enterspeedClient.ExecuteAsync(request, cancellationToken); diff --git a/src/Enterspeed.Cli/Api/MappingSchema/UpdateMappingSchemaVersionRequest.cs b/src/Enterspeed.Cli/Api/MappingSchema/UpdateMappingSchemaVersionRequest.cs new file mode 100644 index 0000000..facfb15 --- /dev/null +++ b/src/Enterspeed.Cli/Api/MappingSchema/UpdateMappingSchemaVersionRequest.cs @@ -0,0 +1,41 @@ +using Enterspeed.Cli.Services.EnterspeedClient; +using MediatR; +using RestSharp; + +namespace Enterspeed.Cli.Api.MappingSchema +{ + public class UpdateMappingSchemaVersionRequest : IRequest + { + public string MappingSchemaId { get; set; } + public int Version { get; set; } + public string Format { get; set; } + public object Schema { get; set; } + } + + public class UpdateMappingSchemaVersionResponse + { + public string IdValue { get; set; } + public string MappingSchemaGuid { get; set; } + public int Version { get; set; } + } + + public class UpdateMappingSchemaVersionRequestHandler : IRequestHandler + { + private readonly IEnterspeedClient _enterspeedClient; + + public UpdateMappingSchemaVersionRequestHandler(IEnterspeedClient enterspeedClient) + { + _enterspeedClient = enterspeedClient; + } + + public async Task Handle(UpdateMappingSchemaVersionRequest updateMappingSchemaVersionRequest, CancellationToken cancellationToken) + { + var request = new RestRequest( + $"tenant/mapping-schemas/{updateMappingSchemaVersionRequest.MappingSchemaId}/version/{updateMappingSchemaVersionRequest.Version}", + Method.Put).AddJsonBody(updateMappingSchemaVersionRequest); + + var response = await _enterspeedClient.ExecuteAsync(request, cancellationToken); + return response; + } + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Commands/Schema/CloneSchemaCommand.cs b/src/Enterspeed.Cli/Commands/Schema/CloneSchemaCommand.cs index 9f92831..9619a87 100644 --- a/src/Enterspeed.Cli/Commands/Schema/CloneSchemaCommand.cs +++ b/src/Enterspeed.Cli/Commands/Schema/CloneSchemaCommand.cs @@ -46,7 +46,7 @@ public async Task InvokeAsync(InvocationContext context) foreach (var schema in schemaResponses) { - _schemaFileService.CreateSchema(schema.ViewHandle, schema.Type, schema.Version); + _schemaFileService.CreateSchema(schema.ViewHandle, schema.Type, schema.Version, schema.Name); } _outputService.Write("Successfully cloned all schemas"); diff --git a/src/Enterspeed.Cli/Commands/Schema/CreateSchemaCommand.cs b/src/Enterspeed.Cli/Commands/Schema/CreateSchemaCommand.cs index 1e8f162..d6a6885 100644 --- a/src/Enterspeed.Cli/Commands/Schema/CreateSchemaCommand.cs +++ b/src/Enterspeed.Cli/Commands/Schema/CreateSchemaCommand.cs @@ -63,9 +63,11 @@ public async Task InvokeAsync(InvocationContext context) Format = SchemaConstants.JavascriptFormat; } + var name = Name ?? Alias; + var createSchemaResponse = await _mediator.Send(new CreateMappingSchemaRequest { - Name = Name ?? Alias, + Name = name, ViewHandle = Alias, Type = schemaType.Value.ToApiString(), Format = Format @@ -78,7 +80,7 @@ public async Task InvokeAsync(InvocationContext context) MappingSchemaId = createSchemaResponse.MappingSchemaGuid }); - _schemaFileService.CreateSchema(Alias, schemaType.Value, schemaResponse.Version); + _schemaFileService.CreateSchema(Alias, schemaType.Value, schemaResponse.Version, name); } else { @@ -101,7 +103,7 @@ private async Task UpdateSchemaToPartialTemplate(string mappingSchemaGuid) { var schema = _schemaFileService.GetSchema(Alias); - var updateSchemaResponse = await _mediator.Send(new UpdateMappingSchemaRequest + var updateSchemaResponse = await _mediator.Send(new UpdateMappingSchemaVersionRequest { Format = schema.Format, MappingSchemaId = mappingSchemaGuid, diff --git a/src/Enterspeed.Cli/Commands/Schema/ImportSchemaCommand.cs b/src/Enterspeed.Cli/Commands/Schema/ImportSchemaCommand.cs index f670f0c..d5c1b16 100644 --- a/src/Enterspeed.Cli/Commands/Schema/ImportSchemaCommand.cs +++ b/src/Enterspeed.Cli/Commands/Schema/ImportSchemaCommand.cs @@ -5,6 +5,7 @@ using Enterspeed.Cli.Services.ConsoleOutput; using Enterspeed.Cli.Services.FileService; using Enterspeed.Cli.Services.FileService.Models; +using Enterspeed.Cli.Services.SchemaService; using MediatR; using Microsoft.Extensions.Logging; @@ -12,7 +13,8 @@ namespace Enterspeed.Cli.Commands.Schema; internal class ImportSchemaCommand : Command { - public ImportSchemaCommand() : base(name: "import", "Imports all schemas from the /schemas folder on the disk. Will create new schemas and update existing schemas if --override is enabled.") + public ImportSchemaCommand() : base(name: "import", + "Imports all schemas from the /schemas folder on the disk. Will create new schemas and update existing schemas if --override is enabled.") { AddOption(new Option(new[] { "--schema-alias", "-a" }, "Provide a schema alias to only import a single schema")); AddOption(new Option(new[] { "--override", "-o" }, "Override existing schemas")); @@ -23,17 +25,20 @@ internal class ImportSchemaCommand : Command private readonly IMediator _mediator; private readonly IOutputService _outputService; private readonly ISchemaFileService _schemaFileService; + private readonly ISchemaNameService _schemaNameService; private readonly ILogger _logger; public Handler(IMediator mediator, IOutputService outputService, ISchemaFileService schemaFileService, - ILogger logger) + ILogger logger, + ISchemaNameService schemaNameService) { _mediator = mediator; _outputService = outputService; _schemaFileService = schemaFileService; _logger = logger; + _schemaNameService = schemaNameService; } public bool Override { get; set; } @@ -81,12 +86,12 @@ public async Task InvokeAsync(InvocationContext context) return 0; } - private async Task UpdateExistingSchema(SchemaFile schemaFile, QueryMappingSchemaResponse queryMappingSchemaResponse) + private async Task UpdateExistingSchema(SchemaFile schemaFile, QueryMappingSchemaResponse matchingSchema) { var existingSchema = await _mediator.Send( new GetMappingSchemaRequest { - MappingSchemaId = queryMappingSchemaResponse.Id.MappingSchemaGuid + MappingSchemaId = matchingSchema.Id.MappingSchemaGuid } ); @@ -96,27 +101,48 @@ private async Task UpdateExistingSchema(SchemaFile schemaFile, QueryMappin return false; } - var updateSchemaResponse = await _mediator.Send(new UpdateMappingSchemaRequest + var relativeDirectoryPathInEnterspeed = Path.GetDirectoryName(existingSchema.Name.TrimEnd('/')); + var relativeDirectoryPathOnDisk = schemaFile.RelativeSchemaDirectory; + + if (!relativeDirectoryPathOnDisk.Equals(relativeDirectoryPathInEnterspeed)) + { + await UpdateSchemaName(existingSchema, relativeDirectoryPathOnDisk); + } + + var updateMappingSchemaVersionResponse = await _mediator.Send(new UpdateMappingSchemaVersionRequest { Format = schemaFile.Format, - MappingSchemaId = queryMappingSchemaResponse.Id.MappingSchemaGuid, + MappingSchemaId = matchingSchema.Id.MappingSchemaGuid, Version = existingSchema.LatestVersion, Schema = schemaFile.GetSchemaContent() }); - _outputService.Write($"Successfully updated schema: {schemaFile.Alias} Version: {updateSchemaResponse.Version}"); + _outputService.Write($"Successfully updated schema: {schemaFile.Alias} Version: {updateMappingSchemaVersionResponse.Version}"); return true; } + private async Task UpdateSchemaName(GetMappingSchemaResponse existingSchema, string relativeDirectoryPathOnDisk) + { + var name = _schemaNameService.BuildNewSchemaName(existingSchema.Name, relativeDirectoryPathOnDisk); + var updateSchemaResponse = await _mediator.Send(new UpdateMappingSchemaRequest() + { + Name = name, + MappingSchemaId = existingSchema.Version.Id.MappingSchemaGuid, + }); + + _outputService.Write($"Successfully updated name to: {name}"); + } + /// /// As the management API does not support upserts we first creates the empty schema and then updates the schema with content /// private async Task CreateNewSchema(SchemaFile schemaFile) { + var name = _schemaNameService.GetSchemaName(schemaFile); var createSchemaResponse = await _mediator.Send(new CreateMappingSchemaRequest { - Name = schemaFile.Alias, + Name = name, ViewHandle = schemaFile.Alias, Type = schemaFile.SchemaType.ToApiString(), Format = schemaFile.Format @@ -130,7 +156,7 @@ private async Task CreateNewSchema(SchemaFile schemaFile) _outputService.Write("Successfully created new schema: " + schemaFile.Alias); - var updateSchemaResponse = await _mediator.Send(new UpdateMappingSchemaRequest + var updateSchemaResponse = await _mediator.Send(new UpdateMappingSchemaVersionRequest { Format = schemaFile.Format, MappingSchemaId = createSchemaResponse.MappingSchemaGuid, diff --git a/src/Enterspeed.Cli/Commands/Schema/SaveSchemaCommand.cs b/src/Enterspeed.Cli/Commands/Schema/SaveSchemaCommand.cs index c02e3f0..00469ae 100644 --- a/src/Enterspeed.Cli/Commands/Schema/SaveSchemaCommand.cs +++ b/src/Enterspeed.Cli/Commands/Schema/SaveSchemaCommand.cs @@ -5,6 +5,7 @@ using Enterspeed.Cli.Extensions; using Enterspeed.Cli.Services.ConsoleOutput; using Enterspeed.Cli.Services.FileService; +using Enterspeed.Cli.Services.SchemaService; using MediatR; using Microsoft.Extensions.Logging; @@ -23,18 +24,21 @@ public SaveSchemaCommand() : base(name: "save", "Saves schema") private readonly IMediator _mediator; private readonly IOutputService _outputService; private readonly ISchemaFileService _schemaFileService; + private readonly ISchemaNameService _schemaNameService; private readonly ILogger _logger; public Handler( IMediator mediator, IOutputService outputService, ISchemaFileService schemaFileService, - ILogger logger) + ILogger logger, + ISchemaNameService schemaNameService) { _mediator = mediator; _outputService = outputService; _schemaFileService = schemaFileService; _logger = logger; + _schemaNameService = schemaNameService; } public string Alias { get; set; } @@ -76,7 +80,15 @@ public async Task InvokeAsync(InvocationContext context) return 1; } - var updateMappingSchemaRequest = new UpdateMappingSchemaRequest + var relativeDirectoryPathInEnterspeed = Path.GetDirectoryName(existingSchema.Name.TrimEnd('/')); + var relativeDirectoryPathOnDisk = schema.RelativeSchemaDirectory; + + if (!relativeDirectoryPathOnDisk.Equals(relativeDirectoryPathInEnterspeed)) + { + await UpdateSchemaName(existingSchema, relativeDirectoryPathOnDisk); + } + + var updateMappingSchemaVersionRequest = new UpdateMappingSchemaVersionRequest { Format = existingSchema.Version.Format, MappingSchemaId = existingSchema.Version.Id.MappingSchemaGuid, @@ -85,7 +97,7 @@ public async Task InvokeAsync(InvocationContext context) }; // Create update schema request - var updateSchemaResponse = await _mediator.Send(updateMappingSchemaRequest); + var updateMappingSchemaVersionResponse = await _mediator.Send(updateMappingSchemaVersionRequest); var updatedSchema = await _mediator.Send( new GetMappingSchemaRequest @@ -99,6 +111,18 @@ public async Task InvokeAsync(InvocationContext context) return 0; } + + private async Task UpdateSchemaName(GetMappingSchemaResponse existingSchema, string relativeDirectoryPathOnDisk) + { + var name = _schemaNameService.BuildNewSchemaName(existingSchema.Name, relativeDirectoryPathOnDisk); + var updateSchemaResponse = await _mediator.Send(new UpdateMappingSchemaRequest() + { + Name = name, + MappingSchemaId = existingSchema.Version.Id.MappingSchemaGuid, + }); + + _outputService.Write($"Successfully updated name to: {name}"); + } } } } \ No newline at end of file diff --git a/src/Enterspeed.Cli/Common/Behaviours/UnhandledExceptionBehaviour.cs b/src/Enterspeed.Cli/Common/Behaviours/UnhandledExceptionBehaviour.cs index b9f9d83..a0a322d 100644 --- a/src/Enterspeed.Cli/Common/Behaviours/UnhandledExceptionBehaviour.cs +++ b/src/Enterspeed.Cli/Common/Behaviours/UnhandledExceptionBehaviour.cs @@ -9,11 +9,11 @@ public class UnhandledExceptionBehaviour : IPipelineBehavio public UnhandledExceptionBehaviour(ILogger logger) => _logger = logger; - public async Task Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate next) + public Task Handle(TRequest request, RequestHandlerDelegate next, CancellationToken cancellationToken) { try { - return await next(); + return next(); } catch (Exception ex) { diff --git a/src/Enterspeed.Cli/Enterspeed.Cli.csproj b/src/Enterspeed.Cli/Enterspeed.Cli.csproj index 7a5c53a..4d30bee 100644 --- a/src/Enterspeed.Cli/Enterspeed.Cli.csproj +++ b/src/Enterspeed.Cli/Enterspeed.Cli.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 enable disable Enterspeed @@ -22,18 +22,17 @@ - - - - - - - + + + + + + - - - - + + + + diff --git a/src/Enterspeed.Cli/Extensions/SchemaExtensions.cs b/src/Enterspeed.Cli/Extensions/SchemaExtensions.cs index 0ef2ada..5c52570 100644 --- a/src/Enterspeed.Cli/Extensions/SchemaExtensions.cs +++ b/src/Enterspeed.Cli/Extensions/SchemaExtensions.cs @@ -4,15 +4,14 @@ using System.Text; using System.Text.Json; -namespace Enterspeed.Cli.Extensions +namespace Enterspeed.Cli.Extensions; + +public static class SchemaExtensions { - public static class SchemaExtensions + public static object GetSchemaContent(this SchemaFile schemaFile) { - public static object GetSchemaContent(this SchemaFile schemaFile) - { - return schemaFile.Format.Equals(SchemaConstants.JavascriptFormat) - ? Convert.ToBase64String(Encoding.UTF8.GetBytes(schemaFile.Content?.ToString() ?? string.Empty)) - : JsonSerializer.SerializeToDocument(schemaFile.Content, SchemaFileService.SerializerOptions); - } + return schemaFile.Format.Equals(SchemaConstants.JavascriptFormat) + ? Convert.ToBase64String(Encoding.UTF8.GetBytes(schemaFile.Content?.ToString() ?? string.Empty)) + : JsonSerializer.SerializeToDocument(schemaFile.Content, SchemaFileService.SerializerOptions); } -} +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Extensions/ServiceCollectionExtensions.cs b/src/Enterspeed.Cli/Extensions/ServiceCollectionExtensions.cs index d189b5c..73b6436 100644 --- a/src/Enterspeed.Cli/Extensions/ServiceCollectionExtensions.cs +++ b/src/Enterspeed.Cli/Extensions/ServiceCollectionExtensions.cs @@ -1,10 +1,8 @@ using Microsoft.Extensions.DependencyInjection; -using System.Reflection; using Enterspeed.Cli.Configuration; using Enterspeed.Cli.Services.ConsoleOutput; using Enterspeed.Cli.Services.EnterspeedClient; using Enterspeed.Cli.Services.FileService; -using MediatR; using Enterspeed.Cli.Services.StateService; using Serilog; using Serilog.Formatting.Compact; @@ -14,6 +12,7 @@ using System.CommandLine.Hosting; using Enterspeed.Cli.Services; using Enterspeed.Cli.Services.IngestService; +using Enterspeed.Cli.Services.SchemaService; using Serilog.Events; namespace Enterspeed.Cli.Extensions; @@ -45,11 +44,13 @@ public static LoggerConfiguration ConfigureSerilog(this LoggerConfiguration logg public static IServiceCollection AddApplication(this IServiceCollection services) { - services.AddMediatR(Assembly.GetExecutingAssembly()); + services.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining()); services.AddSingleton(); services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddSingleton(); diff --git a/src/Enterspeed.Cli/Services/FileService/FilePathService.cs b/src/Enterspeed.Cli/Services/FileService/FilePathService.cs new file mode 100644 index 0000000..3b0f100 --- /dev/null +++ b/src/Enterspeed.Cli/Services/FileService/FilePathService.cs @@ -0,0 +1,40 @@ +namespace Enterspeed.Cli.Services.FileService; + +public class FilePathService : IFilePathService +{ + public string GetRootDirectoryPath(string schemasDirectory) + { + return Path.Combine(Directory.GetCurrentDirectory(), schemasDirectory); + } + + private static string GetRelativeDirectoryPathBySchemaName(string schemaName) + { + return Path.GetDirectoryName(schemaName.TrimEnd('/')); + } + + private static string GetDirectoryPathByFilePath(string filePath) + { + return Path.GetDirectoryName(filePath); + } + + public string GetRelativeSchemaDirectoryPath(string currentSchemaFilePath, string schemasDirectory) + { + var rootSchemaDirectoryPath = GetRootDirectoryPath(schemasDirectory); + var currentSchemaDirectoryPath = GetDirectoryPathByFilePath(currentSchemaFilePath); + var relativeSchemaDirectoryPath = currentSchemaDirectoryPath?.Replace(rootSchemaDirectoryPath, ""); + + if (!string.IsNullOrEmpty(relativeSchemaDirectoryPath) && relativeSchemaDirectoryPath.StartsWith(Path.DirectorySeparatorChar)) + { + relativeSchemaDirectoryPath = relativeSchemaDirectoryPath.Remove(0, 1); + } + + return relativeSchemaDirectoryPath; + } + + public string GetDirectoryPathBySchemaName(string schemaName, string schemasDirectory) + { + var currentSchemaPathRelative = GetRelativeDirectoryPathBySchemaName(schemaName); + var schemaDirectoryPath = Path.Combine(GetRootDirectoryPath(schemasDirectory), currentSchemaPathRelative); + return schemaDirectoryPath; + } +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Services/FileService/IFilePathService.cs b/src/Enterspeed.Cli/Services/FileService/IFilePathService.cs new file mode 100644 index 0000000..01d8ebf --- /dev/null +++ b/src/Enterspeed.Cli/Services/FileService/IFilePathService.cs @@ -0,0 +1,8 @@ +namespace Enterspeed.Cli.Services.FileService; + +public interface IFilePathService +{ + string GetRootDirectoryPath(string schemasDirectory); + string GetRelativeSchemaDirectoryPath(string currentSchemaFilePath, string schemasDirectory); + string GetDirectoryPathBySchemaName(string schemaName, string schemasDirectory); +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Services/FileService/ISchemaFileService.cs b/src/Enterspeed.Cli/Services/FileService/ISchemaFileService.cs index 4b41ba0..b479d50 100644 --- a/src/Enterspeed.Cli/Services/FileService/ISchemaFileService.cs +++ b/src/Enterspeed.Cli/Services/FileService/ISchemaFileService.cs @@ -6,7 +6,7 @@ namespace Enterspeed.Cli.Services.FileService; public interface ISchemaFileService { - void CreateSchema(string alias, SchemaType schemaType, MappingSchemaVersion version); + void CreateSchema(string alias, SchemaType schemaType, MappingSchemaVersion version, string SchemaName); SchemaFile GetSchema(string alias, string filePath = null); IList GetAllSchemas(); bool SchemaExists(string alias); diff --git a/src/Enterspeed.Cli/Services/FileService/Models/SchemaFile.cs b/src/Enterspeed.Cli/Services/FileService/Models/SchemaFile.cs index f4d2143..fdc4dfa 100644 --- a/src/Enterspeed.Cli/Services/FileService/Models/SchemaFile.cs +++ b/src/Enterspeed.Cli/Services/FileService/Models/SchemaFile.cs @@ -8,11 +8,14 @@ public class SchemaFile public SchemaType SchemaType { get; } public object Content { get; } public string Format { get; } - public SchemaFile(string alias, SchemaType schemaType, object content, string format) + public string RelativeSchemaDirectory { get; } + + public SchemaFile(string alias, SchemaType schemaType, object content, string format, string relativeSchemaDirectory) { Alias = alias; SchemaType = schemaType; Content = content; Format = format; + RelativeSchemaDirectory = relativeSchemaDirectory; } } \ No newline at end of file diff --git a/src/Enterspeed.Cli/Services/FileService/SchemaFileService.cs b/src/Enterspeed.Cli/Services/FileService/SchemaFileService.cs index cc834d1..dbda2e6 100644 --- a/src/Enterspeed.Cli/Services/FileService/SchemaFileService.cs +++ b/src/Enterspeed.Cli/Services/FileService/SchemaFileService.cs @@ -1,11 +1,13 @@ using System.Text; using System.Text.Json; using System.Text.Json.Serialization; +using System.Text.RegularExpressions; using Enterspeed.Cli.Api.MappingSchema.Models; using Enterspeed.Cli.Constants; using Enterspeed.Cli.Domain; using Enterspeed.Cli.Domain.Models; using Enterspeed.Cli.Services.FileService.Models; +using Enterspeed.Cli.Services.SchemaService; using Microsoft.Extensions.Logging; namespace Enterspeed.Cli.Services.FileService; @@ -14,11 +16,6 @@ public class SchemaFileService : ISchemaFileService { private readonly ILogger _logger; - private const string SchemaDirectory = "schemas"; - - // logic require partial folder to be a subfolder of normal schema folder - private const string PartialSchemaDirectory = $"{SchemaDirectory}/partials"; - private const string DefaultJsContent = "/** @type {Enterspeed.FullSchema} */\nexport default {\n triggers: function(context) {\n // Example that triggers on 'mySourceEntityType' in 'mySourceGroupAlias', adjust to match your own values\n // See documentation for triggers here: https://docs.enterspeed.com/reference/js/triggers\n context.triggers('mySourceGroupAlias', ['mySourceEntityType'])\n },\n routes: function(sourceEntity, context) {\n // Example that generates a handle with the value of 'my-handle' to use when fetching the view from the Delivery API\n // See documentation for routes here: https://docs.enterspeed.com/reference/js/routes\n context.handle('my-handle')\n },\n properties: function (sourceEntity, context) {\n // Example that returns all properties from the source entity to the view\n // See documentation for properties here: https://docs.enterspeed.com/reference/js/properties\n return sourceEntity.properties\n }\n}"; @@ -30,14 +27,23 @@ public class SchemaFileService : ISchemaFileService DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; - public SchemaFileService(ILogger logger) + private readonly IFilePathService _filePathService; + + private readonly ISchemaNameService _schemaNameService; + + public SchemaFileService( + ILogger logger, + IFilePathService filePathService, + ISchemaNameService schemaNameService) { _logger = logger; + _filePathService = filePathService; + _schemaNameService = schemaNameService; } - public void CreateSchema(string alias, SchemaType schemaType, MappingSchemaVersion version) + public void CreateSchema(string alias, SchemaType schemaType, MappingSchemaVersion version, string schemaName) { - EnsureSchemaFolders(); + EnsureSchemaFolders(schemaName); if (SchemaExists(alias)) { @@ -45,7 +51,8 @@ public void CreateSchema(string alias, SchemaType schemaType, MappingSchemaVersi DeleteSchema(alias); } - using var fs = File.Create(GetRelativeFilePath(alias, schemaType, version.Format)); + var filePath = GetFilePath(schemaName, alias, schemaType, version.Format); + using var fs = File.Create(filePath); if (version.Format.Equals(SchemaConstants.JavascriptFormat)) { CreateJavascriptSchemaFile(schemaType, version, fs); @@ -80,7 +87,7 @@ private void CreateJsonSchemaFile(SchemaType schemaType, MappingSchemaVersion sc var emptyContent = schemaType == SchemaType.Partial ? new SchemaBaseProperties { Properties = new() } : new SchemaBaseProperties { Properties = new(), Triggers = new() }; - + schemaVersion.Data = JsonSerializer.Serialize(emptyContent); } @@ -93,15 +100,12 @@ private void CreateJsonSchemaFile(SchemaType schemaType, MappingSchemaVersion sc public SchemaFile GetSchema(string alias, string filePath = null) { - var schemaFilePath = filePath ?? GetFile(alias); - var schemaFolderName = Path.GetFileName(Path.GetDirectoryName(schemaFilePath)); - var partialSchemaFolderName = Path.GetFileName(Path.GetDirectoryName($"{PartialSchemaDirectory}/")); - var schemaContent = GetSchemaContent(alias, schemaFilePath); - - var schemaType = schemaFolderName == partialSchemaFolderName ? SchemaType.Partial : SchemaType.Normal; - var schemaFormat = schemaFilePath.EndsWith(".js") ? SchemaConstants.JavascriptFormat : SchemaConstants.JsonFormat; + var currentSchemaFilePath = filePath ?? GetFile(alias); + var schemaFormat = currentSchemaFilePath.EndsWith(".js") ? SchemaConstants.JavascriptFormat : SchemaConstants.JsonFormat; + var schemaContent = GetSchemaContent(alias, currentSchemaFilePath); object content; + if (schemaFormat.Equals(SchemaConstants.JavascriptFormat)) { content = schemaContent; @@ -111,24 +115,47 @@ public SchemaFile GetSchema(string alias, string filePath = null) content = JsonSerializer.Deserialize(schemaContent, SerializerOptions); } - return new SchemaFile(alias, schemaType, content, schemaFormat); + var schemasDirectoryName = _schemaNameService.GetSchemasDirectoryName(); + var relativeSchemaDirectoryPath = _filePathService.GetRelativeSchemaDirectoryPath(currentSchemaFilePath, schemasDirectoryName); + var schemaType = GetSchemaTypeFromFilePath(currentSchemaFilePath); + return new SchemaFile(alias, schemaType, content, schemaFormat, relativeSchemaDirectoryPath); } public IList GetAllSchemas() { EnsureSchemaFolders(); - var filePaths = Directory.GetFiles(Path.Combine(Directory.GetCurrentDirectory(), SchemaDirectory), "*", SearchOption.AllDirectories); - + var schemasDirectoryName = _schemaNameService.GetSchemasDirectoryName(); + var filePaths = Directory.GetFiles(_filePathService.GetRootDirectoryPath(schemasDirectoryName), "*", SearchOption.AllDirectories); return filePaths.Select(filePath => { - var alias = GetAliasFromFilePath(filePath); - + var alias = _schemaNameService.GetAliasFromFilePath(filePath); return GetSchema(alias, filePath); }) .ToList(); } + private void EnsureSchemaFolders(string schemaName = null) + { + if (!Directory.Exists(_schemaNameService.GetSchemasDirectoryName())) + { + Directory.CreateDirectory(_schemaNameService.GetSchemasDirectoryName()); + } + + if (schemaName != null) + { + if (_schemaNameService.IsDirectorySchemaName(schemaName)) + { + var schemasDirectoryName = _schemaNameService.GetSchemasDirectoryName(); + var schemaDirectoryPath = _filePathService.GetDirectoryPathBySchemaName(schemaName, schemasDirectoryName); + if (!Directory.Exists(schemaDirectoryPath)) + { + Directory.CreateDirectory(schemaDirectoryPath); + } + } + } + } + public bool SchemaValid(MappingSchemaVersion externalSchema, string schemaAlias) { if (!SchemaExists(schemaAlias)) @@ -137,7 +164,10 @@ public bool SchemaValid(MappingSchemaVersion externalSchema, string schemaAlias) } var localSchema = GetSchemaContent(schemaAlias); - return CompareSchemaContent(externalSchema.Data, localSchema, externalSchema.Format); + + return externalSchema.Format.Equals(SchemaConstants.JsonFormat) + ? CompareJsonSchemas(externalSchema.Data, localSchema) + : CompareJavascriptSchemas(externalSchema.Data, localSchema); } public bool SchemaExists(string alias) @@ -145,20 +175,7 @@ public bool SchemaExists(string alias) return GetFile(alias) is not null; } - private static void EnsureSchemaFolders() - { - if (!Directory.Exists(SchemaDirectory)) - { - Directory.CreateDirectory(SchemaDirectory); - } - - if (!Directory.Exists(PartialSchemaDirectory)) - { - Directory.CreateDirectory(PartialSchemaDirectory); - } - } - - private static void DeleteSchema(string alias) + private void DeleteSchema(string alias) { var file = GetFile(alias); if (file is not null) @@ -167,54 +184,66 @@ private static void DeleteSchema(string alias) } } - private static string GetSchemaContent(string alias, string filePath = null) + private string GetSchemaContent(string alias, string filePath = null) { var schemaFilePath = filePath ?? GetFile(alias); return File.ReadAllText(schemaFilePath); } - private static string GetRelativeFilePath(string alias, SchemaType schemaType, string format) + private string GetFilePath(string schemaName, string alias, SchemaType schemaType, string format) { - return schemaType == SchemaType.Normal - ? Path.Combine(SchemaDirectory, GetFileName(alias, format)) - : Path.Combine(PartialSchemaDirectory, GetFileName(alias, format)); - } + var schemasDirectoryName = _schemaNameService.GetSchemasDirectoryName(); - private static string GetFileName(string alias, string format) - { - if (format.Equals(SchemaConstants.JavascriptFormat)) + // Folder structure is defined by name, therefore name is passed as parameter + if (_schemaNameService.IsDirectorySchemaName(schemaName)) { - return $"{alias}.js"; + var schemaDirectoryPath = _filePathService.GetDirectoryPathBySchemaName(schemaName, schemasDirectoryName); + var fullFilePath = Path.Combine(schemaDirectoryPath, GetFileName(alias, format, schemaType)); + return fullFilePath; } - return $"{alias}.json"; + return Path.Combine(schemasDirectoryName, GetFileName(alias, format, schemaType)); } - - private static string GetFile(string alias) + + private SchemaType GetSchemaTypeFromFilePath(string filePath) { - var currentDirectory = Directory.GetCurrentDirectory(); - var searchDirectory = Path.Combine(currentDirectory, SchemaDirectory); - - if (!Directory.Exists(searchDirectory)) + if (Regex.IsMatch(filePath, ".*.full.(?:js|json)$")) { - return null; + return SchemaType.Normal; + } + if (Regex.IsMatch(filePath, ".*.partial.(?:js|json)$")) + { + return SchemaType.Partial; } - return Directory.GetFiles(searchDirectory, alias + ".json", SearchOption.AllDirectories).FirstOrDefault() ?? - Directory.GetFiles(searchDirectory, alias + ".js", SearchOption.AllDirectories).FirstOrDefault(); + throw new Exception($"file: '{filePath}' is missing a valid schema type. e.g. schemaAlias.full.js or schemaAlias.partial.js"); } - private static string GetAliasFromFilePath(string filePath) + private static string GetFileName(string alias, string format, SchemaType schemaType) { - return Path.GetFileNameWithoutExtension(filePath); - } + var schemaTypeName = schemaType switch + { + SchemaType.Normal => "full", + SchemaType.Partial => "partial", + _ => throw new ArgumentOutOfRangeException(nameof(schemaType), schemaType, null) + }; + var schemaName = $"{alias}.{schemaTypeName}"; + return format.Equals(SchemaConstants.JavascriptFormat) ? $"{schemaName}.js" : $"{schemaName}.json"; + } - private bool CompareSchemaContent(string externalSchema, string localSchema, string schemaFormat) + private string GetFile(string alias) { - return schemaFormat.Equals(SchemaConstants.JsonFormat) - ? CompareJsonSchemas(externalSchema, localSchema) - : CompareJavascriptSchemas(externalSchema, localSchema); + var schemasDirectoryName = _schemaNameService.GetSchemasDirectoryName(); + var searchDirectory = _filePathService.GetRootDirectoryPath(schemasDirectoryName); + + if (!Directory.Exists(searchDirectory)) + { + return null; + } + + return Directory.GetFiles(searchDirectory, alias + ".*.json", SearchOption.AllDirectories).FirstOrDefault() ?? + Directory.GetFiles(searchDirectory, alias + ".*.js", SearchOption.AllDirectories).FirstOrDefault(); } private static bool CompareJavascriptSchemas(string externalSchema, string localSchema) diff --git a/src/Enterspeed.Cli/Services/SchemaService/ISchemaNameService.cs b/src/Enterspeed.Cli/Services/SchemaService/ISchemaNameService.cs new file mode 100644 index 0000000..feb01c2 --- /dev/null +++ b/src/Enterspeed.Cli/Services/SchemaService/ISchemaNameService.cs @@ -0,0 +1,12 @@ +using Enterspeed.Cli.Services.FileService.Models; + +namespace Enterspeed.Cli.Services.SchemaService; + +public interface ISchemaNameService +{ + bool IsDirectorySchemaName(string schemaName); + string BuildNewSchemaName(string existingSchemaName, string relativeDirectoryPathOnDisk); + string GetSchemaName(SchemaFile schemaFile); + string GetAliasFromFilePath(string filePath); + string GetSchemasDirectoryName(); +} \ No newline at end of file diff --git a/src/Enterspeed.Cli/Services/SchemaService/SchemaNameService.cs b/src/Enterspeed.Cli/Services/SchemaService/SchemaNameService.cs new file mode 100644 index 0000000..9e88390 --- /dev/null +++ b/src/Enterspeed.Cli/Services/SchemaService/SchemaNameService.cs @@ -0,0 +1,46 @@ +using Enterspeed.Cli.Services.FileService.Models; + +namespace Enterspeed.Cli.Services.SchemaService; + +public class SchemaNameService : ISchemaNameService +{ + private const string SchemaDirectory = "schemas"; + + public bool IsDirectorySchemaName(string schemaName) + { + return schemaName.Contains('/'); + } + + public string BuildNewSchemaName(string existingSchemaName, string relativeDirectoryPathOnDisk) + { + var lastSegment = Path.GetFileName(existingSchemaName.TrimEnd('/')); + + if (!string.IsNullOrEmpty(relativeDirectoryPathOnDisk)) + { + return $"{relativeDirectoryPathOnDisk.Replace("\\", "/")}/{lastSegment}"; + } + + return lastSegment; + } + + public string GetSchemaName(SchemaFile schemaFile) + { + if (!string.IsNullOrEmpty(schemaFile.RelativeSchemaDirectory)) + { + return $"{schemaFile.RelativeSchemaDirectory.Replace("\\", "/")}/{schemaFile.Alias}"; + } + + return schemaFile.Alias; + } + + public string GetAliasFromFilePath(string filePath) + { + return Path.GetFileNameWithoutExtension(filePath).Replace(".partial", "") + .Replace(".full", ""); + } + + public string GetSchemasDirectoryName() + { + return SchemaDirectory; + } +} \ No newline at end of file