JokeAPI · API Governance Rules

JokeAPI API Rules

Spectral linting rules defining API design standards and conventions for JokeAPI.

46 Rules error 15 warn 21 info 10
View Rules File View on GitHub

Rule Categories

error examples get info no openapi operation parameter path post query request response schema server servers tag tags

Rules

error
info-title-jokeapi
info.title MUST be "JokeAPI" (the canonical product name).
$.info
error
info-description-required
info.description MUST be present with at least 80 characters.
$.info
error
info-version-required
info.version MUST be present and follow semver.
$.info
warn
info-license-mit
License MUST be declared as MIT (JokeAPI is MIT-licensed).
$.info.license
info
info-contact-required
Contact information SHOULD be present.
$.info
warn
openapi-version-3-0
Use OpenAPI 3.0.x — Microcks and tooling compatibility.
$.openapi
error
servers-required
servers MUST be defined and non-empty.
$
error
server-url-https
Every server URL MUST use HTTPS.
$.servers[*].url
info
server-canonical-host
At least one server URL SHOULD point at v2.jokeapi.dev.
$
warn
path-kebab-case
Path segments MUST be lowercase kebab-case.
$.paths.*~
error
path-no-trailing-slash
Paths MUST NOT end with a trailing slash.
$.paths.*~
error
path-no-query-string
Paths MUST NOT contain a query string.
$.paths.*~
error
operation-id-required
Every operation MUST have an operationId.
$.paths.*[get,post,put,patch,delete]
warn
operation-id-camel-case
operationId MUST be camelCase.
$.paths.*[get,post,put,patch,delete].operationId
error
operation-summary-required
Every operation MUST have a summary.
$.paths.*[get,post,put,patch,delete]
warn
operation-summary-jokeapi-prefix
Operation summary MUST start with "JokeAPI ".
$.paths.*[get,post,put,patch,delete].summary
warn
operation-description-required
Every operation MUST have a description of at least 30 characters.
$.paths.*[get,post,put,patch,delete]
error
operation-tags-required
Every operation MUST be tagged.
$.paths.*[get,post,put,patch,delete]
info
operation-microcks-extension
Every operation SHOULD declare an x-microcks-operation extension.
$.paths.*[get,post,put,patch,delete]
warn
tags-global-defined
Global tags array MUST be defined.
$
warn
tag-title-case
Tag names MUST be Title Case (e.g. "Jokes", "Metadata", "System").
$.tags[*].name
warn
tag-description-required
Every global tag MUST have a description.
$.tags[*]
warn
parameter-description-required
Every parameter MUST have a description.
$.paths.*.*.parameters[*]
error
parameter-schema-required
Every parameter MUST have a schema with type.
$.paths.*.*.parameters[*]
warn
parameter-camel-or-kebab-case
Parameter names follow JokeAPI's actual convention — camelCase (`blacklistFlags`, `idRange`) or kebab-case (`safe-mode`).
$.paths.*.*.parameters[*].name
info
parameter-format-enum
`format` parameter SHOULD declare its enum [json, xml, yaml, txt].
$.paths.*.*.parameters[?(@.name=='format')]
warn
request-body-json-content
Request bodies MUST advertise application/json content.
$.paths.*.*.requestBody.content
info
request-body-description
Request bodies SHOULD have a description.
$.paths.*.*.requestBody
error
response-2xx-required
Every operation MUST document at least one 2xx response.
$.paths.*[get,post,put,patch,delete].responses
info
response-429-documented
Operations SHOULD document a 429 response (JokeAPI rate-limits at 120 req/min).
$.paths.*[get,post,put,patch,delete].responses
error
response-description-required
Every response MUST have a description.
$.paths.*.*.responses.*
info
response-json-content
Responses SHOULD include application/json content.
$.paths.*.*.responses.*.content
warn
schema-description-required
Top-level component schemas MUST have a description.
$.components.schemas.*
error
schema-type-required
Top-level component schemas MUST declare a type.
$.components.schemas.*
info
schema-property-camel-case
Schema property names use the same casing as the live API (camelCase or single-word lowercase).
$.components.schemas.*.properties.*~
warn
schema-error-envelope
JokeAPI error responses follow the `{error, code, message, causedBy, timestamp}` envelope.
$.components.schemas.JokeError
warn
schema-timestamp-integer
`timestamp` properties MUST be `integer` (Unix epoch milliseconds).
$..properties.timestamp
warn
no-security-schemes
JokeAPI requires NO authentication — security schemes MUST NOT be declared.
$.components
error
get-no-request-body
GET operations MUST NOT have a request body.
$.paths.*.get
warn
post-has-request-body
POST operations SHOULD declare a request body.
$.paths.*.post
warn
no-empty-descriptions
Descriptions MUST NOT be empty whitespace.
$..description
info
examples-on-schemas
Schema properties SHOULD carry examples to power Microcks mocks.
$.components.schemas.*.properties.*
warn
query-params-casing
Query parameters should be snake_case (the dominant convention in this API).
$.paths[*][get,post,put,patch,delete].parameters[?(@.in=='query')]
warn
schema-names-casing
Component schema names should be PascalCase (the dominant convention in this API).
$.components.schemas
info
schema-properties-casing
Schema properties should be snake_case (the dominant convention in this API).
$.components.schemas[*].properties
warn
error-schema-defined
A shared error schema (JokeError) should be defined for error payloads.
$.components.schemas

