extends: spectral:oas rules: # ── INFO / METADATA ────────────────────────────────────────────── info-title-jsonplaceholder-prefix: description: Spec title should begin with "JSONPlaceholder". severity: warn given: $.info then: field: title function: pattern functionOptions: match: '^JSONPlaceholder' info-description-required: description: Spec must include a non-empty description. severity: error given: $.info then: field: description function: truthy info-version-required: description: Spec must declare a version. severity: error given: $.info then: field: version function: truthy info-license-required: description: Spec must include license info (JSONPlaceholder is MIT). severity: warn given: $.info then: field: license function: truthy # ── OPENAPI VERSION ────────────────────────────────────────────── openapi-version-3-0-3: description: Specs must target OpenAPI 3.0.3. severity: warn given: $.openapi then: function: pattern functionOptions: match: '^3\.0\.' # ── SERVERS ────────────────────────────────────────────────────── servers-https-required: description: All server URLs must use HTTPS. severity: error given: $.servers[*].url then: function: pattern functionOptions: match: '^https://' servers-typicode-host: description: Production server URL must point at jsonplaceholder.typicode.com. severity: warn given: $.servers[*].url then: function: pattern functionOptions: match: 'jsonplaceholder\.typicode\.com' # ── PATHS — NAMING CONVENTIONS ─────────────────────────────────── paths-lowercase: description: Paths must be lowercase (collection nouns are plural lowercase). severity: error given: $.paths.*~ then: function: pattern functionOptions: match: '^/[a-z0-9/{}_-]*$' paths-no-trailing-slash: description: Paths must not end with a trailing slash. severity: error given: $.paths.*~ then: function: pattern functionOptions: notMatch: '.+/$' paths-plural-resources: description: Top-level resources use plural nouns (posts, comments, albums, photos, todos, users). severity: warn given: $.paths.*~ then: function: pattern functionOptions: match: '^/(posts|comments|albums|photos|todos|users)(/.*)?$' # ── OPERATIONS ─────────────────────────────────────────────────── operation-summary-required: description: Every operation must have a summary. severity: error given: $.paths.*[get,post,put,patch,delete] then: field: summary function: truthy operation-description-required: description: Every operation must have a description. severity: warn given: $.paths.*[get,post,put,patch,delete] then: field: description function: truthy operation-summary-jsonplaceholder-prefix: description: Every operation summary must start with "JSONPlaceholder". severity: warn given: $.paths.*[get,post,put,patch,delete].summary then: function: pattern functionOptions: match: '^JSONPlaceholder' operation-operationid-required: description: Every operation must declare an operationId. severity: error given: $.paths.*[get,post,put,patch,delete] then: field: operationId function: truthy operation-operationid-camelcase: description: 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-operationid-verb-prefix: description: operationId should begin with a known verb (list/get/create/replace/update/delete). severity: info given: $.paths.*[get,post,put,patch,delete].operationId then: function: pattern functionOptions: match: '^(list|get|create|replace|update|delete)[A-Z]' operation-tags-required: description: Every operation must declare at least one tag. severity: error given: $.paths.*[get,post,put,patch,delete] then: field: tags function: truthy operation-microcks-extension: description: Every operation should declare x-microcks-operation for mock-server compatibility. severity: info given: $.paths.*[get,post,put,patch,delete] then: field: x-microcks-operation function: truthy # ── TAGS ───────────────────────────────────────────────────────── tag-title-case: description: Global tag names must use Title Case (Posts, Comments, Albums, Photos, Todos, Users). severity: warn given: $.tags[*].name then: function: pattern functionOptions: match: '^[A-Z][a-z]+$' tag-description-required: description: Each global tag must include a description. severity: warn given: $.tags[*] then: field: description function: truthy # ── PARAMETERS ─────────────────────────────────────────────────── parameter-description-required: description: Every parameter must have a description. severity: warn given: $.paths.*[get,post,put,patch,delete].parameters[*] then: field: description function: truthy parameter-camelcase: description: Parameter names must be camelCase (e.g. userId, postId, albumId). severity: warn given: $.paths.*[get,post,put,patch,delete].parameters[*].name then: function: pattern functionOptions: match: '^[a-z][a-zA-Z0-9]*$' parameter-id-suffix: description: Identifier parameters should follow the {resource}Id pattern. severity: info given: $.paths.*[get,post,put,patch,delete].parameters[?(@.in=='query')].name then: function: pattern functionOptions: match: '^(id|userId|postId|albumId|completed)$' # ── REQUEST BODIES ─────────────────────────────────────────────── request-body-json-content: description: Request bodies must accept application/json. severity: error given: $.paths.*[post,put,patch].requestBody.content then: field: application/json function: truthy # ── RESPONSES ──────────────────────────────────────────────────── response-success-required: description: Every operation must define a 2xx success response. severity: error given: $.paths.*[get,post,put,patch,delete].responses then: function: schema functionOptions: schema: type: object patternProperties: '^2\d\d$': {} minProperties: 1 response-description-required: description: Every response must have a description. severity: warn given: $.paths.*[get,post,put,patch,delete].responses.* then: field: description function: truthy response-json-content: description: Success responses must return application/json. severity: warn given: $.paths.*[get,post,put,patch,delete].responses[?(@property.match(/^2\d\d$/))].content then: field: application/json function: truthy # ── SCHEMAS — PROPERTY NAMING ──────────────────────────────────── schema-property-camelcase: description: Schema property names must be camelCase (matches the JSONPlaceholder JSON shape). severity: warn given: $.components.schemas.*.properties.*~ then: function: pattern functionOptions: match: '^[a-z][a-zA-Z0-9]*$' schema-description-required: description: Top-level schemas must include a description. severity: warn given: $.components.schemas.* then: field: description function: truthy schema-type-required: description: Top-level schemas must declare a type. severity: error given: $.components.schemas.* then: field: type function: truthy # ── HTTP METHOD CONVENTIONS ────────────────────────────────────── get-no-request-body: description: GET operations must not declare a request body. severity: error given: $.paths.*.get then: field: requestBody function: falsy delete-no-request-body: description: DELETE operations must not declare a request body. severity: error given: $.paths.*.delete then: field: requestBody function: falsy post-put-patch-require-body: description: POST, PUT, and PATCH operations should declare a request body. severity: warn given: $.paths.*[post,put,patch] then: field: requestBody function: truthy # ── GENERAL QUALITY ────────────────────────────────────────────── no-empty-descriptions: description: Descriptions must be non-empty when present. severity: warn given: '$..description' then: function: truthy