{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://apigenie.pl/schemas/apigenie-contract-report.json",
  "title": "apigenie-server contract report",
  "description": "The contract report of `apigenie-server` (the `--report` file and `GET /__apigenie/contract`) for OpenAPI, AsyncAPI, GraphQL and gRPC specs. The members shared by the four families are described here; each family adds its own (the OpenAPI report lists `operations` and `undocumented` routes, the gRPC one `calls`...). Adding members keeps formatVersion; removing a member or changing its meaning or type increments it.",
  "type": "object",
  "required": ["report", "formatVersion", "spec", "summary"],
  "properties": {
    "report": {
      "description": "apigenie-server-contract; apigenie test writes the same members under apigenie-test.",
      "enum": ["apigenie-server-contract", "apigenie-test"]
    },
    "formatVersion": {
      "description": "The version of this format.",
      "const": 1
    },
    "version": {
      "description": "The former name of formatVersion, removed in the next release.",
      "const": 1,
      "deprecated": true
    },
    "spec": {
      "description": "The spec the traffic was checked against.",
      "type": "string"
    },
    "upstream": {
      "description": "The real service of the validating proxy (--proxy-to).",
      "type": "string"
    },
    "clients": {
      "description": "The clients that presented a TLS certificate (apigenie-server --tls-client-ca), in the order first seen.",
      "type": "array",
      "items": {
        "type": "object",
        "required": ["subject", "exchanges"],
        "properties": {
          "subject": { "description": "The certificate's subject, e.g. \"CN=client, O=Example\".", "type": "string" },
          "exchanges": { "description": "HTTP requests, gRPC calls, WebSocket connections and MQTT messages that carried it.", "type": "integer", "minimum": 1 }
        },
        "additionalProperties": false
      }
    },
    "startedAt": { "type": "string", "format": "date-time" },
    "endedAt": { "type": "string", "format": "date-time" },
    "validation": {
      "type": "object",
      "properties": {
        "request": { "type": "string" },
        "response": { "type": "string" }
      }
    },
    "summary": {
      "type": "object",
      "properties": {
        "violations": { "$ref": "#/$defs/violationCounts" },
        "coverage": {
          "type": "object",
          "additionalProperties": { "$ref": "#/$defs/coverage" }
        },
        "gate": { "$ref": "#/$defs/gate" },
        "properties": {
          "description": "AsyncAPI: the MQTT messages whose bindings state MQTT 5 properties, checked (MQTT 5) or not (MQTT 3.1.1); present when there were any.",
          "type": "object",
          "required": ["checked", "unchecked"],
          "properties": {
            "checked": { "type": "integer", "minimum": 0 },
            "unchecked": { "type": "integer", "minimum": 0 }
          }
        }
      }
    },
    "violations": {
      "description": "The violations seen, aggregated by location, code and message.",
      "type": "array",
      "items": { "$ref": "#/$defs/violation" }
    },
    "sections": {
      "description": "A server of several services (ADR-159): the report of each service's document, with its name and mount; summary then counts them all (coverage.combined) and its gate decides.",
      "type": "array",
      "items": {
        "type": "object",
        "allOf": [{ "$ref": "#" }],
        "properties": {
          "name": { "type": "string" },
          "mount": { "type": "string" }
        },
        "required": ["name", "mount"]
      }
    },
    "droppedViolations": {
      "description": "Violations not listed because the list reached its limit.",
      "type": "integer",
      "minimum": 0
    }
  },
  "$defs": {
    "violationCounts": {
      "type": "object",
      "properties": {
        "error": { "type": "integer", "minimum": 0 },
        "warn": { "type": "integer", "minimum": 0 },
        "info": { "type": "integer", "minimum": 0 }
      }
    },
    "coverage": {
      "type": "object",
      "required": ["covered", "total"],
      "properties": {
        "covered": { "type": "integer", "minimum": 0 },
        "total": { "type": "integer", "minimum": 0 },
        "percent": { "type": "number", "minimum": 0, "maximum": 100 }
      }
    },
    "gate": {
      "description": "The pass/fail decision of --fail-on and --min-coverage.",
      "type": "object",
      "required": ["passed", "reasons"],
      "properties": {
        "failOn": { "type": "string" },
        "minCoverage": { "type": "number" },
        "passed": { "type": "boolean" },
        "reasons": { "type": "array", "items": { "type": "string" } }
      }
    },
    "violation": {
      "type": "object",
      "required": ["check", "code", "severity", "message"],
      "properties": {
        "check": { "type": "string" },
        "code": { "type": "string" },
        "severity": { "enum": ["error", "warn", "info"] },
        "location": {
          "description": "The path of the violating value, segment by segment.",
          "type": "array",
          "items": { "type": "string" }
        },
        "message": { "type": "string" },
        "count": { "type": "integer", "minimum": 1 },
        "firstSeen": { "type": "string", "format": "date-time" }
      }
    }
  }
}
