# Spectral ruleset for IPGeolocation.io OpenAPI specifications. # Enforces conventions observed across the seven per-product specs # (IP Geolocation, IP Security, ASN, Abuse, Timezone, Astronomy, User Agent). rules: # ── INFO / METADATA ───────────────────────────────────────────────────── info-title-prefix: description: Info title must start with "IPGeolocation.io". severity: error given: $.info then: field: title function: pattern functionOptions: match: '^IPGeolocation\.io' info-version-required: description: Info must declare an API version. severity: error given: $.info then: field: version function: truthy info-description-required: description: Info must include a description of at least 40 characters. severity: warn given: $.info then: field: description function: length functionOptions: min: 40 # ── OPENAPI VERSION ──────────────────────────────────────────────────── openapi-version-3: description: Specs must declare OpenAPI 3.0.x or 3.1.x. severity: error given: $ then: field: openapi function: pattern functionOptions: match: '^3\.' # ── SERVERS ──────────────────────────────────────────────────────────── servers-required: description: A servers array must be defined. severity: error given: $ then: field: servers function: truthy server-must-be-https: description: All server URLs must use HTTPS. severity: error given: $.servers[*] then: field: url function: pattern functionOptions: match: '^https://' server-must-be-ipgeolocation-host: description: Server URL must be https://api.ipgeolocation.io. severity: warn given: $.servers[*] then: field: url function: pattern functionOptions: match: '^https://api\.ipgeolocation\.io' # ── PATHS — NAMING CONVENTIONS ───────────────────────────────────────── path-must-be-versioned: description: All operation paths must begin with /v3/. severity: error given: $.paths then: field: '@key' function: pattern functionOptions: match: '^/v3/' path-no-trailing-slash: description: Paths must not end with a trailing slash. severity: error given: $.paths then: field: '@key' function: pattern functionOptions: notMatch: '.+/$' path-kebab-or-camel: description: >- Path segments are kebab-case (preferred) or camelCase. No snake_case, no PascalCase, no spaces. severity: warn given: $.paths then: field: '@key' function: pattern functionOptions: match: '^(/[a-z][a-zA-Z0-9-]*)+$' # ── 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-operationid-required: description: Every operation must define an operationId. severity: error given: $.paths[*][get,post,put,patch,delete] then: field: operationId function: truthy operation-operationid-camelcase: description: operationId must be camelCase (e.g. lookupIpGeolocation). severity: warn given: $.paths[*][get,post,put,patch,delete] then: field: operationId function: pattern functionOptions: match: '^[a-z][a-zA-Z0-9]*$' 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: schema functionOptions: schema: type: array minItems: 1 operation-microcks-extension: description: Operations should declare x-microcks-operation for mock-server use. severity: info given: $.paths[*][get,post,put,patch,delete] then: field: x-microcks-operation function: truthy # ── TAGS ─────────────────────────────────────────────────────────────── tags-must-be-defined-globally: description: A global tags array must exist. severity: warn given: $ then: field: tags function: truthy tag-title-case: description: >- Tag names use Title Case (e.g. "IP Geolocation", "User Agent", "Astronomy"). No kebab-case, no snake_case. severity: warn given: $.tags[*] then: field: name function: pattern functionOptions: match: '^[A-Z][A-Za-z0-9]*( [A-Z][A-Za-z0-9]*)*$' 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: Each parameter must include a description. severity: warn given: $.paths[*][get,post,put,patch,delete].parameters[*] then: field: description function: truthy parameter-schema-required: description: Each parameter must declare a schema with type. severity: error given: $.paths[*][get,post,put,patch,delete].parameters[*] then: field: schema function: truthy parameter-name-camelcase: description: Parameter names use camelCase (e.g. ip, apiKey, lang). severity: warn given: $.paths[*][get,post,put,patch,delete].parameters[*] then: field: name function: pattern functionOptions: match: '^[a-z][a-zA-Z0-9]*$' # ── REQUEST BODIES ───────────────────────────────────────────────────── request-body-json-content-type: description: Request bodies must support application/json. severity: warn given: $.paths[*][post,put,patch].requestBody.content then: field: application/json function: truthy request-body-description-required: description: Each request body should have a description. severity: info given: $.paths[*][post,put,patch].requestBody then: field: description function: truthy # ── RESPONSES ────────────────────────────────────────────────────────── response-success-required: description: Every operation must define at least one 2xx response. severity: error given: $.paths[*][get,post,put,patch,delete].responses then: function: schema functionOptions: schema: type: object patternProperties: '^2[0-9][0-9]$': {} minProperties: 1 response-401-required: description: All operations must document a 401 unauthorized response. severity: warn given: $.paths[*][get,post,put,patch,delete].responses then: field: '401' function: truthy response-429-recommended: description: Operations should document a 429 throttling response. severity: info given: $.paths[*][get,post,put,patch,delete].responses then: field: '429' function: truthy response-description-required: description: Every response must include a description. severity: error given: $.paths[*][get,post,put,patch,delete].responses[*] then: field: description function: truthy response-json-content-type: description: Success responses must offer application/json. severity: warn given: $.paths[*][get,post,put,patch,delete].responses['200','201'].content then: field: application/json function: truthy # ── SCHEMAS — PROPERTY NAMING ────────────────────────────────────────── schema-property-snake-or-camel: description: >- Schema property names use snake_case (preferred) or camelCase across IPGeolocation responses. No PascalCase or kebab-case. severity: warn given: $.components.schemas[*].properties then: field: '@key' function: pattern functionOptions: match: '^[a-z][a-zA-Z0-9_]*$' schema-description-required: description: Top-level schemas should include a description. severity: warn given: $.components.schemas[*] then: field: description function: truthy # ── SECURITY ─────────────────────────────────────────────────────────── security-scheme-required: description: The spec must define at least one security scheme. severity: error given: $.components.securitySchemes then: function: truthy security-apikey-scheme: description: Security scheme must be the apiKey query parameter named apiKey. severity: warn given: $.components.securitySchemes.ApiKeyAuth then: function: schema functionOptions: schema: type: object properties: type: { const: apiKey } in: { const: query } name: { const: apiKey } required: [type, in, name] security-global-required: description: A global security requirement must be declared. severity: warn given: $ then: field: security 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 bulk-must-be-post: description: Bulk endpoints must use POST. severity: error given: $.paths[?(@property.match(/-bulk$/i))] then: field: get function: falsy # ── GENERAL QUALITY ──────────────────────────────────────────────────── no-empty-summary: description: Operation summaries must be non-empty. severity: error given: $.paths[*][get,post,put,patch,delete].summary then: function: truthy contact-recommended: description: Info should include a contact block for support routing. severity: info given: $.info then: field: contact function: truthy license-recommended: description: Info should declare a license. severity: info given: $.info then: field: license function: truthy