rules: # ── INFO / METADATA ──────────────────────────────────────────────────── info-title-required: description: Info object must define a title. severity: error given: $.info then: field: title function: truthy info-title-prefix-ipify: description: Spec title should start with `ipify` for consistent provider branding. severity: warn given: $.info.title then: function: pattern functionOptions: match: '^ipify' info-description-required: description: Info object must include a description of the API. severity: error given: $.info then: field: description function: truthy info-contact-required: description: Info object should include a contact for the provider. severity: warn given: $.info then: field: contact function: truthy info-license-required: description: Info object should declare a license (MIT for the free API, Commercial for Geolocation). severity: warn given: $.info then: field: license function: truthy # ── OPENAPI VERSION ──────────────────────────────────────────────────── openapi-version-3: description: All ipify specs must declare OpenAPI 3.x. severity: error given: $.openapi then: function: pattern functionOptions: match: '^3\.' # ── SERVERS ──────────────────────────────────────────────────────────── servers-required: description: A `servers` array must be defined at the document root. severity: error given: $ then: field: servers function: truthy servers-must-be-https: description: ipify endpoints are HTTPS-only. No `http://` server URLs allowed. severity: error given: $.servers[*].url then: function: pattern functionOptions: notMatch: '^http://' servers-description-required: description: Each server entry must explain which surface it points to (IPv4, IPv6, dual-stack, account utility, etc.). severity: warn given: $.servers[*] then: field: description function: truthy # ── PATHS — NAMING CONVENTIONS ───────────────────────────────────────── paths-no-trailing-slash: description: Paths must not end with a trailing slash (except the root `/`). severity: error given: $.paths then: function: pattern functionOptions: match: '^(\/|[^\/]+(\/[^\/]+)*)$' paths-no-query-strings: description: Paths must not contain query strings — express them as parameters. severity: error given: $.paths then: function: pattern functionOptions: notMatch: '\?' paths-lowercase-segments: description: Path segments must be lowercase kebab-case (matches ipify's `/country`, `/country,city`, `/service/account-balance`). severity: warn given: $.paths then: function: pattern functionOptions: match: '^/[a-z0-9,_\-\/{}]*$' # ── OPERATIONS ───────────────────────────────────────────────────────── 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 (e.g. `getPublicIp`, `getCountryLocation`, `getAccountBalance`). severity: warn given: $.paths[*][get,post,put,patch,delete].operationId then: function: pattern functionOptions: match: '^[a-z][a-zA-Z0-9]*$' operation-summary-required: description: Every operation must declare a `summary`. severity: error given: $.paths[*][get,post,put,patch,delete] then: field: summary function: truthy operation-summary-prefix-ipify: description: Operation summary must start with `ipify ` for consistent provider branding. severity: warn given: $.paths[*][get,post,put,patch,delete].summary then: function: pattern functionOptions: match: '^ipify ' operation-description-required: description: Every operation must declare a `description`. severity: warn given: $.paths[*][get,post,put,patch,delete] then: field: description function: truthy 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-required: description: Every operation must declare an `x-microcks-operation` extension for mock-server compatibility. severity: warn given: $.paths[*][get,post,put,patch,delete] then: field: x-microcks-operation function: truthy # ── TAGS ─────────────────────────────────────────────────────────────── tags-root-defined: description: A root `tags` array must enumerate every tag with a description. severity: warn given: $ then: field: tags function: truthy tags-title-case: description: Tag names must be Title Case (e.g. `IP Address`, `Geolocation`, `Account`). severity: warn given: $.tags[*].name then: function: pattern functionOptions: match: '^([A-Z][A-Za-z]*)( [A-Z][A-Za-z]*)*$' tag-description-required: description: Every declared tag must include a description. severity: warn given: $.tags[*] then: field: description function: truthy # ── PARAMETERS ───────────────────────────────────────────────────────── parameter-description-required: description: Every parameter must include a description. severity: warn given: '#Parameter' then: field: description function: truthy parameter-camelcase: description: Query/path parameter names must be camelCase (matches ipify's `apiKey`, `ipAddress`, `reverseIp`, `escapedUnicode`). severity: warn given: $.paths[*][*].parameters[?(@.in == 'query' || @.in == 'path')].name then: function: pattern functionOptions: match: '^[a-z][a-zA-Z0-9]*$' parameter-schema-required: description: Every parameter must declare a `schema` with a `type`. severity: error given: $.paths[*][*].parameters[*] then: field: schema function: truthy # ── RESPONSES ────────────────────────────────────────────────────────── response-2xx-required: description: Every operation must declare at least one 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 include a description. severity: warn given: $.paths[*][*].responses[*] then: field: description function: truthy response-error-codes-geolocation: description: Geolocation operations should document 400, 401, 403, 422, and 429 error responses. severity: info given: $.paths[/country,/country\,city,/country\,city\,vpn][get].responses then: function: schema functionOptions: schema: type: object required: ['400', '401', '403', '422', '429'] # ── SCHEMAS — PROPERTY NAMING ────────────────────────────────────────── schema-camelcase-properties: description: Schema property names must be camelCase (matches the Geolocation API's `postalCode`, `geonameId`, `escapedUnicode`). severity: warn given: $.components.schemas[*].properties then: function: pattern functionOptions: match: '^[a-z][a-zA-Z0-9]*$' schema-description-required: description: Every top-level component schema must include a description. severity: warn given: $.components.schemas[*] then: field: description function: truthy schema-type-required: description: Every top-level component schema must declare a `type`. severity: error given: $.components.schemas[*] then: field: type function: truthy # ── SECURITY ─────────────────────────────────────────────────────────── security-scheme-apikey-query: description: The Geolocation API authenticates with an `apiKey` query parameter (no other schemes are supported). severity: info given: $.components.securitySchemes[*] then: function: schema functionOptions: schema: type: object properties: type: { const: apiKey } in: { const: query } name: { const: apiKey } security-public-api-no-auth: description: The free public IP API (`api.ipify.org`) must not declare a security scheme — it is intentionally unauthenticated. severity: info given: $.paths[/].get then: field: security function: falsy # ── HTTP METHOD CONVENTIONS ──────────────────────────────────────────── get-no-request-body: description: GET operations must not declare a request body — both ipify APIs are GET-only. severity: error given: $.paths[*].get then: field: requestBody function: falsy ipify-get-only-api: description: ipify exposes only GET operations. Reject any POST/PUT/PATCH/DELETE. severity: error given: $.paths[*] then: function: schema functionOptions: schema: type: object not: anyOf: - required: [post] - required: [put] - required: [patch] - required: [delete] # ── GENERAL QUALITY ──────────────────────────────────────────────────── deprecation-documented: description: "Deprecated operations and schemas must be marked with `deprecated: true` (e.g. the VPN/proxy block)." severity: info given: $..deprecated then: function: truthy example-encouraged: description: Schema properties should include `example` values to support mock-server scenarios. severity: info given: $.components.schemas[*].properties[*] then: field: example function: truthy