~jomco/openapi-v3-validator

Clojure OpenAPI v3 validator library
Hints for min/max-length/items
Reuse re-pred for pattern validator

clone

read-only
https://git.sr.ht/~jomco/openapi-v3-validator
read/write
git@git.sr.ht:~jomco/openapi-v3-validator

You can also use your local clone with git send-email.

#OpenAPI V3 Validator

A pure-clojure library for validating ring requests & responses against OpenAPI v3 specifications.

#Build status

builds.sr.ht status

#Deps

Clojars Project

#Goals

#Portable clojure

Long term goal: Should work on any Clojure implementation.

Status: the core validator code has no dependencies and should work with minimal changes on any Clojure implementation. Clojure JVM and Babashka are currently tested as part of development.

Target Status Remarks
JVM Clojure DONE
Babashka DONE
ClojureScript TODO Patches welcome
.NET Clojure N/A Not planned, patches welcome

#Schema validation

Target: Complete implementation of Ring request/response maps against OpenAPI v3.1.0 (the current version of the OpenAPI specification). We do not intent to implement v2 or earlier OpenAPI specifications.

Status: mostly complete and usable for real world scenarios. See below table.

Validation Status Remarks
JSON $dynamicAnchor N/A Not implemented, not planned for development
JSON $dynamicRef N/A Not implemented, not planned for development
JSON $id N/A Not implemented, not planned for development
JSON $ref PARTIAL Simple fragment identifiers only
JSON $vocabulary N/A Not implemented, not planned for development
JSON additionalProperties DONE
JSON allOf DONE Issues of sub-schemas are concatenated
JSON anyOf DONE Reports :sub-issues
JSON const DONE Using JS-style semantics (unified numeric types)
JSON contains DONE Using JS-style semantics (unified numeric types)
JSON contentEncoding TODO Should be configurable
JSON contentMediaType TODO Should be configurable
JSON contentSchema TODO
JSON dependentRequired DONE
JSON dependentSchemas TODO
JSON discriminator PARTIAL OpenAPI Extension; implemented for oneOf schemas only
JSON enum DONE
JSON exclusiveMaximium DONE
JSON exclusiveMinimum DONE
JSON format PARTIAL Only validates "uuid", disabled by default
JSON if then else DONE
JSON items DONE
JSON maxItems DONE
JSON maxLength DONE Counts Unicode codepoints (not Java chars)
JSON maxProperties DONE
JSON maximum DONE
JSON minItems DONE
JSON minLength DONE Counts Unicode codepoints (not Java chars)
JSON minProperties DONE
JSON minimum DONE
JSON multipleOf DONE
JSON not DONE
JSON oneOf DONE Reports :sub-issues
JSON pattern DONE Using java.util.regex.Pattern
JSON patternProperties DONE Using java.util.regex.Pattern
JSON prefixItems DONE
JSON propertyNames DONE
JSON properties DONE
JSON required DONE
JSON type DONE Using JS-Style semantics (unified numeric types)
JSON unevaluatedItems TODO
JSON unevaluatedProperties TODO
JSON uniqueItems DONE Using JS-style semantics (unified numeric types)
JSON xml TODO OpenAPI Extension
OpenAPI PARTIAL v3.1.0 mostly implemented
OpenAPI $ref PARTIAL Simple fragment identifiers only
OpenAPI Callback N/A Not relevant
OpenAPI Components DONE Can be referred to using $ref
OpenAPI Contact N/A Not relevant
OpenAPI Encoding TODO Not implemented
OpenAPI Header DONE
OpenAPI Info N/A Not relevant
OpenAPI Licence N/A Not relevant
OpenAPI Link N/A Not relevant
OpenAPI Media Type DONE Any content-type, body must be parsed before validation
OpenAPI OAuth Flow TODO
OpenAPI OAuth Flows TODO
OpenAPI Operation DONE Including headers and parameters
OpenAPI Parameter PARTIAL Except allowEmptyValue, style=form + explode=true, deepObject=true
OpenAPI Path Item PARTIAL Parameters in paths item are not validated
OpenAPI Paths DONE
OpenAPI Request Body DONE
OpenAPI Response DONE
OpenAPI Responses DONE
OpenAPI Schema PARTIAL See JSON entries in this table for status per JSON Schema keyword
OpenAPI Security Requirement TODO
OpenAPI Security Scheme TODO
OpenAPI Server TODO Not sure if relevant to validation
OpenAPI Server Variable N/A Not relevant
OpenAPI Tag N/A Not relevant

