Skip to content

Commit

Permalink
Version-specific validators (#12)
Browse files Browse the repository at this point in the history
* Add OcpiValidator base class

These validators will use a specific OCPI version to validate it's entities

* Working on OcpiValidator

* Forward current OcpiVersion to OcpiValidators

* Working on validators for different OCPI versions

* Improve sample app

* Validator improvements
  • Loading branch information
YuriyDurov authored Sep 19, 2023
1 parent 6b7129c commit 0c5fc6e
Show file tree
Hide file tree
Showing 36 changed files with 375 additions and 146 deletions.
6 changes: 6 additions & 0 deletions OCPI.Net.sln
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{1A55D222-2CD3-4C34-AE50-74D051F25E32}"
ProjectSection(SolutionItems) = preProject
docs\OCPI-2.2.1.pdf = docs\OCPI-2.2.1.pdf
docs\OCPI-2.2.pdf = docs\OCPI-2.2.pdf
docs\OCPI_2.0-d2.pdf = docs\OCPI_2.0-d2.pdf
docs\OCPI_2.0.pdf = docs\OCPI_2.0.pdf
docs\OCPI_2.1.1-d2.pdf = docs\OCPI_2.1.1-d2.pdf
docs\OCPI_2.1.1.pdf = docs\OCPI_2.1.1.pdf
docs\OCPI_2.1.pdf = docs\OCPI_2.1.pdf
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OCPI.Net.Tests", "tests\OCPI.Net.Tests\OCPI.Net.Tests.csproj", "{B95AB476-AA1B-44AB-9195-6B1FC2E7BBE6}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ public class OcpiAuthorizeAttribute : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
var token = GetToken(context.HttpContext.Request);
//var token = GetToken(context.HttpContext.Request);

// Your authorization logic.
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,36 +6,43 @@ namespace OCPI.Sample.Controllers;
[OcpiEndpoint(OcpiModule.Credentials, "Receiver", "2.2, 2.2.1")]
[Route("2.2/credentials")]
[Route("2.2.1/credentials")]
public class CredentialsController : OcpiController
public class OcpiCredentialsController : OcpiController
{
[HttpGet]
public IActionResult Get()
{
// Retreive requesting platform's credentials
// Your Credentials GET logic

return OcpiOk(SampleCredentials);
}

[HttpPost]
public IActionResult Post([FromBody] OcpiCredentials credentials)
{
// Register requesting platform
// if (platform.IsRegistered == true) throw ApiException.MethodNotAllowed("The platform is already registered.");

// Your Credentials POST logic

return OcpiOk(SampleCredentials);
}

[HttpPut]
public IActionResult Put([FromBody] OcpiCredentials credentials)
{
// Update requesting platform's credentials
// if (platform.IsRegistered == false) throw ApiException.MethodNotAllowed("The platform has to be registered in order to perform this action.");

// Your Credentials PUT logic

return OcpiOk(SampleCredentials);
}

[HttpDelete]
public IActionResult Delete()
{
// Unregister the requesting platform
// if (platform.IsRegistered == false) throw ApiException.MethodNotAllowed("The platform has to be registered in order to perform this action.");

// Your Credentials DELETE logic

return OcpiOk("Success");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using Microsoft.AspNetCore.Mvc;
using OCPI.Contracts;

namespace OCPI.Sample.Controllers;

[OcpiEndpoint(OcpiModule.Locations, "Receiver", "2.2, 2.2.1")]
[Route("2.2/locations")]
[Route("2.2.1/locations")]
[OcpiAuthorize]
public class OcpiLocationsReceiverController : OcpiController
{
[HttpGet("{countryCode}/{partyId}/{locationId}")]
public IActionResult Get(
[FromRoute] string countryCode,
[FromRoute] string partyId,
[FromRoute] string locationId)
{
return OcpiOk(SampleLocation);
}

[HttpPut("{countryCode}/{partyId}/{locationId}")]
public IActionResult Put(
[FromRoute] string countryCode,
[FromRoute] string partyId,
[FromRoute] string locationId,
[FromBody] OcpiLocation location)
{
// Use a built-in OCPI validator
OcpiValidate(location);

// Your Location PUT logic

return OcpiOk(location);
}

[HttpPatch("{countryCode}/{partyId}/{locationId}")]
public IActionResult Patch(
[FromRoute] string countryCode,
[FromRoute] string partyId,
[FromRoute] string locationId,
[FromBody] OcpiLocation location)
{
// Use a built-in OCPI validator
// Validator behavior will be appropriate to PATCH
// Since this is a PATCH method
// (not throw errors on missing fields)
OcpiValidate(location);

// Your Location PATCH logic

return OcpiOk(location);
}

private static OcpiLocation SampleLocation => new()
{
CountryCode = CountryCode.Belgium,
PartyId = "BEC",
Id = "LOC1",
Publish = true,
Name = "Gent Zuid",
Address = "F.Rooseveltlaan 3A",
City = "Gent",
PostalCode = "9000",
Country = "BEL",
Coordinates = new OcpiGeolocation
{
Latitude = "51.047599",
Longitude = "3.729944"
},
ParkingType = ParkingType.OnStreet,
TimeZone = "Europe/Brussels",
LastUpdated = DateTime.UtcNow
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
using Microsoft.AspNetCore.Mvc;
using OCPI.Contracts;

namespace OCPI.Sample.Controllers;

[OcpiEndpoint(OcpiModule.Locations, "Sender", "2.2, 2.2.1")]
[Route("2.2/locations")]
[Route("2.2.1/locations")]
[OcpiAuthorize]
public class OcpiLocationsSenderController : OcpiController
{
[HttpGet]
public IActionResult GetPage([FromQuery] OcpiPageRequest pageRequest)
{
// Set maximum Limit value
// Required for OCPI.Net PageResult handling
SetMaxLimit(pageRequest, 100);

// Process GetPage using Offset, Limit, DateFrom, DateTo from pageRequest

// You can use BitzArt.Pagination nuget package
// https://github.com/BitzArt/Pagination
// to handle Offset and Limit (does not handle DateFrom/DateTo),
// or implement your own OcpiPageRequest handling logic.

// Example:
var databaseLocations = Enumerable.Range(1, 100).Select(x =>
{
var location = SampleLocation;
location.Id = $"SampleLocation-{x}";
return location;
});

// This will handle Offset and Limit but not DateFrom or DateTo:
var result = databaseLocations.ToPage(pageRequest);

// Returning a PageResult in OcpiOk will process the paginated response
// and add appropriate page response headers.
return OcpiOk(result);
}

private static OcpiLocation SampleLocation => new()
{
CountryCode = CountryCode.Belgium,
PartyId = "BEC",
Id = "LOC1",
Publish = true,
Name = "Gent Zuid",
Address = "F.Rooseveltlaan 3A",
City = "Gent",
PostalCode = "9000",
Country = "BEL",
Coordinates = new OcpiGeolocation
{
Latitude = "51.047599",
Longitude = "3.729944"
},
ParkingType = ParkingType.OnStreet,
TimeZone = "Europe/Brussels",
LastUpdated = DateTime.UtcNow
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
namespace OCPI.Sample.Controllers;

[Route("versions")]
public class VersionsController : OcpiController
public class OcpiVersionsController : OcpiController
{
// This service scans your codebase and maps all OCPI paths from
// controllers marked with OcpiEndpoint attribute on startup.
private readonly IOcpiVersionService _versionService;

public VersionsController(IOcpiVersionService versionService)
public OcpiVersionsController(IOcpiVersionService versionService)
{
_versionService = versionService;
}
Expand Down
3 changes: 2 additions & 1 deletion src/OCPI.Net.Controllers/Models/OcpiControllerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection;
using OCPI.Contracts;
using OCPI.Services;

namespace OCPI;
Expand All @@ -27,7 +28,7 @@ public OkObjectResult OcpiOk([ActionResultObjectValue] object? value)
public void OcpiValidate<T>(T value)
{
if (value is null) return;
var validator = GetRequiredService<ActionValidator<T>>();
var validator = GetRequiredService<OcpiValidator<T>>();
var validationResult = validator.Validate(value);

if (!validationResult.IsValid)
Expand Down
2 changes: 1 addition & 1 deletion src/OCPI.Net.Controllers/Models/OcpiDateTimeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ internal class OcpiDateTimeConverter : JsonConverter<DateTime>
{
private const string _format = "yyyy'-'MM'-'dd'T'HH':'mm':'ss'.'fffZ";

private static readonly DateTime _start = new(2020, 1, 1);
private static readonly DateTime _start = new(2010, 1, 1);
private static readonly DateTime _end = new(2100, 1, 1);

public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
Expand Down
13 changes: 9 additions & 4 deletions src/OCPI.Net.Validation/Extensions/AddOcpiValidationExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using OCPI.Contracts;

namespace OCPI;

Expand All @@ -19,16 +20,20 @@ public static WebApplicationBuilder AddOcpiValidation(this WebApplicationBuilder
.Assembly.DefinedTypes
.Where(x => !x.IsAbstract)
.Select(x => x.DeclaringType)
.Where(x => x!.IsSubclassOfRawGeneric(typeof(ActionValidator<>)));
.Where(x => x!.IsSubclassOfRawGeneric(typeof(OcpiValidator<>)) && x!.IsGenericType == false);

foreach (var type in validatorTypes)
{
var modelType = type!.BaseType!.GenericTypeArguments.First();
var resultingType = typeof(ActionValidator<>).MakeGenericType(modelType);
var resultingType = typeof(OcpiValidator<>).MakeGenericType(modelType);
builder.Services.AddScoped(resultingType, x =>
{
var actionType = x.GetRequiredService<HttpContext>().Request.Method.ToActionType();
var instance = Activator.CreateInstance(type, actionType);
var httpContext = x.GetRequiredService<HttpContext>();
var request = httpContext.Request;

var ocpiVersion = request.GetCurrentOcpiVersion();
var actionType = request.Method.ToActionType();
var instance = Activator.CreateInstance(type, actionType, ocpiVersion);
return instance!;
});
}
Expand Down
6 changes: 6 additions & 0 deletions src/OCPI.Net.Validation/OCPI.Net.Validation.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,10 @@
<ProjectReference Include="..\OCPI.Net.Exceptions\OCPI.Net.Exceptions.csproj" />
</ItemGroup>

<ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>OCPI.Net.Controllers</_Parameter1>
</AssemblyAttribute>
</ItemGroup>

</Project>
19 changes: 19 additions & 0 deletions src/OCPI.Net.Validation/Utility/GetCurrentOcpiVersionExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using BitzArt.EnumToMemberValue;
using Microsoft.AspNetCore.Http;

namespace OCPI;

internal static class GetCurrentOcpiVersionExtension
{
public static OcpiVersion GetCurrentOcpiVersion(this HttpRequest request)
{
var path = request.Path.Value!.TrimStart('/');
var versionLength = path.IndexOf('/');
if (versionLength == -1) versionLength = path.Length;

var versionString = path[..versionLength];

var result = versionString.ToEnum<OcpiVersion>();
return result;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

namespace OCPI.Contracts;

internal partial class OcpiAdditionalGeolocationValidator : ActionValidator<OcpiAdditionalGeolocation>
internal partial class OcpiAdditionalGeolocationValidator : OcpiValidator<OcpiAdditionalGeolocation>
{
public OcpiAdditionalGeolocationValidator(ActionType actionType) : base(actionType)
public OcpiAdditionalGeolocationValidator(ActionType actionType, OcpiVersion ocpiVersion) : base(actionType, ocpiVersion)
{
JsonRuleFor(x => x.Latitude)
.NotEmpty()
Expand All @@ -16,7 +16,7 @@ public OcpiAdditionalGeolocationValidator(ActionType actionType) : base(actionTy
.ValidLongitude();

JsonRuleFor(x => x.Name!)
.SetValidator(new OcpiDisplayTextValidator(actionType))
.SetValidator(new OcpiDisplayTextValidator(actionType, ocpiVersion))
.When(x => x.Name is not null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

namespace OCPI.Contracts;

internal class OcpiBusinessDetailsValidator : ActionValidator<OcpiBusinessDetails>
internal class OcpiBusinessDetailsValidator : OcpiValidator<OcpiBusinessDetails>
{
public OcpiBusinessDetailsValidator(ActionType actionType) : base(actionType)
public OcpiBusinessDetailsValidator(ActionType actionType, OcpiVersion ocpiVersion) : base(actionType, ocpiVersion)
{
JsonRuleFor(x => x.Name)
.NotEmpty()
Expand All @@ -15,6 +15,6 @@ public OcpiBusinessDetailsValidator(ActionType actionType) : base(actionType)
.ValidUrl();

JsonRuleFor(x => x.Logo!)
.SetValidator(new OcpiImageValidator(actionType));
.SetValidator(new OcpiImageValidator(actionType, ocpiVersion));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

namespace OCPI.Contracts;

internal class OcpiDisplayTextValidator : ActionValidator<OcpiDisplayText>
internal class OcpiDisplayTextValidator : OcpiValidator<OcpiDisplayText>
{
public OcpiDisplayTextValidator(ActionType actionType) : base(actionType)
public OcpiDisplayTextValidator(ActionType actionType, OcpiVersion ocpiVersion) : base(actionType, ocpiVersion)
{
JsonRuleFor(x => x.Language)
.NotEmpty()
Expand Down
Loading

0 comments on commit 0c5fc6e

Please sign in to comment.