{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://schema.openmock.dev/openmock-0.2.0.json",
  "title": "OpenMock",
  "description": "Schema for an OpenMock document — an open format for defining API mocks in a single YAML file. A document declares a list of named, type-tagged servers, each carrying its own operations. Validates the envelope strictly while allowing arbitrary YAML bodies and messages.",
  "type": "object",
  "required": ["openmock", "servers"],
  "additionalProperties": false,
  "patternProperties": { "^x-": {} },
  "properties": {
    "openmock": {
      "description": "The OpenMock format version this document targets (SemVer).",
      "type": "string",
      "pattern": "^\\d+\\.\\d+\\.\\d+$"
    },
    "info": {
      "$ref": "#/$defs/info"
    },
    "servers": {
      "description": "The mock servers this document describes. Each server is named, carries one protocol type, and holds its own operations; a document may declare several servers of the same type.",
      "type": "array",
      "minItems": 1,
      "items": {
        "$comment": "Discriminated on type so a malformed server is validated against the one shape it means, not reported as failing all four. Server-name uniqueness is a spec-level load-time rule (§3.6) — JSON Schema cannot compare values across array items.",
        "type": "object",
        "required": ["type"],
        "properties": {
          "type": {
            "description": "The server's protocol.",
            "enum": ["http", "grpc", "graphql", "websocket"]
          }
        },
        "allOf": [
          {
            "if": { "required": ["type"], "properties": { "type": { "const": "http" } } },
            "then": { "$ref": "#/$defs/httpServer" }
          },
          {
            "if": { "required": ["type"], "properties": { "type": { "const": "grpc" } } },
            "then": { "$ref": "#/$defs/grpcServer" }
          },
          {
            "if": { "required": ["type"], "properties": { "type": { "const": "graphql" } } },
            "then": { "$ref": "#/$defs/graphqlServer" }
          },
          {
            "if": { "required": ["type"], "properties": { "type": { "const": "websocket" } } },
            "then": { "$ref": "#/$defs/websocketServer" }
          }
        ]
      }
    }
  },
  "$defs": {
    "info": {
      "type": "object",
      "description": "Human-oriented, non-normative metadata.",
      "additionalProperties": false,
      "properties": {
        "title": { "type": "string" },
        "version": { "type": "string" },
        "description": { "type": "string" }
      }
    },
    "serverName": {
      "description": "The server's name: how normalized requests address it. Unique across the servers list (a spec-level load-time rule).",
      "type": "string",
      "minLength": 1
    },
    "serverPort": {
      "description": "Optional default port hint for implementations serving this server over a network. A hint only: it never affects routing, matching, or rendering, and engines MAY override it. See docs/serving.md.",
      "type": "integer",
      "minimum": 1,
      "maximum": 65535
    },
    "httpServer": {
      "type": "object",
      "description": "An HTTP mock server: a named group of method+path operations.",
      "required": ["name", "type", "operations"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "name": { "$ref": "#/$defs/serverName" },
        "type": { "const": "http" },
        "port": { "$ref": "#/$defs/serverPort" },
        "operations": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/httpOperation" }
        }
      }
    },
    "grpcServer": {
      "type": "object",
      "description": "A gRPC mock server: a named group of service+rpc operations, optionally carrying its compiled protobuf schema.",
      "required": ["name", "type", "operations"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "name": { "$ref": "#/$defs/serverName" },
        "type": { "const": "grpc" },
        "port": { "$ref": "#/$defs/serverPort" },
        "descriptorSet": {
          "description": "Path, resolved relative to the document, to a serialized google.protobuf.FileDescriptorSet covering the services this server mocks. Implementations MAY ignore it; it never changes matching or responses.",
          "type": "string",
          "minLength": 1
        },
        "operations": {
          "type": "array",
          "minItems": 1,
          "items": {
            "$comment": "Discriminated on the call type: server-streaming operations use messages, everything else is unary and uses message.",
            "if": {
              "required": ["type"],
              "properties": { "type": { "const": "server-streaming" } }
            },
            "then": { "$ref": "#/$defs/grpcStreamOperation" },
            "else": { "$ref": "#/$defs/grpcUnaryOperation" }
          }
        }
      }
    },
    "graphqlServer": {
      "type": "object",
      "description": "A GraphQL mock server: a named group of operationType+operationName operations, optionally carrying its SDL schema.",
      "required": ["name", "type", "operations"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "name": { "$ref": "#/$defs/serverName" },
        "type": { "const": "graphql" },
        "port": { "$ref": "#/$defs/serverPort" },
        "schema": {
          "description": "Path, resolved relative to the document, to a GraphQL schema in SDL covering the operations this server mocks. Implementations MAY ignore it; it never changes matching or responses.",
          "type": "string",
          "minLength": 1
        },
        "operations": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/graphqlOperation" }
        }
      }
    },
    "websocketServer": {
      "type": "object",
      "description": "A WebSocket mock server: a named group of connection-path operations.",
      "required": ["name", "type", "operations"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "name": { "$ref": "#/$defs/serverName" },
        "type": { "const": "websocket" },
        "port": { "$ref": "#/$defs/serverPort" },
        "operations": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/websocketOperation" }
        }
      }
    },
    "httpOperation": {
      "type": "object",
      "required": ["method", "path", "scenarios"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "method": {
          "description": "HTTP method, matched case-insensitively.",
          "type": "string",
          "pattern": "^[A-Za-z]+$"
        },
        "path": {
          "description": "Absolute path template; {name} segments capture path parameters.",
          "type": "string",
          "pattern": "^/"
        },
        "summary": {
          "type": "string"
        },
        "scenarios": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/httpScenario" }
        }
      }
    },
    "grpcUnaryOperation": {
      "type": "object",
      "required": ["service", "rpc", "scenarios"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "service": {
          "description": "Fully-qualified gRPC service name, matched case-sensitively.",
          "type": "string",
          "minLength": 1
        },
        "rpc": {
          "description": "RPC method name, matched case-sensitively.",
          "type": "string",
          "minLength": 1
        },
        "type": {
          "description": "The RPC's call type. unary is the default; write it explicitly for clarity.",
          "const": "unary"
        },
        "summary": { "type": "string" },
        "scenarios": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/grpcUnaryScenario" }
        }
      }
    },
    "grpcStreamOperation": {
      "type": "object",
      "required": ["service", "rpc", "type", "scenarios"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "service": {
          "description": "Fully-qualified gRPC service name, matched case-sensitively.",
          "type": "string",
          "minLength": 1
        },
        "rpc": {
          "description": "RPC method name, matched case-sensitively.",
          "type": "string",
          "minLength": 1
        },
        "type": {
          "description": "Server-streaming call type; required, never inferred from response shape.",
          "const": "server-streaming"
        },
        "summary": { "type": "string" },
        "scenarios": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/grpcStreamScenario" }
        }
      }
    },
    "graphqlOperation": {
      "type": "object",
      "required": ["operationName", "scenarios"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "operationType": {
          "description": "The GraphQL operation type. query is the default; subscriptions are not part of v0.2.",
          "enum": ["query", "mutation"]
        },
        "operationName": {
          "description": "The GraphQL operation name this route answers, matched case-sensitively.",
          "type": "string",
          "minLength": 1
        },
        "summary": { "type": "string" },
        "scenarios": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/graphqlScenario" }
        }
      }
    },
    "websocketOperation": {
      "type": "object",
      "required": ["path", "scenarios"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "path": {
          "description": "Path template the connection is established against; {name} segments capture path parameters.",
          "type": "string",
          "pattern": "^/"
        },
        "summary": { "type": "string" },
        "scenarios": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/websocketScenario" }
        }
      }
    },
    "httpScenario": {
      "type": "object",
      "required": ["name", "response"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "name": {
          "description": "Identifier for the scenario, unique within its operation.",
          "type": "string",
          "minLength": 1
        },
        "summary": {
          "description": "Optional human-oriented description of the scenario's intent. Non-normative.",
          "type": "string"
        },
        "when": { "$ref": "#/$defs/httpWhen" },
        "response": { "$ref": "#/$defs/httpResponse" }
      }
    },
    "grpcUnaryScenario": {
      "type": "object",
      "required": ["name", "response"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "name": {
          "description": "Identifier for the scenario, unique within its operation.",
          "type": "string",
          "minLength": 1
        },
        "summary": {
          "description": "Optional human-oriented description of the scenario's intent. Non-normative.",
          "type": "string"
        },
        "when": { "$ref": "#/$defs/grpcWhen" },
        "response": { "$ref": "#/$defs/grpcUnaryResponse" }
      }
    },
    "grpcStreamScenario": {
      "type": "object",
      "required": ["name", "response"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "name": {
          "description": "Identifier for the scenario, unique within its operation.",
          "type": "string",
          "minLength": 1
        },
        "summary": {
          "description": "Optional human-oriented description of the scenario's intent. Non-normative.",
          "type": "string"
        },
        "when": { "$ref": "#/$defs/grpcWhen" },
        "response": { "$ref": "#/$defs/grpcStreamResponse" }
      }
    },
    "graphqlScenario": {
      "type": "object",
      "required": ["name", "response"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "name": {
          "description": "Identifier for the scenario, unique within its operation.",
          "type": "string",
          "minLength": 1
        },
        "summary": {
          "description": "Optional human-oriented description of the scenario's intent. Non-normative.",
          "type": "string"
        },
        "when": { "$ref": "#/$defs/graphqlWhen" },
        "response": { "$ref": "#/$defs/graphqlResponse" }
      }
    },
    "websocketScenario": {
      "type": "object",
      "required": ["name", "response"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "name": {
          "description": "Identifier for the scenario, unique within its operation.",
          "type": "string",
          "minLength": 1
        },
        "summary": {
          "description": "Optional human-oriented description of the scenario's intent. Non-normative.",
          "type": "string"
        },
        "when": { "$ref": "#/$defs/websocketWhen" },
        "response": { "$ref": "#/$defs/websocketResponse" }
      }
    },
    "httpWhen": {
      "type": "object",
      "description": "Condition under which an HTTP scenario applies. All declared facets must match. Omitting when makes the scenario the default fallback.",
      "additionalProperties": false,
      "minProperties": 1,
      "properties": {
        "params": { "$ref": "#/$defs/matcherMap" },
        "query": { "$ref": "#/$defs/queryMatcherMap" },
        "headers": { "$ref": "#/$defs/matcherMap" },
        "body": { "$ref": "#/$defs/payloadMatcher" },
        "calls": { "$ref": "#/$defs/callsMatcher" }
      }
    },
    "grpcWhen": {
      "type": "object",
      "description": "Condition under which a gRPC scenario applies. All declared facets must match. Omitting when makes the scenario the default fallback.",
      "additionalProperties": false,
      "minProperties": 1,
      "properties": {
        "metadata": { "$ref": "#/$defs/matcherMap" },
        "message": { "$ref": "#/$defs/payloadMatcher" },
        "calls": { "$ref": "#/$defs/callsMatcher" }
      }
    },
    "graphqlWhen": {
      "type": "object",
      "description": "Condition under which a GraphQL scenario applies. All declared facets must match. Omitting when makes the scenario the default fallback.",
      "additionalProperties": false,
      "minProperties": 1,
      "properties": {
        "variables": { "$ref": "#/$defs/matcherMap" },
        "headers": { "$ref": "#/$defs/matcherMap" },
        "calls": { "$ref": "#/$defs/callsMatcher" }
      }
    },
    "websocketWhen": {
      "type": "object",
      "description": "Condition under which a WebSocket scenario applies. params/query/headers match the connection-scoped values fixed at establishment; message matches the individual inbound message. All declared facets must match. Omitting when makes the scenario the default fallback.",
      "additionalProperties": false,
      "minProperties": 1,
      "properties": {
        "params": { "$ref": "#/$defs/matcherMap" },
        "query": { "$ref": "#/$defs/queryMatcherMap" },
        "headers": { "$ref": "#/$defs/matcherMap" },
        "message": { "$ref": "#/$defs/payloadMatcher" },
        "calls": { "$ref": "#/$defs/callsMatcher" }
      }
    },
    "callsMatcher": {
      "description": "Matches the operation's call counter: an exact positive integer, or an object with inclusive min/max bounds (at least one required). `n` is shorthand for {min: n, max: n}.",
      "oneOf": [
        { "type": "integer", "minimum": 1 },
        {
          "type": "object",
          "additionalProperties": false,
          "minProperties": 1,
          "properties": {
            "min": { "type": "integer", "minimum": 1 },
            "max": { "type": "integer", "minimum": 1 }
          }
        }
      ]
    },
    "httpResponse": {
      "type": "object",
      "required": ["status"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "status": {
          "description": "Response status code.",
          "type": "integer",
          "minimum": 100,
          "maximum": 599
        },
        "headers": { "$ref": "#/$defs/headerMap" },
        "delay": {
          "description": "Delay before responding, in milliseconds.",
          "type": "integer",
          "minimum": 0
        },
        "body": {
          "description": "The response body as native YAML/JSON — any object, array, or scalar. Intentionally unconstrained.",
          "$comment": "Arbitrary bodies are allowed by design; only the envelope is validated strictly."
        }
      }
    },
    "grpcUnaryResponse": {
      "type": "object",
      "required": ["status"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "status": { "$ref": "#/$defs/grpcStatus" },
        "error": {
          "description": "gRPC status message accompanying a non-OK status.",
          "type": "string"
        },
        "metadata": {
          "description": "Initial response metadata (headers), sent before the response message.",
          "$ref": "#/$defs/stringMap"
        },
        "trailers": {
          "description": "Trailing response metadata (trailers), sent with the final status.",
          "$ref": "#/$defs/stringMap"
        },
        "delay": {
          "description": "Delay before responding, in milliseconds.",
          "type": "integer",
          "minimum": 0
        },
        "message": {
          "description": "The unary response message as native YAML/JSON. Intentionally unconstrained.",
          "$comment": "Arbitrary messages are allowed by design; only the envelope is validated strictly."
        }
      },
      "if": {
        "properties": { "status": { "const": "OK" } },
        "required": ["status"]
      },
      "else": {
        "not": { "required": ["message"] },
        "$comment": "A unary call yields either a response message or an error status, never both."
      }
    },
    "grpcStreamResponse": {
      "type": "object",
      "required": ["status"],
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "properties": {
        "status": { "$ref": "#/$defs/grpcStatus" },
        "error": {
          "description": "gRPC status message accompanying a non-OK status.",
          "type": "string"
        },
        "metadata": {
          "description": "Initial response metadata (headers), sent before the first message.",
          "$ref": "#/$defs/stringMap"
        },
        "trailers": {
          "description": "Trailing response metadata (trailers), sent with the final status.",
          "$ref": "#/$defs/stringMap"
        },
        "delay": {
          "description": "Delay before the first message, in milliseconds.",
          "type": "integer",
          "minimum": 0
        },
        "messages": {
          "description": "Ordered response messages, emitted first to last. May be combined with a non-OK status (stream that fails mid-way).",
          "type": "array",
          "items": {}
        }
      }
    },
    "graphqlResponse": {
      "type": "object",
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "anyOf": [
        { "required": ["data"] },
        { "required": ["errors"] }
      ],
      "$comment": "A GraphQL response carries data, errors, or both (a partial response).",
      "properties": {
        "data": {
          "description": "The response's data payload as native YAML/JSON. Intentionally unconstrained.",
          "$comment": "Arbitrary data is allowed by design; only the envelope is validated strictly."
        },
        "errors": {
          "description": "GraphQL errors, in order. May be combined with data (a partial response).",
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/graphqlError" }
        },
        "extensions": {
          "description": "Top-level response extensions: free-form, machine-readable data the GraphQL spec permits alongside data/errors.",
          "type": "object"
        },
        "delay": {
          "description": "Delay before responding, in milliseconds.",
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "graphqlError": {
      "type": "object",
      "description": "A GraphQL error object, per the GraphQL specification: message plus optional locations, path, and extensions.",
      "required": ["message"],
      "additionalProperties": false,
      "properties": {
        "message": {
          "description": "Human-readable error description.",
          "type": "string"
        },
        "locations": {
          "type": "array",
          "items": {
            "type": "object",
            "required": ["line", "column"],
            "additionalProperties": false,
            "properties": {
              "line": { "type": "integer", "minimum": 1 },
              "column": { "type": "integer", "minimum": 1 }
            }
          }
        },
        "path": {
          "description": "Path to the response field the error concerns: field names (strings) and list indices (integers).",
          "type": "array",
          "items": {
            "oneOf": [
              { "type": "string" },
              { "type": "integer", "minimum": 0 }
            ]
          }
        },
        "extensions": {
          "description": "Free-form, machine-readable error details. Intentionally unconstrained.",
          "type": "object"
        }
      }
    },
    "websocketResponse": {
      "type": "object",
      "additionalProperties": false,
      "patternProperties": { "^x-": {} },
      "anyOf": [
        { "required": ["messages"] },
        { "required": ["close"] }
      ],
      "$comment": "A WebSocket response carries reply messages, a close, or both. An empty messages list with no close deliberately ignores the inbound message.",
      "properties": {
        "messages": {
          "description": "Ordered reply messages as native YAML/JSON, emitted first to last. MAY be empty (ignore the inbound message). Intentionally unconstrained items.",
          "type": "array",
          "items": {}
        },
        "close": {
          "description": "Close the connection after the last reply message.",
          "type": "object",
          "additionalProperties": false,
          "properties": {
            "code": {
              "description": "WebSocket close code. Default 1000 (normal closure).",
              "type": "integer",
              "minimum": 1000,
              "maximum": 4999
            },
            "reason": {
              "description": "Human-readable close reason.",
              "type": "string"
            }
          }
        },
        "delay": {
          "description": "Delay before the first reply message (or the close), in milliseconds.",
          "type": "integer",
          "minimum": 0
        }
      }
    },
    "grpcStatus": {
      "description": "A canonical gRPC status code name.",
      "enum": [
        "OK",
        "CANCELLED",
        "UNKNOWN",
        "INVALID_ARGUMENT",
        "DEADLINE_EXCEEDED",
        "NOT_FOUND",
        "ALREADY_EXISTS",
        "PERMISSION_DENIED",
        "RESOURCE_EXHAUSTED",
        "FAILED_PRECONDITION",
        "ABORTED",
        "OUT_OF_RANGE",
        "UNIMPLEMENTED",
        "INTERNAL",
        "UNAVAILABLE",
        "DATA_LOSS",
        "UNAUTHENTICATED"
      ]
    },
    "stringMap": {
      "type": "object",
      "description": "A map of string keys to string values.",
      "additionalProperties": { "type": "string" }
    },
    "existsMatcher": {
      "type": "object",
      "description": "Presence matcher: true asserts the target is present (any value), false asserts it is absent.",
      "additionalProperties": false,
      "required": ["exists"],
      "properties": {
        "exists": { "type": "boolean" }
      }
    },
    "patternMatcher": {
      "type": "object",
      "description": "Pattern matcher: an RE2 pattern matched against the target's entire canonical string form (implicitly anchored). A pattern that is not valid RE2 makes the document invalid.",
      "additionalProperties": false,
      "required": ["pattern"],
      "properties": {
        "pattern": { "type": "string" }
      }
    },
    "valueMatcher": {
      "oneOf": [
        { "$ref": "#/$defs/existsMatcher" },
        { "$ref": "#/$defs/patternMatcher" }
      ]
    },
    "matcherMap": {
      "type": "object",
      "description": "A when-facet map: each value is a string (exact match on canonical string form) or a matcher object ({exists: bool} | {pattern: RE2}). At least one key — an empty facet map is not a constraint.",
      "minProperties": 1,
      "additionalProperties": {
        "oneOf": [
          { "type": "string" },
          { "$ref": "#/$defs/valueMatcher" }
        ]
      }
    },
    "payloadMatcher": {
      "description": "body/message facet: a map of dotted paths to strings or matcher objects, or a single string matching the whole payload by its canonical string form.",
      "oneOf": [
        { "type": "string" },
        { "$ref": "#/$defs/matcherMap" }
      ]
    },
    "queryMatcherMap": {
      "type": "object",
      "description": "Query facet: each value is a non-empty list matched against the parameter's full occurrence sequence exactly — same order, same count; elements are strings or pattern matchers. Alternatively an exists matcher asserts the parameter's presence or absence. There is no string form. At least one key — an empty facet map is not a constraint.",
      "minProperties": 1,
      "additionalProperties": {
        "oneOf": [
          {
            "type": "array",
            "items": {
              "oneOf": [
                { "type": "string" },
                { "$ref": "#/$defs/patternMatcher" }
              ]
            },
            "minItems": 1
          },
          { "$ref": "#/$defs/existsMatcher" }
        ]
      }
    },
    "headerMap": {
      "type": "object",
      "description": "Response headers: each value is a string, or a non-empty list of strings emitted as one field line per item, in order (for headers that cannot be comma-joined, like Set-Cookie).",
      "additionalProperties": {
        "oneOf": [
          { "type": "string" },
          {
            "type": "array",
            "items": { "type": "string" },
            "minItems": 1
          }
        ]
      }
    }
  }
}