#Other TODO items

#Usage

Validating OpenAPI requests/responses:

(require '[nl.jomco.openapi.v3.validator :as validator])
(require '[clojure.data.json :as json])
(require '[clojure.java.io :as io])

(def ooapi-spec
  (json/read (io/reader (io/file "ooapiv5.json")) {:key-fn identity))

(def validate
  (-> ooapi-spec
      (validator/validator-context nil)
      validator/interaction-validator))


(validate {:request  {:method       :get
                      :uri          "/courses"
                      :query-params {"pageNumber" "foo"
                                     "sort"       "something,-name"}}
           :response {:status  200
                      :headers {"content-type" "application/json; charset=utf8"}
                      :body    {"pagesize"        0
                                "pageNumber"      1
                                "hasPreviousPage" false
                                "hasNextPage"     "true"
                                "items"           []}}}
          [])

;; =>

[{:canonical-schema-path ["components" "parameters" "pageNumber"],
  :instance              "foo",
  :issue                 "coercion-error",
  :path                  [:request :query-params "pageNumber"],
  :schema                {"type" "integer"},
  :schema-path           ["paths" "/courses" "get" "parameters" 2 "schema"]}
 {:instance              "something",
  :path                  [:request :query-params "sort" 0],
  :schema-path           ["paths" "/courses" "get" "parameters" 8 "schema" "items" "enum"],
  :canonical-schema-path ["paths" "/courses" "get" "parameters" 8 "schema" "items" "enum"],
  :schema-keyword        "enum",
  :schema                {"enum" ["courseId" "name" "-courseId" "-name"]},
  :issue                 "schema-validation-error"}
 {:instance              {"pagesize"        0,
                          "pageNumber"      1,
                          "hasPreviousPage" false,
                          "hasNextPage"     "true",
                          "items"           []},
  :path                  [:response :body],
  :schema-path           ["paths" "/courses" "get" "responses" "200" "content"
                          "application/json" "schema" "allOf" 0 "required"],
  :canonical-schema-path ["components" "schemas" "Pagination" "required"],
  :hints                 {:missing ["pageSize"]},
  :schema-keyword        "required",
  :schema                {"required" ["pageSize" "pageNumber" "hasPreviousPage"
                                      "hasNextPage" "items"]},
  :issue                 "schema-validation-error"}
 {:instance              "true",
  :path                  [:response :body "hasNextPage"],
  :schema-path           ["paths" "/courses" "get" "responses" "200" "content"
                          "application/json" "schema" "allOf" 0 "properties" "hasNextPage" "type"],
  :canonical-schema-path ["components" "schemas" "Pagination" "properties" "hasNextPage" "type"],
  :schema-keyword        "type",
  :schema                {"type" "boolean"},
  :issue                 "schema-validation-error"}]

Validating JSON Schemas:

(require '[nl.jomco.openapi.v3.schema-validator :as validator])

(def validate
   (validator/schema-validator
      (validator/validator-context specification)
      ["path" "to" "schema" "in" "spec"]))

(def issues
  (validate instance ["path" "of" "instance"]))

#Validation issue format

Validations result in nil when no issues are present, or a collection of issues.

Issues are maps with a key :issue with one of the following values:

  • "schema-validation-error" - instance (body or parameter part) did not validate according to the JSON Schema specification.
  • "coercion-error" - can't coerce parameter to the correct type
  • "method-error" - the request method did not match the specification
  • "uri-error" - the uri path did not match the specification
  • "content-type-error" - the request/response content type did not match the specification.
  • "status-error" - the response status code did not match the specification.

An issue can optionally have one or more of the following keys:

  • :instance - the part of the document that failed to validate.
  • :path - the absolute path to instance
  • :schema-path the path in the specification that resolved to the failing validation.
  • :canonical-schema-path the absolute path to the schema of the failing validation.
  • :schema the relevant parts of the schema that did not validate. Usually a map.
  • :hints a map with additional information depending on the error.
  • :sub-issues - for combining schemas (oneOf and anyOf), the collections of results of the sub schema validations.
  • :schema-keyword - for JSON Schema keyword validations, the "main" schema keyword of the failing validation.

Paths are vectors of keys (strings, keywords and integers).

#Interaction / request / response format

Requests and resposes follow ring format:

  • :uri is the path of the request
  • :request-params is a map with string keys
  • :headers is a map of with string keys
  • :method is a keyword :get, :put, :post etc
  • :body if present should be a parsed document (probably JSON) and have string keys.

Interactions are maps with :request and :response keys

#Equality semantics

In JSON Schema (according to the JSON Schema Test Suite), numeric semantics are different from Java / Clojure:

  • There is only one Number type for decimals and integers, so 10.0 (decimal) is equal to 10 (integer).
  • No complex numbers

Implications:

  • [5.0] is equal to [5]
  • [5, 5.0] does not have unique entries
  • {"a": 5} is equal to {"a": 5.0}
  • 5.0 validates according to {"type": "integer"}

Further more, some of the JSON Schema Test Suite expects exact results for operations (multipleOf) on decimal numbers (BigDecimal semantics).

Affected JSON Schemas keywords:

  • const
  • unique
  • enum
  • type
  • multipleOf

Java / Clojure JSON parsers tend to parse 5.0 as a double, and 5 as a long, and you can't rely on a {"type": "integer"} schema validation to restrict input -- 5.0 is explictly valid for {"type": "integer"}.

Note: clojure.data.json has a :bigdec option, but that only applies to numbers with decimal parts. Non-decimal numbers are always parsed as long or BigInteger.

The JSON Schema validator, nl.jomco.openapi.v3.validator.json-schema-validator by default uses the semantics of JSON Schema Test Suite, while working with standard JSON parsers.

The solution implemented is to use a json-coerce function to coerce numbers (as scalars and in collections) to BigDecimals, and then compare using the coerced values.

You can turn this off and use standard Clojure semantics by passing the :numeric-coercion identity option to validator/validator-context.

#CHANGELOG

#0.2.6

  • Add count hints for maxLength, MinLength, MaxItems and MinItems
  • Fix maxProperties count hint
  • Add missing validations to README

#0.2.5

  • Mark one more namespace as undocumented.

#0.2.4

  • Improve API documentation. Mark internal namespaces and vars as undocumented.

#0.2.3

  • Implement discriminator OpenAPI JSON Schema extension. This is only implemented when combined with oneOf since a discriminator makes no sense in combination with allOf or someOf.

#0.2.2

  • Fix test dependencies on babashka; use org.babashka/json

#0.2.1

  • Pluggable numeric comparison semantics using :numeric-coercion option.
  • Fix "uri-error" and "method-error" when request does not match specification

#0.2.0

  • Implement if then else JSON Schema keywords
  • Implement prefixItems JSON Schema keyword
  • Implement patternProperties and additionalProperties JSON Schema keywords
  • Fix exclusiveMaximium and exclusiveMinimum keywords
  • Implement propertyNames JSON Schema keyword
  • Fix minContains = 0 with maxContains
  • Fix maxContains without minContains
  • Handle $ref as part of JSON Schema
  • Handle # as root JSON pointer
  • Handle trailing "/" in JSON Pointers
  • Fix minLength and maxLength to count codepoints
  • Fix "const": null validation
  • Disabled format validations by default (according to spec)
  • Add JSON-Schema-Test-Suite tests
  • Include license info in pom.xml

#0.1.83

  • Removed txt files that broke cljdoc.org

#0.1.82

  • Many small fixes and JSON schema keywords supported
  • Documentation of what is supported
  • Documentation of issue format
  • OpenAPI specification docs and request/reponse bodies should now be parsed /without/ keywordized keys (plain strings).

#0.1.77

  • Moved to deps.edn / tools.build project structure
  • Renamed namespaces
  • Lots of little fixes

#0.1.0-SNAPSHOT

  • Initial test release

#License

Copyright © 2022-2023 Joost Diepenmaat, Jomco BV

This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0 which is available at http://www.eclipse.org/legal/epl-2.0.

This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version, with the GNU Classpath Exception which is available at https://www.gnu.org/software/classpath/license.html.