From 8ffbe6ed26d6a8b5257b17cc57a7441468e38c7c Mon Sep 17 00:00:00 2001 From: Yuriy Durov Date: Wed, 10 Jan 2024 19:32:44 +0400 Subject: [PATCH] Improvements to OCPI endpoint version parsing --- sample/OCPI.Net.Sample/appsettings.json | 3 +- .../Extensions/AddOcpiVersioningExtension.cs | 1 + .../Services/OcpiVersionService.cs | 89 +++++++++++++++++-- .../Enums/Versioning/OcpiVersion.cs | 4 +- 4 files changed, 84 insertions(+), 13 deletions(-) diff --git a/sample/OCPI.Net.Sample/appsettings.json b/sample/OCPI.Net.Sample/appsettings.json index 56c7152..e18ebe5 100644 --- a/sample/OCPI.Net.Sample/appsettings.json +++ b/sample/OCPI.Net.Sample/appsettings.json @@ -2,7 +2,8 @@ "Logging": { "LogLevel": { "Default": "Information", - "Microsoft.AspNetCore": "Warning" + "Microsoft.AspNetCore": "Warning", + "OCPI.Versioning.Services.OcpiVersionService": "Debug" } }, "OCPI": { diff --git a/src/OCPI.Net.Controllers/Extensions/AddOcpiVersioningExtension.cs b/src/OCPI.Net.Controllers/Extensions/AddOcpiVersioningExtension.cs index f8f39ec..0a95bd9 100644 --- a/src/OCPI.Net.Controllers/Extensions/AddOcpiVersioningExtension.cs +++ b/src/OCPI.Net.Controllers/Extensions/AddOcpiVersioningExtension.cs @@ -10,6 +10,7 @@ public static WebApplicationBuilder AddOcpiVersioning(this WebApplicationBuilder { builder.AddOcpiOptions(); + builder.Services.AddLogging(); builder.Services.AddSingleton(); return builder; diff --git a/src/OCPI.Net.Controllers/Services/OcpiVersionService.cs b/src/OCPI.Net.Controllers/Services/OcpiVersionService.cs index 61113af..64aba2c 100644 --- a/src/OCPI.Net.Controllers/Services/OcpiVersionService.cs +++ b/src/OCPI.Net.Controllers/Services/OcpiVersionService.cs @@ -1,5 +1,6 @@ using BitzArt.EnumToMemberValue; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; using OCPI.Contracts; namespace OCPI.Versioning.Services; @@ -7,14 +8,18 @@ namespace OCPI.Versioning.Services; internal class OcpiVersionService : IOcpiVersionService { private readonly OcpiOptions _options; + private readonly ILogger _logger; private readonly ICollection _routeMaps; private readonly IDictionary> _versionRouteMaps; - public OcpiVersionService(OcpiOptions options) + public OcpiVersionService(OcpiOptions options, ILogger logger) { _options = options; + _logger = logger; + + _logger.LogInformation("Initializing OcpiVersionService"); _routeMaps = new HashSet(); _versionRouteMaps = new Dictionary>(); @@ -24,9 +29,14 @@ public OcpiVersionService(OcpiOptions options) .GetAssemblies() .SelectMany(x => x.DefinedTypes) .Where(x => !x.IsAbstract) - .Where(x => typeof(OcpiController).IsAssignableFrom(x)); + .Where(x => typeof(OcpiController).IsAssignableFrom(x)) + .ToList(); + + _logger.LogInformation("{count} OCPI Controllers found", controllerTypes.Count); foreach (var type in controllerTypes) ParseEndpoint(type); + + _logger.LogInformation("OCPI Controllers parsed successfully"); } public IEnumerable GetRoutes() => _routeMaps; @@ -60,10 +70,15 @@ private OcpiVersionInfo GetVersionInfo(OcpiVersion version, string template) public OcpiVersionDetails GetVersionDetails(string request) { - var version = request.ToEnum(OcpiVersion.Invalid); - if (version == OcpiVersion.Invalid) throw OcpiException.ClientError($"Invalid OCPI Version: '{request}'."); - - return GetVersionDetails(version); + try + { + var version = request.ToEnum(); + return GetVersionDetails(version); + } + catch (Exception ex) + { + throw OcpiException.ClientError($"Invalid OCPI Version: '{request}'.", ex); + } } public OcpiVersionDetails GetVersionDetails(OcpiVersion request) @@ -118,12 +133,13 @@ private void ParseEndpoint(Type type) var endpoint = GetAttribute(type); if (endpoint is null) return; - var routes = GetAttributes(type).Select(x => x.Template); + _logger.LogDebug("Parsing OCPI endpoints for Controller {ControllerName}...", type.Name); + + var routes = GetAttributes(type).Select(x => x.Template).ToList(); foreach (var version in endpoint.Versions) { - var route = routes.FirstOrDefault(x => x.StartsWith(version.ToMemberValue())); - if (route is null) throw new Exception($"Appropriate route not found for Controller {type.Name} and OCPI Version {version.ToMemberValue()}"); + var route = FindRouteForVersion(version, endpoint.Versions, routes, type.Name); foreach (var role in endpoint.Roles) { @@ -141,6 +157,61 @@ private void ParseEndpoint(Type type) } } + private string FindRouteForVersion(OcpiVersion version, IEnumerable allVersions, IEnumerable routes, string controllerName) + { + var target = version.ToMemberValue(); + + _logger.LogDebug("Attempting to find a matching route for OCPI Version {version}", target); + + var otherVersions = allVersions + .Except(new[] { version }) + .Select(x => x.ToMemberValue()) + .ToList(); + + var badMatches = new List(); + + foreach (var route in routes) + { + if (!route.Contains(target)) continue; + + _logger.LogDebug("Route '{route}' matched with OCPI Version {target}", route, target); + + var otherMatches = otherVersions.Where(route.Contains).ToList(); + if (otherMatches.Any()) + { + _logger.LogDebug("Route '{route}' also matched with other OCPI Versions this endpoint implements.", route); + + if (otherMatches.All(target.Contains)) + { + _logger.LogDebug("Version conflict successfully resolved, using route '{route}'", route); + return route; + } + + _logger.LogDebug("Version conflict could not be resolved, keeping '{route}' as bad match for version '{version}'", route, target); + badMatches.Add(route); + continue; + } + + _logger.LogDebug("A good match was found!"); + + return route; + } + + if (!badMatches.Any()) throw new RouteNotFoundForOcpiVersionException($"No matching route was found for Controller {controllerName} and OCPI Version {version.ToMemberValue()}"); + + _logger.LogWarning("No good matches were found for Endpoint {endpoint} and OcpiVersion {target}, using a bad match.", controllerName, target); + return badMatches.First(); + } + + internal class RouteNotFoundForOcpiVersionException : Exception + { + public RouteNotFoundForOcpiVersionException(string controllerName, string version) : base($"Unable to find a matching route for Controller {controllerName} and OCPI Version {version}") { } + + public RouteNotFoundForOcpiVersionException(string message) : base(message) + { + } + } + private void AddToVersionMap(OcpiVersion version, OcpiEndpointRouteMap route) { var versionPresent = _versionRouteMaps.TryGetValue(version, out var map); diff --git a/src/OCPI.Net.Core/Enums/Versioning/OcpiVersion.cs b/src/OCPI.Net.Core/Enums/Versioning/OcpiVersion.cs index 6f0b130..8f51713 100644 --- a/src/OCPI.Net.Core/Enums/Versioning/OcpiVersion.cs +++ b/src/OCPI.Net.Core/Enums/Versioning/OcpiVersion.cs @@ -2,10 +2,8 @@ namespace OCPI; -public enum OcpiVersion : ushort +public enum OcpiVersion : byte { - Invalid = 0, - [EnumMember(Value = "2.0")] v2_0 = 200,