Skip to content

Tag: json schema

Parsing OpenAPI Style Parameters

Parameters in the OpenAPI 3.1 specification can be defined in two ways using a JSON schema; either by using a media type object, which is useful when the parameter is complex to describe, or by using styles, which is more common in simple scenarios, which often is the case with HTTP parameters.

Let’s have a look at the styles defined and where they fit into a HTTP request.

Path Parameters

Path parameters are parameters defined in a path, for example id in the path /user/{id}. Path parameters can be described using label, matrix or simple styles, which are all defined by RFC6570, URI Template.

Here are some examples using the JSON primitive value 1 for a parameter named id :

  • Simple: /user/1
  • Matrix: /user/;id=1
  • Label: /user/.1

It’s also possible to describe arrays. Using the same parameter as above with two JSON primitive values, 1 and 2, it get’s serialized as:

  • Simple: /users/1,2
  • Matrix: /user/;id=1,2
  • Label: /user/.1.2

Given a JSON object for a parameter named user with the value, { "id": 1, "name": "foo" }, it becomes:

  • Simple: /user/id,1,name,foo
  • Matrix: /user/;user=id,1,name,foo
  • Label: /user/.id.1.name.foo

The explode modifier can be used to enforce composite values (name/value pairs). For primitive values this has no effect, neither for label and simple arrays. With the above examples and styles where explode has effect, here’s the equivalent:

Arrays

  • Matrix: /user/;id=1;id=2

Objects

  • Simple: /user/id=1,name=foo
  • Matrix: /user/;id=1;name=foo
  • Label: /user/.id=1.name=foo

Query Parameters

Query parameters can be described with form, space delimited, pipe delimited or deep object styles. The form style is defined by RFC6570, the rest are defined by OpenAPI.

Primitives

Using the example from path parameters, a serialized user, ?user={user}, or user id, ?id={id}, defined as a query parameter value would look like:

  • Form: id=1

Note that the examples doesn’t describe any primitive values for pipe and space delimited styles, even though they are quite similar to the simple style.

Arrays

  • Form: id=1,2
  • SpaceDelimited: id=1%202
  • PipeDelimited: id=1|2

Objects

  • Form: user=id,1,name,foo
  • SpaceDelimited: user=id%201%20name%20foo
  • PipeDelimited: user=id|1|name|foo

Note that the examples lack the parameter name for array and objects, this has been corrected in 3.1.1.

Not defining explode for deepObject style is not applicable, and like path styles, explode doesn’t have effect on primitive values.

Exploded pipe and space delimited parameters are not described in the example, even though they are similar to form. Do note though that neither of them would be possible to parse, as the parameter name cannot be inferred.

With all this in mind here are the respective explode examples:

Arrays

  • Form: id=1&id=2
  • SpaceDelimited: id=1%202
  • PipeDelimited: id=1|2

Objects

  • Form: id=1&name=foo
  • SpaceDelimited: id=1%20name=foo
  • PipeDelimited: id=1|name=foo
  • DeepObject: user[id]=1&user[name]=foo

Header Parameters

Header parameter can only be described using the simple style.

Given the header user: {user} and id: {id}, a respective header parameter value with simple style would look like:

Primitives

  • Simple: 1

Arrays

  • Simple: 1,2

Objects

  • Simple: id,1,name,foo

Similar to the other parameters described, explode with primitive values have no effect, neither for arrays. For objects it would look like:

  • Simple: id=1,name=foo

Cookie Parameters

A cookie parameter can only be described with form style, and is represented in a similar way as query parameters. Using the example Cookie: id={id} and Cookie: user={user} a cookie parameter value would look like:

Primitives

  • Form: id=1

Arrays

  • Form: id=1,2

Objects

  • Form: user=id,1,name,foo

Similar to the other parameters described, explode with primitive values have no effect. For arrays and objects it looks like:

Arrays

  • Form: id=1&id=2

Objects

  • Form: id=1&name=foo

Note that exploded objects for cookie parameters have the same problem as query parameters; the parameter name cannot be inferred.

Object Complexity

Theoretically an object can have an endless deep property structure, where each property are objects that also have properties that are objects and so on. RFC6570 nor OpenAPI 3.1 defines how deep a structure can be, but it would be difficult to define array items and object properties as objects in most styles.

As OpenAPI provides media type objects as a complement for complex parameters, it’s advisable to use those instead in such scenarios.

OpenAPI.ParameterStyleParsers

To support parsing and serialization of style defined parameters, I’ve created a .NET library, OpenAPI.ParameterStyleParsers. It parses style serialized parameters into the corresponding JSON instance and vice versa. It supports all examples defined in the OpenAPI 3.1 specification, corrected according to the inconsistencies described earlier. It only supports arrays with primitive item values and objects with primitive property values, for more complex scenarios use media type objects. The JSON instance type the parameter get’s parsed according to is determined by the schema type keyword. If no type information is defined it falls back on a best effort guess based on the parameter style.

Leave a Comment

OpenAPI Evaluation

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