Spectral Ruleset

Raw ↑
# jokeapi — Spectral ruleset (strengthened)
# Plain Spectral. Existing hand-authored rules preserved; measured rules added
# from this provider's own OpenAPI conventions by strengthen_ruleset.py,
# then self-validated against the spec.
#
# Provenance:
#   - servers-https-only: 100% of servers already https (error)
#   - query-params-casing: snake @ 81% (n=21)
#   - operationid-casing: camel @ 90% (n=10)
#   - schema-names-casing: pascal @ 100% (n=15)
#   - schema-properties-casing: snake @ 86% (n=78)
#   - error-schema-defined: JokeError
#   - merge: kept 42 existing, added 4 measured, upgraded 0
#   - added: query-params-casing, schema-names-casing, schema-properties-casing, error-schema-defined
extends:
  - spectral:oas
rules:
  info-title-jokeapi:
    description: info.title MUST be "JokeAPI" (the canonical product name).
    message: '{{property}}: title should equal "JokeAPI"'
    severity: error
    given: $.info
    then:
      field: title
      function: pattern
      functionOptions:
        match: ^JokeAPI$
  info-description-required:
    description: info.description MUST be present with at least 80 characters.
    message: '{{property}}: description is missing or too short (<80 chars)'
    severity: error
    given: $.info
    then:
      field: description
      function: length
      functionOptions:
        min: 80
  info-version-required:
    description: info.version MUST be present and follow semver.
    message: '{{property}}: version is missing or not semver'
    severity: error
    given: $.info
    then:
      field: version
      function: pattern
      functionOptions:
        match: ^\d+\.\d+(\.\d+)?$
  info-license-mit:
    description: License MUST be declared as MIT (JokeAPI is MIT-licensed).
    message: '{{property}}: license.name should be "MIT"'
    severity: warn
    given: $.info.license
    then:
      field: name
      function: pattern
      functionOptions:
        match: ^MIT$
  info-contact-required:
    description: Contact information SHOULD be present.
    message: '{{property}}: contact block is missing'
    severity: info
    given: $.info
    then:
      field: contact
      function: truthy
  openapi-version-3-0:
    description: Use OpenAPI 3.0.x — Microcks and tooling compatibility.
    message: '{{property}}: openapi version should match 3.0.x'
    severity: warn
    given: $.openapi
    then:
      function: pattern
      functionOptions:
        match: ^3\.0\.
  servers-required:
    description: servers MUST be defined and non-empty.
    message: '{{property}}: at least one server entry is required'
    severity: error
    given: $
    then:
      field: servers
      function: truthy
  server-url-https:
    description: Every server URL MUST use HTTPS.
    message: '{{value}} must use https://'
    severity: error
    given: $.servers[*].url
    then:
      function: pattern
      functionOptions:
        match: ^https://
  server-canonical-host:
    description: At least one server URL SHOULD point at v2.jokeapi.dev.
    message: '{{property}}: no canonical v2.jokeapi.dev server entry found'
    severity: info
    given: $
    then:
      field: servers
      function: schema
      functionOptions:
        schema:
          type: array
          contains:
            type: object
            properties:
              url:
                type: string
                pattern: ^https://v2\.jokeapi\.dev
            required:
            - url
  path-kebab-case:
    description: Path segments MUST be lowercase kebab-case.
    message: '{{property}}: path segments must be lowercase kebab-case'
    severity: warn
    given: $.paths.*~
    then:
      function: pattern
      functionOptions:
        match: ^(/[a-z0-9-]+|/\{[a-zA-Z]+\})+$
  path-no-trailing-slash:
    description: Paths MUST NOT end with a trailing slash.
    message: '{{property}}: path must not end with /'
    severity: error
    given: $.paths.*~
    then:
      function: pattern
      functionOptions:
        notMatch: .+/$
  path-no-query-string:
    description: Paths MUST NOT contain a query string.
    message: '{{property}}: path must not contain ?'
    severity: error
    given: $.paths.*~
    then:
      function: pattern
      functionOptions:
        notMatch: \?
  operation-id-required:
    description: Every operation MUST have an operationId.
    message: '{{property}}: operationId is missing'
    severity: error
    given: $.paths.*[get,post,put,patch,delete]
    then:
      field: operationId
      function: truthy
  operation-id-camel-case:
    description: operationId MUST be camelCase.
    message: '{{value}}: operationId must be camelCase'
    severity: warn
    given: $.paths.*[get,post,put,patch,delete].operationId
    then:
      function: pattern
      functionOptions:
        match: ^[a-z][a-zA-Z0-9]+$
  operation-summary-required:
    description: Every operation MUST have a summary.
    message: '{{property}}: summary is missing'
    severity: error
    given: $.paths.*[get,post,put,patch,delete]
    then:
      field: summary
      function: truthy
  operation-summary-jokeapi-prefix:
    description: Operation summary MUST start with "JokeAPI ".
    message: '{{value}}: summary must start with "JokeAPI "'
    severity: warn
    given: $.paths.*[get,post,put,patch,delete].summary
    then:
      function: pattern
      functionOptions:
        match: '^JokeAPI '
  operation-description-required:
    description: Every operation MUST have a description of at least 30 characters.
    message: '{{property}}: description is missing or too short'
    severity: warn
    given: $.paths.*[get,post,put,patch,delete]
    then:
      field: description
      function: length
      functionOptions:
        min: 30
  operation-tags-required:
    description: Every operation MUST be tagged.
    message: '{{property}}: tags array is missing or empty'
    severity: error
    given: $.paths.*[get,post,put,patch,delete]
    then:
      field: tags
      function: truthy
  operation-microcks-extension:
    description: Every operation SHOULD declare an x-microcks-operation extension.
    message: '{{property}}: x-microcks-operation is missing'
    severity: info
    given: $.paths.*[get,post,put,patch,delete]
    then:
      field: x-microcks-operation
      function: truthy
  tags-global-defined:
    description: Global tags array MUST be defined.
    message: '{{property}}: global tags array is missing'
    severity: warn
    given: $
    then:
      field: tags
      function: truthy
  tag-title-case:
    description: Tag names MUST be Title Case (e.g. "Jokes", "Metadata", "System").
    message: '{{value}}: tag name must be Title Case'
    severity: warn
    given: $.tags[*].name
    then:
      function: pattern
      functionOptions:
        match: ^[A-Z][a-zA-Z]*( [A-Z][a-zA-Z]*)*$
  tag-description-required:
    description: Every global tag MUST have a description.
    message: '{{property}}: tag description is missing'
    severity: warn
    given: $.tags[*]
    then:
      field: description
      function: truthy
  parameter-description-required:
    description: Every parameter MUST have a description.
    message: '{{property}}: parameter description is missing'
    severity: warn
    given: $.paths.*.*.parameters[*]
    then:
      field: description
      function: truthy
  parameter-schema-required:
    description: Every parameter MUST have a schema with type.
    message: '{{property}}: parameter schema/type is missing'
    severity: error
    given: $.paths.*.*.parameters[*]
    then:
      field: schema
      function: truthy
  parameter-camel-or-kebab-case:
    description: Parameter names follow JokeAPI's actual convention — camelCase (`blacklistFlags`, `idRange`)
      or kebab-case (`safe-mode`).
    message: '{{value}}: parameter name must be camelCase or kebab-case'
    severity: warn
    given: $.paths.*.*.parameters[*].name
    then:
      function: pattern
      functionOptions:
        match: ^[a-z][a-zA-Z0-9-]*$
  parameter-format-enum:
    description: '`format` parameter SHOULD declare its enum [json, xml, yaml, txt].'
    message: '{{property}}: format parameter should enumerate json/xml/yaml/txt'
    severity: info
    given: $.paths.*.*.parameters[?(@.name=='format')]
    then:
      field: schema.enum
      function: truthy
  request-body-json-content:
    description: Request bodies MUST advertise application/json content.
    message: '{{property}}: request body must include application/json'
    severity: warn
    given: $.paths.*.*.requestBody.content
    then:
      field: application/json
      function: truthy
  request-body-description:
    description: Request bodies SHOULD have a description.
    message: '{{property}}: requestBody description is missing'
    severity: info
    given: $.paths.*.*.requestBody
    then:
      field: description
      function: truthy
  response-2xx-required:
    description: Every operation MUST document at least one 2xx response.
    message: '{{property}}: no 2xx response defined'
    severity: error
    given: $.paths.*[get,post,put,patch,delete].responses
    then:
      function: schema
      functionOptions:
        schema:
          type: object
          patternProperties:
            ^2\d\d$:
              type: object
          minProperties: 1
  response-429-documented:
    description: Operations SHOULD document a 429 response (JokeAPI rate-limits at 120 req/min).
    message: '{{property}}: 429 response is missing — JokeAPI is rate-limited'
    severity: info
    given: $.paths.*[get,post,put,patch,delete].responses
    then:
      field: '429'
      function: truthy
  response-description-required:
    description: Every response MUST have a description.
    message: '{{property}}: response description is missing'
    severity: error
    given: $.paths.*.*.responses.*
    then:
      field: description
      function: truthy
  response-json-content:
    description: Responses SHOULD include application/json content.
    message: '{{property}}: response missing application/json content'
    severity: info
    given: $.paths.*.*.responses.*.content
    then:
      field: application/json
      function: truthy
  schema-description-required:
    description: Top-level component schemas MUST have a description.
    message: '{{property}}: schema description is missing'
    severity: warn
    given: $.components.schemas.*
    then:
      field: description
      function: truthy
  schema-type-required:
    description: Top-level component schemas MUST declare a type.
    message: '{{property}}: schema type is missing'
    severity: error
    given: $.components.schemas.*
    then:
      field: type
      function: truthy
  schema-property-camel-case:
    description: Schema property names use the same casing as the live API (camelCase or single-word lowercase).
    message: '{{value}}: schema property must be camelCase or single-word lowercase'
    severity: info
    given: $.components.schemas.*.properties.*~
    then:
      function: pattern
      functionOptions:
        match: ^[a-z][a-zA-Z0-9]*$
  schema-error-envelope:
    description: JokeAPI error responses follow the `{error, code, message, causedBy, timestamp}` envelope.
    message: '{{property}}: JokeError schema must include all canonical envelope fields'
    severity: warn
    given: $.components.schemas.JokeError
    then:
      field: required
      function: schema
      functionOptions:
        schema:
          type: array
          contains:
            type: string
            enum:
            - error
            - code
            - message
            - causedBy
            - timestamp
  schema-timestamp-integer:
    description: '`timestamp` properties MUST be `integer` (Unix epoch milliseconds).'
    message: '{{property}}: timestamp must be type integer'
    severity: warn
    given: $..properties.timestamp
    then:
      field: type
      function: pattern
      functionOptions:
        match: ^integer$
  no-security-schemes:
    description: JokeAPI requires NO authentication — security schemes MUST NOT be declared.
    message: '{{property}}: JokeAPI is open; do not declare securitySchemes'
    severity: warn
    given: $.components
    then:
      field: securitySchemes
      function: falsy
  get-no-request-body:
    description: GET operations MUST NOT have a request body.
    message: '{{property}}: GET operation must not declare a requestBody'
    severity: error
    given: $.paths.*.get
    then:
      field: requestBody
      function: falsy
  post-has-request-body:
    description: POST operations SHOULD declare a request body.
    message: '{{property}}: POST operation should declare a requestBody'
    severity: warn
    given: $.paths.*.post
    then:
      field: requestBody
      function: truthy
  no-empty-descriptions:
    description: Descriptions MUST NOT be empty whitespace.
    message: '{{property}}: description is empty'
    severity: warn
    given: $..description
    then:
      function: pattern
      functionOptions:
        notMatch: ^\s*$
  examples-on-schemas:
    description: Schema properties SHOULD carry examples to power Microcks mocks.
    message: '{{property}}: property has no example value'
    severity: info
    given: $.components.schemas.*.properties.*
    then:
      field: example
      function: truthy
  query-params-casing:
    description: Query parameters should be snake_case (the dominant convention in this API).
    severity: warn
    given: $.paths[*][get,post,put,patch,delete].parameters[?(@.in=='query')]
    then:
      field: name
      function: casing
      functionOptions:
        type: snake
  schema-names-casing:
    description: Component schema names should be PascalCase (the dominant convention in this API).
    severity: warn
    given: $.components.schemas
    then:
      field: '@key'
      function: casing
      functionOptions:
        type: pascal
  schema-properties-casing:
    description: Schema properties should be snake_case (the dominant convention in this API).
    severity: info
    given: $.components.schemas[*].properties
    then:
      field: '@key'
      function: casing
      functionOptions:
        type: snake
  error-schema-defined:
    description: A shared error schema (JokeError) should be defined for error payloads.
    severity: warn
    given: $.components.schemas
    then:
      field: JokeError
      function: truthy