Json Schema has a validation vocabulary which can be used to set constraints on json structures. OpenAPI uses Json Schemas to describe parameters and content, so wouldn’t it be nice to be able to evaluate HTTP request and response messages?
OpenAPI.Evaluation
is a .NET library which can evaluate HTTP request and response messages according to an OpenAPI specification. Json schemas are evaluated using JsonSchema.NET
together with the JsonSchema.Net.OpenApi
vocabulary. It supports the standard HttpRequestMessage
and HttpResponseMessage
and comes with a DelegatingHandler
, OpenApiEvaluationHandler
, which can be used by HttpClient
to intercept and evaluate requests going out and responses coming in according to an OpenAPI 3.1 specification. It’s also possible to manually evaluate requests and responses by traversing the parsed OpenAPI specification and feed it’s evaluators with the corresponding extracted content.
ASP.NET
OpenAPI.Evaluation.AspNet
integrates OpenAPI.Evaluation
with the ASP.NET request pipeline to enable server side evaluation. It comes with extension methods to evaluate HttpRequest
and HttpResponse
abstractions. It also supports integration via the HttpContext
enabling easy access for ASP.NET request and response pipelines and controllers. A middleware is provided for simple integration and can be enabled via the application builder and service collection.
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApiEvaluation(OpenAPI.Evaluation.Specification.OpenAPI.Parse(JsonNode.Parse(File.OpenRead("openapi.json"));
var app = builder.Build();
// Registers the middleware into the request pipeline
app.UseOpenApiEvaluation();
To evaluate directly from a request pipeline, the OpenAPI specification first needs to be loaded and registered as described above. Extension methods for HttpContext
can then be used for request and response evaluation:
var requestEvaluationResult = context.EvaluateRequest();
...
var responseEvaluationResult = context.EvaluateResponse(200, responseHeaders, responseContent);
Evaluation Result
JsonSchema.NET
implements the Json Schema output format which OpenAPI.Evaluation
is influenced by. The OpenAPI specification doesn’t define annotations as Json Schema does, so I decided to adopt something similar. The evaluation result contains information describing each specification object traversed and what path through the specification the evaluation process took. An example produced by the default evaluation result json converter is shown belong, it uses a hierarchical output format.
{
"valid": false,
"evaluationPath": "",
"specificationLocation": "http://localhost/#",
"details": [
{
"valid": false,
"evaluationPath": "/paths",
"specificationLocation": "http://localhost/#/paths",
"details": [
{
"valid": false,
"evaluationPath": "/paths/~1user~1{user-id}",
"specificationLocation": "http://localhost/#/paths/%7e1user%7e1%7buser-id%7d",
"details": [
{
"valid": false,
"evaluationPath": "/paths/~1user~1{user-id}/get",
"specificationLocation": "http://localhost/#/paths/%7e1user%7e1%7buser-id%7d/get",
"details": [
{
"valid": false,
"evaluationPath": "/paths/~1user~1{user-id}/get/parameters",
"specificationLocation": "http://localhost/#/paths/%7e1user%7e1%7buser-id%7d/get/parameters",
"details": [
{
"valid": false,
"evaluationPath": "/paths/~1user~1{user-id}/get/parameters/0",
"specificationLocation": "http://localhost/#/paths/%7e1user%7e1%7buser-id%7d/get/parameters/0",
"details": [
{
"valid": false,
"evaluationPath": "/paths/~1user~1{user-id}/get/parameters/0/$ref/components/parameters/user/content",
"specificationLocation": "http://localhost/#/components/parameters/user/content",
"details": [
{
"valid": false,
"evaluationPath": "/paths/~1user~1{user-id}/get/parameters/0/$ref/components/parameters/user/content/application~1json",
"specificationLocation": "http://localhost/#/components/parameters/user/content/application%7e1json",
"details": [
{
"valid": false,
"evaluationPath": "/paths/~1user~1{user-id}/get/parameters/0/$ref/components/parameters/user/content/application~1json/schema",
"specificationLocation": "http://localhost/#/components/parameters/user/content/application%7e1json/schema",
"schemaEvaluationResults": [
{
"valid": false,
"evaluationPath": "",
"schemaLocation": "http://localhost#",
"instanceLocation": "",
"errors": {
"required": "Required properties [\"first-name\"] are not present"
},
"details": [
{
"valid": true,
"evaluationPath": "/properties/last-name",
"schemaLocation": "http://localhost/#/properties/last-name",
"instanceLocation": "/last-name"
}
]
}
]
}
]
}
]
}
]
}
]
}
]
}
]
}
]
},
{
"valid": true,
"evaluationPath": "/servers",
"specificationLocation": "http://localhost/#/servers",
"details": [
{
"valid": true,
"evaluationPath": "/servers/0",
"specificationLocation": "http://localhost/#/servers/0",
"annotations": {
"url": "http://localhost/v1",
"description": "v1"
}
}
]
}
]
}
Parameter Value Parsers
Headers, path values, query strings and cookies can be described in an OpenAPI specification using a combination of instructive metadata, like styles, and schemas. It’s designed to cater for simple data structures and is complemented by content media types for more complex scenarios.
OpenAPI.Evaluation supports all the styles described in the specification, but it’s not explicitly defined how complex scenarios the specification should support, that is left to implementors to decide. In order to cater for more complex scenarios, it’s possible to define custom parsers per parameter by implementing the IParameterValueParser
and register it when parsing the OpenAPI specification.
OpenAPI.Evaluation.Specification.OpenAPI.Parse(jsonDocument, parameterValueParsers: new[] { customParameterValueParser });
Leave a Comment