# Spectral ruleset for TMDB API conventions # # Generated by api-evangelist. Enforces the patterns observed across the TMDB OpenAPI # specification (v3): path-versioned, snake_case path/query/property naming, summaries # prefixed with "TMDB", PascalCase operationIds, JSON-only responses, bearer + apiKey # auth, and Microcks-compatible mocking conventions. # # Run with: # npx @stoplight/spectral lint -r rules/tmdb-rules.yml openapi/*.yml extends: "spectral:oas" rules: # ───────────────────────────────────────────────────────────────────── # INFO / METADATA # ───────────────────────────────────────────────────────────────────── tmdb-info-title-prefix: description: "info.title should start with 'TMDB'." message: "info.title should start with 'TMDB' (got '{{value}}')." severity: warn given: "$.info.title" then: function: pattern functionOptions: match: "^TMDB" tmdb-info-description-required: description: "info.description is required and must be a substantive paragraph (>= 80 chars)." severity: error given: "$.info" then: - field: description function: truthy - field: description function: length functionOptions: min: 80 tmdb-info-version-required: description: "info.version is required." severity: error given: "$.info" then: field: version function: truthy tmdb-info-contact-required: description: "info.contact should be defined with name and url." severity: warn given: "$.info" then: - field: contact function: truthy - field: contact.url function: truthy tmdb-info-license-required: description: "info.license must be set (TMDB API Terms of Use)." severity: warn given: "$.info" then: field: license.url function: truthy # ───────────────────────────────────────────────────────────────────── # OPENAPI VERSION # ───────────────────────────────────────────────────────────────────── tmdb-openapi-version: description: "Spec must target OpenAPI 3.1.x." severity: error given: "$.openapi" then: function: pattern functionOptions: match: "^3\\.1\\." # ───────────────────────────────────────────────────────────────────── # SERVERS # ───────────────────────────────────────────────────────────────────── tmdb-servers-defined: description: "At least one server URL is required." severity: error given: "$.servers" then: function: length functionOptions: min: 1 tmdb-servers-https: description: "Server URLs must use HTTPS." severity: error given: "$.servers[*].url" then: function: pattern functionOptions: match: "^https://" tmdb-servers-host: description: "Server URL should be https://api.themoviedb.org." severity: warn given: "$.servers[*].url" then: function: pattern functionOptions: match: "^https://api\\.themoviedb\\.org" # ───────────────────────────────────────────────────────────────────── # PATHS — NAMING CONVENTIONS # ───────────────────────────────────────────────────────────────────── tmdb-paths-version-prefix: description: "Paths must start with /3/ (or /4/ for OAuth-list endpoints)." severity: error given: "$.paths.*~" then: function: pattern functionOptions: match: "^/(3|4)/" tmdb-paths-no-trailing-slash: description: "Paths must not end with a trailing slash." severity: error given: "$.paths.*~" then: function: pattern functionOptions: notMatch: ".+/$" tmdb-paths-no-query-string: description: "Paths must not contain a '?' (use parameters instead)." severity: error given: "$.paths.*~" then: function: pattern functionOptions: notMatch: "\\?" tmdb-paths-snake-case: description: "Path segments are snake_case alphanumerics (e.g. /watch/providers, /authentication/guest_session/new)." severity: warn given: "$.paths.*~" then: function: pattern functionOptions: match: "^(/(3|4))(/[a-z0-9_]+|/\\{[a-z_]+\\})*$" # ───────────────────────────────────────────────────────────────────── # OPERATIONS # ───────────────────────────────────────────────────────────────────── tmdb-operation-summary-required: description: "Every operation must have a summary." severity: error given: "$.paths.*.[get,post,put,patch,delete]" then: field: summary function: truthy tmdb-operation-summary-tmdb-prefix: description: "Operation summaries should start with 'TMDB '." message: "Operation summary should start with 'TMDB ' (got '{{value}}')." severity: warn given: "$.paths.*.[get,post,put,patch,delete].summary" then: function: pattern functionOptions: match: "^TMDB " tmdb-operation-description-required: description: "Every operation must have a description." severity: warn given: "$.paths.*.[get,post,put,patch,delete]" then: field: description function: truthy tmdb-operation-id-required: description: "Every operation must have an operationId." severity: error given: "$.paths.*.[get,post,put,patch,delete]" then: field: operationId function: truthy tmdb-operation-id-kebab: description: "operationId uses kebab-case (e.g. movie-details, account-add-favorite)." severity: warn given: "$.paths.*.[get,post,put,patch,delete].operationId" then: function: pattern functionOptions: match: "^[a-z][a-z0-9-]*$" tmdb-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: length functionOptions: min: 1 tmdb-operation-microcks: description: "Every operation should declare x-microcks-operation for mock-server compatibility." severity: warn given: "$.paths.*.[get,post,put,patch,delete]" then: field: x-microcks-operation function: truthy # ───────────────────────────────────────────────────────────────────── # TAGS # ───────────────────────────────────────────────────────────────────── tmdb-tags-defined: description: "Global tags array must be declared so each operation tag is documented." severity: warn given: "$" then: field: tags function: length functionOptions: min: 1 tmdb-tags-description: description: "Every global tag must have a description." severity: warn given: "$.tags[*]" then: field: description function: truthy tmdb-tags-title-case: description: "Tag names are Title Case (e.g. 'Movies', 'TV Series', 'Watch Providers')." severity: info given: "$.tags[*].name" then: function: pattern functionOptions: match: "^[A-Z][A-Za-z]+( [A-Z][A-Za-z]+)*$" # ───────────────────────────────────────────────────────────────────── # PARAMETERS # ───────────────────────────────────────────────────────────────────── tmdb-parameter-description: description: "Parameters require a description." severity: warn given: "$.paths.*.[get,post,put,patch,delete].parameters[*]" then: field: description function: truthy tmdb-parameter-snake-case: description: "Parameter names use snake_case (e.g. movie_id, language, api_key, append_to_response)." severity: warn given: "$.paths.*.[get,post,put,patch,delete].parameters[*].name" then: function: pattern functionOptions: match: "^[a-z][a-z0-9_]*$" tmdb-parameter-schema-type: description: "Parameter schemas must specify a type." severity: error given: "$.paths.*.[get,post,put,patch,delete].parameters[*]" then: field: schema.type function: truthy tmdb-parameter-pagination: description: "Pagination parameter is named 'page' (TMDB uses page-based pagination)." severity: info given: "$.paths.*.[get,post,put,patch,delete].parameters[?(@.in == 'query' && (@.name == 'offset' || @.name == 'limit' || @.name == 'cursor'))]" then: function: falsy # ───────────────────────────────────────────────────────────────────── # RESPONSES # ───────────────────────────────────────────────────────────────────── tmdb-response-success: description: "Every operation must define a 2xx success response." severity: error given: "$.paths.*.[get,post,put,patch,delete].responses" then: function: pattern functionOptions: match: "2\\d\\d" tmdb-response-401: description: "Operations should document a 401 response for auth failures." severity: warn given: "$.paths.*.[get,post,put,patch,delete].responses" then: field: "401" function: truthy tmdb-response-429: description: "Operations should document 429 rate-limit responses." severity: warn given: "$.paths.*.[get,post,put,patch,delete].responses" then: field: "429" function: truthy tmdb-response-json-content: description: "Response content-type must be application/json." severity: error given: "$.paths.*.[get,post,put,patch,delete].responses.*.content" then: field: "application/json" function: truthy tmdb-response-description: description: "Every response must have a description." severity: error given: "$.paths.*.[get,post,put,patch,delete].responses.*" then: field: description function: truthy # ───────────────────────────────────────────────────────────────────── # SCHEMAS — PROPERTY NAMING # ───────────────────────────────────────────────────────────────────── tmdb-schema-property-snake-case: description: "Schema property names use snake_case (e.g. release_date, poster_path, vote_average)." severity: warn given: "$.components.schemas.*.properties.*~" then: function: pattern functionOptions: match: "^[a-z][a-z0-9_]*$" tmdb-schema-title: description: "Top-level schemas should declare a title." severity: info given: "$.components.schemas.*" then: field: title function: truthy tmdb-schema-type: description: "Schemas must declare a type." severity: warn given: "$.components.schemas.*" then: field: type function: truthy # ───────────────────────────────────────────────────────────────────── # SECURITY # ───────────────────────────────────────────────────────────────────── tmdb-security-global: description: "A global security requirement should be set (bearerAuth or apiKeyAuth)." severity: warn given: "$.security" then: function: length functionOptions: min: 1 tmdb-security-scheme-bearer: description: "components.securitySchemes.bearerAuth must be declared for v4 token auth." severity: warn given: "$.components.securitySchemes" then: field: bearerAuth function: truthy tmdb-security-scheme-apikey: description: "components.securitySchemes.apiKeyAuth must be declared for v3 api_key auth." severity: warn given: "$.components.securitySchemes" then: field: apiKeyAuth function: truthy # ───────────────────────────────────────────────────────────────────── # HTTP METHOD CONVENTIONS # ───────────────────────────────────────────────────────────────────── tmdb-get-no-request-body: description: "GET operations must not declare a requestBody." severity: error given: "$.paths.*.get" then: field: requestBody function: falsy tmdb-delete-no-request-body: description: "DELETE operations should not declare a requestBody." severity: warn given: "$.paths.*.delete" then: field: requestBody function: falsy # ───────────────────────────────────────────────────────────────────── # GENERAL QUALITY # ───────────────────────────────────────────────────────────────────── tmdb-no-empty-description: description: "Descriptions must not be empty strings." severity: warn given: "$..description" then: function: pattern functionOptions: notMatch: "^$" tmdb-examples-encouraged: description: "JSON response media types should include named examples for mock-server compatibility." severity: info given: "$.paths.*.[get,post,put,patch,delete].responses.*.content.application/json" then: field: examples function: truthy