├── .cra └── .cveignore ├── .dockerignore ├── .eslintrc.js ├── .github ├── pull_request_template.md └── workflows │ ├── build.yaml │ └── docker.yaml ├── .gitignore ├── .releaserc ├── .secrets.baseline ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── Migration-Guide.md ├── README.md ├── appscan-config.xml ├── docs ├── automated-quality-screening.md ├── ibm-cloud-rules.md └── openapi-ruleset-utilities.md ├── package-lock.json ├── package.json ├── packages ├── ruleset │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── functions │ │ │ ├── accept-and-return-models.js │ │ │ ├── allowed-keywords.js │ │ │ ├── anchored-patterns.js │ │ │ ├── api-symmetry.js │ │ │ ├── array-attributes.js │ │ │ ├── array-of-arrays.js │ │ │ ├── array-responses.js │ │ │ ├── avoid-multiple-types.js │ │ │ ├── binary-schemas.js │ │ │ ├── circular-refs.js │ │ │ ├── collection-array-property.js │ │ │ ├── consecutive-path-segments.js │ │ │ ├── delete-body.js │ │ │ ├── disallowed-header-parameter.js │ │ │ ├── discriminator-property-exists.js │ │ │ ├── duplicate-path-parameter.js │ │ │ ├── enum-casing-convention.js │ │ │ ├── error-response-schemas.js │ │ │ ├── etag-header-exists.js │ │ │ ├── index.js │ │ │ ├── inline-schemas.js │ │ │ ├── integer-attributes.js │ │ │ ├── major-version-in-path.js │ │ │ ├── merge-patch-properties.js │ │ │ ├── no-ambiguous-paths.js │ │ │ ├── no-nullable-properties.js │ │ │ ├── no-operation-requestbody.js │ │ │ ├── no-ref-in-example.js │ │ │ ├── no-superfluous-allof.js │ │ │ ├── no-unsupported-keywords.js │ │ │ ├── operation-summary-exists.js │ │ │ ├── operation-summary-length.js │ │ │ ├── operationid-casing-convention.js │ │ │ ├── operationid-naming-convention.js │ │ │ ├── optional-request-body.js │ │ │ ├── pagination-style.js │ │ │ ├── parameter-casing-convention.js │ │ │ ├── parameter-default.js │ │ │ ├── parameter-description-exists.js │ │ │ ├── parameter-order.js │ │ │ ├── patch-request-content-type.js │ │ │ ├── path-parameter-not-crn.js │ │ │ ├── path-segment-casing-convention.js │ │ │ ├── pattern-properties.js │ │ │ ├── precondition-header.js │ │ │ ├── prefer-token-pagination.js │ │ │ ├── property-attributes.js │ │ │ ├── property-casing-convention.js │ │ │ ├── property-consistent-name-and-type.js │ │ │ ├── property-description-exists.js │ │ │ ├── property-name-collision.js │ │ │ ├── ref-pattern.js │ │ │ ├── ref-sibling-duplicate-description.js │ │ │ ├── request-and-response-content.js │ │ │ ├── requestbody-name.js │ │ │ ├── required-array-properties-in-response.js │ │ │ ├── required-enum-properties-in-response.js │ │ │ ├── required-property.js │ │ │ ├── resource-response-consistency.js │ │ │ ├── response-example-exists.js │ │ │ ├── response-status-codes.js │ │ │ ├── schema-casing-convention.js │ │ │ ├── schema-description-exists.js │ │ │ ├── schema-naming-convention.js │ │ │ ├── schema-or-content-provided.js │ │ │ ├── schema-type-exists.js │ │ │ ├── schema-type-format.js │ │ │ ├── securityscheme-attributes.js │ │ │ ├── securityschemes.js │ │ │ ├── string-attributes.js │ │ │ ├── unevaluated-properties.js │ │ │ ├── unique-parameter-request-property-names.js │ │ │ ├── unused-tags.js │ │ │ ├── use-date-based-format.js │ │ │ ├── valid-path-segments.js │ │ │ ├── valid-schema-example.js │ │ │ └── well-defined-dictionaries.js │ │ ├── ibm-oas.js │ │ ├── rules │ │ │ ├── accept-and-return-models.js │ │ │ ├── accept-header.js │ │ │ ├── anchored-patterns.js │ │ │ ├── api-symmetry.js │ │ │ ├── array-attributes.js │ │ │ ├── array-of-arrays.js │ │ │ ├── array-responses.js │ │ │ ├── authorization-header.js │ │ │ ├── avoid-multiple-types.js │ │ │ ├── binary-schemas.js │ │ │ ├── circular-refs.js │ │ │ ├── collection-array-property.js │ │ │ ├── consecutive-path-segments.js │ │ │ ├── content-contains-schema.js │ │ │ ├── content-type-header.js │ │ │ ├── content-type-is-specific.js │ │ │ ├── delete-body.js │ │ │ ├── discriminator-property-exists.js │ │ │ ├── duplicate-path-parameter.js │ │ │ ├── enum-casing-convention.js │ │ │ ├── error-content-type-is-json.js │ │ │ ├── error-response-schemas.js │ │ │ ├── etag-header-exists.js │ │ │ ├── examples-name-contains-space.js │ │ │ ├── ibm-sdk-operations.js │ │ │ ├── if-modified-since-header.js │ │ │ ├── if-unmodified-since-header.js │ │ │ ├── index.js │ │ │ ├── inline-schemas.js │ │ │ ├── integer-attributes.js │ │ │ ├── major-version-in-path.js │ │ │ ├── merge-patch-properties.js │ │ │ ├── no-ambiguous-paths.js │ │ │ ├── no-nullable-properties.js │ │ │ ├── no-operation-requestbody.js │ │ │ ├── no-ref-in-example.js │ │ │ ├── no-superfluous-allof.js │ │ │ ├── no-unsupported-keywords.js │ │ │ ├── operation-responses.js │ │ │ ├── operation-summary-exists.js │ │ │ ├── operation-summary-length.js │ │ │ ├── operationid-casing-convention.js │ │ │ ├── operationid-naming-convention.js │ │ │ ├── optional-request-body-deprecated.js │ │ │ ├── optional-request-body.js │ │ │ ├── pagination-style.js │ │ │ ├── parameter-casing-convention.js │ │ │ ├── parameter-default.js │ │ │ ├── parameter-description-exists.js │ │ │ ├── parameter-order.js │ │ │ ├── parameter-schema-or-content-exists.js │ │ │ ├── patch-request-content-type.js │ │ │ ├── path-parameter-not-crn.js │ │ │ ├── path-segment-casing-convention.js │ │ │ ├── pattern-properties.js │ │ │ ├── precondition-header.js │ │ │ ├── prefer-token-pagination.js │ │ │ ├── property-attributes.js │ │ │ ├── property-casing-convention.js │ │ │ ├── property-consistent-name-and-type.js │ │ │ ├── property-description-exists.js │ │ │ ├── property-name-collision.js │ │ │ ├── ref-pattern.js │ │ │ ├── ref-sibling-duplicate-description.js │ │ │ ├── request-and-response-content.js │ │ │ ├── requestbody-is-object.js │ │ │ ├── requestbody-name.js │ │ │ ├── required-array-properties-in-response.js │ │ │ ├── required-enum-properties-in-response.js │ │ │ ├── required-property-missing.js │ │ │ ├── resource-response-consistency.js │ │ │ ├── response-example-exists.js │ │ │ ├── response-status-codes.js │ │ │ ├── schema-casing-convention.js │ │ │ ├── schema-description-exists.js │ │ │ ├── schema-keywords.js │ │ │ ├── schema-naming-convention.js │ │ │ ├── schema-type-exists.js │ │ │ ├── schema-type-format.js │ │ │ ├── securityscheme-attributes.js │ │ │ ├── securityschemes.js │ │ │ ├── server-variable-default-value.js │ │ │ ├── string-attributes.js │ │ │ ├── summary-sentence-style.js │ │ │ ├── typed-enum.js │ │ │ ├── unevaluated-properties.js │ │ │ ├── unique-parameter-request-property-names.js │ │ │ ├── unused-tags.js │ │ │ ├── use-date-based-format.js │ │ │ ├── valid-path-segments.js │ │ │ ├── valid-schema-example.js │ │ │ └── well-defined-dictionaries.js │ │ ├── schemas │ │ │ └── x-sdk-operations.json │ │ └── utils │ │ │ ├── compute-refs-at-paths.js │ │ │ ├── constants.js │ │ │ ├── date-based-utils.js │ │ │ ├── get-composite-schema-attribute.js │ │ │ ├── get-resource-oriented-paths.js │ │ │ ├── get-resource-specific-sibling-path.js │ │ │ ├── get-response-codes.js │ │ │ ├── get-schema-name-at-path.js │ │ │ ├── index.js │ │ │ ├── is-create-operation.js │ │ │ ├── is-deprecated.js │ │ │ ├── is-empty-object-schema.js │ │ │ ├── is-operation-of-type.js │ │ │ ├── is-ref-sibling-schema.js │ │ │ ├── is-requestbody-exploded.js │ │ │ ├── logger-factory.js │ │ │ ├── merge-allof-schema-properties.js │ │ │ ├── mimetype-utils.js │ │ │ ├── pagination-utils.js │ │ │ ├── path-has-minimally-represented-resource.js │ │ │ ├── path-location-utils.js │ │ │ ├── path-matches-regexp.js │ │ │ └── schema-finding-utils.js │ └── test │ │ ├── meta │ │ ├── rule-style.test.js │ │ └── spectral-depedencies.test.js │ │ ├── rules │ │ ├── accept-and-return-models.test.js │ │ ├── accept-header.test.js │ │ ├── anchored-patterns.test.js │ │ ├── api-symmetry.test.js │ │ ├── array-attributes.test.js │ │ ├── array-of-arrays.test.js │ │ ├── array-responses.test.js │ │ ├── authorization-header.test.js │ │ ├── avoid-multiple-types.test.js │ │ ├── binary-schemas.test.js │ │ ├── circular-refs.test.js │ │ ├── collection-array-property.test.js │ │ ├── consecutive-path-segments.test.js │ │ ├── content-contains-schema.test.js │ │ ├── content-type-header.test.js │ │ ├── content-type-is-specific.test.js │ │ ├── delete-body.test.js │ │ ├── discriminator-property.test.js │ │ ├── duplicate-path-parameter.test.js │ │ ├── enum-casing-convention.test.js │ │ ├── error-content-type-is-json.test.js │ │ ├── error-response-schemas.test.js │ │ ├── etag-header-exists.test.js │ │ ├── examples-name-contains-space.test.js │ │ ├── if-modified-since-header.test.js │ │ ├── if-unmodified-since-header.test.js │ │ ├── inline-schemas.test.js │ │ ├── integer-attributes.test.js │ │ ├── major-version-in-path.test.js │ │ ├── merge-patch-properties.test.js │ │ ├── no-ambiguous-paths.test.js │ │ ├── no-nullable-properties.test.js │ │ ├── no-operation-requestbody.test.js │ │ ├── no-ref-in-example.test.js │ │ ├── no-superfluous-allof.test.js │ │ ├── no-unsupported-keywords.test.js │ │ ├── operation-responses.test.js │ │ ├── operation-summary-exists.test.js │ │ ├── operation-summary-length.test.js │ │ ├── operationid-casing-convention.test.js │ │ ├── operationid-naming-convention.test.js │ │ ├── optional-request-body.test.js │ │ ├── pagination-style.test.js │ │ ├── parameter-casing-convention.test.js │ │ ├── parameter-default.test.js │ │ ├── parameter-description.test.js │ │ ├── parameter-order.test.js │ │ ├── parameter-schema-or-content.test.js │ │ ├── patch-request-content-type.test.js │ │ ├── path-parameter-not-crn.test.js │ │ ├── path-segment-casing-convention.test.js │ │ ├── pattern-properties.test.js │ │ ├── precondition-headers.test.js │ │ ├── prefer-token-pagination.test.js │ │ ├── property-attributes.test.js │ │ ├── property-casing-convention.test.js │ │ ├── property-consistent-name-and-type.test.js │ │ ├── property-description.test.js │ │ ├── property-name-collision.test.js │ │ ├── ref-pattern.test.js │ │ ├── ref-sibling-duplicate-description.test.js │ │ ├── request-and-response-content.test.js │ │ ├── requestbody-is-object.test.js │ │ ├── requestbody-name.test.js │ │ ├── required-array-properties-in-response.test.js │ │ ├── required-enum-properties-in-response.test.js │ │ ├── required-property-missing.test.js │ │ ├── resource-response-consistency.test.js │ │ ├── response-example.test.js │ │ ├── response-status-codes.test.js │ │ ├── schema-casing-convention.test.js │ │ ├── schema-description.test.js │ │ ├── schema-keywords.test.js │ │ ├── schema-naming-convention.test.js │ │ ├── schema-type-format.test.js │ │ ├── schema-type.test.js │ │ ├── securityscheme-attributes.test.js │ │ ├── securityschemes.test.js │ │ ├── server-variable-default-value.test.js │ │ ├── string-attributes.test.js │ │ ├── summary-sentence-style.test.js │ │ ├── typed-enum.test.js │ │ ├── unevaluated-properties.test.js │ │ ├── unique-parameter-request-property-names.test.js │ │ ├── unused-tags.test.js │ │ ├── use-date-based-format.test.js │ │ ├── valid-path-segments.test.js │ │ ├── valid-schema-example.test.js │ │ └── well-defined-dictionaries.test.js │ │ ├── test-utils │ │ ├── all-schemas-document.js │ │ ├── helper-artifacts.js │ │ ├── index.js │ │ ├── make-copy.js │ │ ├── root-document.js │ │ ├── severity-codes.js │ │ └── test-rule.js │ │ └── utils │ │ ├── compute-refs-at-paths.test.js │ │ ├── constants.test.js │ │ ├── date-based-utils.test.js │ │ ├── get-composite-schema-attribute.test.js │ │ ├── get-resource-oriented-paths.test.js │ │ ├── get-resource-specific-sibling-path.test.js │ │ ├── get-response-codes.test.js │ │ ├── get-schema-name-at-path.test.js │ │ ├── is-create-operation.test.js │ │ ├── is-deprecated.test.js │ │ ├── is-empty-object-schema.test.js │ │ ├── is-operation-of-type.test.js │ │ ├── is-ref-sibling-schema.test.js │ │ ├── is-requestbody-exploded.test.js │ │ ├── logger-factory.test.js │ │ ├── merge-allof-schema-properties.test.js │ │ ├── mimetype-utils.test.js │ │ ├── pagination-utils.test.js │ │ ├── path-has-minimally-represented-resource.test.js │ │ ├── path-location-utils.test.js │ │ ├── path-matches-regexp.test.js │ │ └── schema-finding-utils.test.js ├── utilities │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ │ ├── collections │ │ │ └── index.js │ │ ├── index.js │ │ └── utils │ │ │ ├── collect-from-composed-schemas.js │ │ │ ├── get-examples-for-schema.js │ │ │ ├── get-property-names-for-schema.js │ │ │ ├── get-schema-type.js │ │ │ ├── index.js │ │ │ ├── is-object.js │ │ │ ├── schema-has-constraint.js │ │ │ ├── schema-has-property.js │ │ │ ├── schema-loosely-has-constraint.js │ │ │ ├── schema-path.js │ │ │ ├── schema-requires-property.js │ │ │ ├── spectral-context-utils.js │ │ │ ├── validate-composed-schemas.js │ │ │ ├── validate-nested-schemas.js │ │ │ └── validate-subschemas.js │ ├── test │ │ ├── collect-from-composed-schemas.test.js │ │ ├── collections.test.js │ │ ├── get-examples-for-schema.test.js │ │ ├── get-property-names-for-schema.test.js │ │ ├── get-schema-type.test.js │ │ ├── is-array-schema.test.js │ │ ├── is-binary-schema.test.js │ │ ├── is-boolean-schema.test.js │ │ ├── is-byte-schema.test.js │ │ ├── is-date-schema.test.js │ │ ├── is-date-time-schema.test.js │ │ ├── is-double-schema.test.js │ │ ├── is-enumeration-schema.test.js │ │ ├── is-float-schema.test.js │ │ ├── is-int32-schema.test.js │ │ ├── is-int64-schema.test.js │ │ ├── is-integer-schema.test.js │ │ ├── is-number-schema.test.js │ │ ├── is-object-schema.test.js │ │ ├── is-object.test.js │ │ ├── is-primitive-schema.test.js │ │ ├── is-string-schema.test.js │ │ ├── schema-has-constraint.test.js │ │ ├── schema-has-property.test.js │ │ ├── schema-is-of-type.test.js │ │ ├── schema-loosely-has-constraint.test.js │ │ ├── schema-path.test.js │ │ ├── schema-requires-property.test.js │ │ ├── spectral-context-utils.test.js │ │ ├── utils │ │ │ ├── all-schemas-document.js │ │ │ ├── index.js │ │ │ ├── test-rule-paths.js │ │ │ └── test-rule.js │ │ ├── validate-composed-schemas.test.js │ │ ├── validate-nested-schemas.test.js │ │ └── validate-subschemas.test.js │ ├── tsconfig.json │ └── types │ │ ├── collections │ │ ├── index.d.ts │ │ └── index.d.ts.map │ │ ├── index.d.ts │ │ ├── index.d.ts.map │ │ └── utils │ │ ├── collect-from-composed-schemas.d.ts │ │ ├── collect-from-composed-schemas.d.ts.map │ │ ├── get-examples-for-schema.d.ts │ │ ├── get-examples-for-schema.d.ts.map │ │ ├── get-property-names-for-schema.d.ts │ │ ├── get-property-names-for-schema.d.ts.map │ │ ├── get-schema-type.d.ts │ │ ├── get-schema-type.d.ts.map │ │ ├── index.d.ts │ │ ├── index.d.ts.map │ │ ├── is-object.d.ts │ │ ├── is-object.d.ts.map │ │ ├── schema-has-constraint.d.ts │ │ ├── schema-has-constraint.d.ts.map │ │ ├── schema-has-property.d.ts │ │ ├── schema-has-property.d.ts.map │ │ ├── schema-loosely-has-constraint.d.ts │ │ ├── schema-loosely-has-constraint.d.ts.map │ │ ├── schema-path.d.ts │ │ ├── schema-path.d.ts.map │ │ ├── schema-requires-property.d.ts │ │ ├── schema-requires-property.d.ts.map │ │ ├── spectral-context-utils.d.ts │ │ ├── spectral-context-utils.d.ts.map │ │ ├── validate-composed-schemas.d.ts │ │ ├── validate-composed-schemas.d.ts.map │ │ ├── validate-nested-schemas.d.ts │ │ ├── validate-nested-schemas.d.ts.map │ │ ├── validate-subschemas.d.ts │ │ └── validate-subschemas.d.ts.map └── validator │ ├── .npmignore │ ├── .releaserc │ ├── CHANGELOG.md │ ├── README.md │ ├── package.json │ ├── src │ ├── cli-validator │ │ ├── index.js │ │ ├── run-validator.js │ │ └── utils │ │ │ ├── check-ruleset-version.js │ │ │ ├── check-version.js │ │ │ ├── cli-options.js │ │ │ ├── configuration-manager.js │ │ │ ├── file-extension-validator.js │ │ │ ├── get-copyright-string.js │ │ │ ├── get-default-ruleset-version.js │ │ │ ├── get-local-ruleset-version.js │ │ │ ├── get-version-string.js │ │ │ ├── index.js │ │ │ ├── preprocess-file.js │ │ │ ├── print-json.js │ │ │ ├── print-results.js │ │ │ ├── print-versions.js │ │ │ ├── read-yaml.js │ │ │ └── validate-schema.js │ ├── markdown-report │ │ ├── index.js │ │ ├── markdown-table.js │ │ ├── report.js │ │ ├── tables │ │ │ ├── categorized-scores.js │ │ │ ├── index.js │ │ │ ├── primary.js │ │ │ ├── rule-violation-details.js │ │ │ ├── rule-violation-summary.js │ │ │ └── scoring-data.js │ │ └── write-file.js │ ├── schemas │ │ ├── config-file.yaml │ │ ├── results-object.yaml │ │ └── rubric-entry.yaml │ ├── scoring-tool │ │ ├── categories.js │ │ ├── compute-metrics.js │ │ ├── get-title.js │ │ ├── index.js │ │ ├── metrics.js │ │ ├── output.js │ │ ├── rubric.js │ │ └── score.js │ └── spectral │ │ ├── index.js │ │ └── utils.js │ └── test │ ├── cli-validator │ ├── alternate-spectral-configs │ │ └── extends-default.yaml │ ├── mock-files │ │ ├── bad-json.json │ │ ├── config │ │ │ ├── config1.yaml │ │ │ ├── five-warnings.json │ │ │ ├── invalid-config.yaml │ │ │ ├── invalid-json.json │ │ │ ├── invalid-values.json │ │ │ ├── valid-config.js │ │ │ ├── valid-config.json │ │ │ ├── valid-config.yaml │ │ │ └── zero-warnings.json │ │ ├── multi-file-spec │ │ │ ├── city.yaml │ │ │ ├── main.yaml │ │ │ ├── schema.yaml │ │ │ └── sports-team.yaml │ │ ├── oas3 │ │ │ ├── clean-with-tabs.yml │ │ │ ├── clean.yml │ │ │ ├── complex-test-compose-model.yaml │ │ │ ├── component-path-example.yaml │ │ │ ├── compose-model-items.yaml │ │ │ ├── compose-model-props.yaml │ │ │ ├── compose-models-use-array.yaml │ │ │ ├── duplicate-keys.json │ │ │ ├── err-and-warn.yaml │ │ │ ├── just-warn.yml │ │ │ ├── test-compose-model.yaml │ │ │ ├── test-formats.yaml │ │ │ ├── testoneof.yaml │ │ │ ├── trailing-comma.json │ │ │ └── warn-threshold.yml │ │ ├── oas31 │ │ │ ├── clean.yml │ │ │ ├── component-path-example.yaml │ │ │ ├── err-and-warn.yaml │ │ │ └── warn-threshold.yml │ │ ├── openapi-3-1.json │ │ └── swagger-2.json │ └── tests │ │ ├── configuration-manager.test.js │ │ ├── error-handling.test.js │ │ ├── expected-output.test.js │ │ ├── option-handling.test.js │ │ ├── run-validator.test.js │ │ └── utils │ │ ├── read-yaml.test.js │ │ └── schema-validator.test.js │ ├── markdown-report │ ├── markdown-table.test.js │ ├── report.test.js │ ├── tables │ │ ├── categorized-scores.test.js │ │ ├── primary.test.js │ │ ├── rule-violation-details.test.js │ │ ├── rule-violation-summary.test.js │ │ └── scoring-data.test.js │ └── write-file.test.js │ ├── scoring-tool │ ├── categories.test.js │ ├── compute-metrics.test.js │ ├── get-title.test.js │ ├── metrics.test.js │ ├── output.test.js │ ├── rubric.test.js │ └── score.test.js │ └── test-utils │ ├── get-captured-text.js │ ├── get-message-and-path-from-captured-text.js │ ├── index.js │ ├── mock-json-output.json │ ├── parse-scoring-table.js │ ├── strip-ansi.js │ └── test-validator.js └── scripts ├── create-binaries.sh ├── deploy-container-image.sh ├── generate-utilities-docs.js └── templates └── package.mustache /.cra/.cveignore: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "cve": "CVE-2024-24828", 4 | "alwaysOmit": true 5 | }, 6 | { 7 | "cve": "CVE-2023-42366", 8 | "alwaysOmit": true 9 | }, 10 | { 11 | "cve": "CVE-2023-42363", 12 | "alwaysOmit": true 13 | }, 14 | { 15 | "cve": "CVE-2023-42364", 16 | "alwaysOmit": true 17 | }, 18 | { 19 | "cve": "CVE-2023-42365", 20 | "alwaysOmit": true 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | Dockerfile 3 | .git 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['prettier'], 3 | env: { 4 | es6: true, 5 | node: true, 6 | mocha: true, 7 | jest: true, 8 | }, 9 | parserOptions: { 10 | ecmaVersion: 13, 11 | }, 12 | rules: { 13 | 'prettier/prettier': [ 14 | 'error', 15 | { 16 | singleQuote: true, 17 | arrowParens: 'avoid', 18 | trailingComma: 'es5', 19 | }, 20 | ], 21 | 'no-console': 0, 22 | 'no-var': 'error', 23 | 'prefer-const': 'error', 24 | }, 25 | extends: ['prettier', 'eslint:recommended'], 26 | globals: { 27 | AggregateError: 'readonly', 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## PR summary 2 | 3 | 4 | 5 | ## PR Checklist 6 | 7 | ### General checklist 8 | Please make sure that your PR fulfills the following requirements: 9 | - [ ] The commit message follows the [Angular Commit Message Guidelines](https://github.com/angular/angular/blob/master/CONTRIBUTING.md#-commit-message-guidelines). 10 | - [ ] Tests for the changes have been added (for bug fixes / features) 11 | - [ ] Docs have been added / updated (for bug fixes / features) 12 | - [ ] Dependencies have been updated as needed 13 | - [ ] `.secrets.baseline` has been updated as needed 14 | - [ ] `npm run update-utilities` has been run if any files in `packages/utilities/src` have been updated 15 | 16 | #### Checklist for adding a new validation rule: 17 | - [ ] Added new validation rule definition (packages/ruleset/src/rules/*.js, index.js) 18 | - [ ] If necessary, added new validation rule implementation (packages/ruleset/src/functions/*.js, updated index.js) 19 | - [ ] Added new rule to default configuration (packages/ruleset/src/ibm-oas.js) 20 | - [ ] Added tests for new rule (packages/ruleset/test/*.test.js) 21 | - [ ] Added docs for new rule (docs/ibm-cloud-rules.md) 22 | - [ ] Added scoring rubric entry for new rule (packages/validator/src/scoring-tool/rubric.js) 23 | -------------------------------------------------------------------------------- /.github/workflows/docker.yaml: -------------------------------------------------------------------------------- 1 | # This workflow is responsible for deploying a Docker image for a given 2 | # release to Docker Hub. It is triggered when a new release is created. This 3 | # job is separated from the publishing job, as it may fail independently and 4 | # we need to be able to retry it without re-running the publish step. 5 | 6 | name: docker 7 | 8 | on: 9 | release: 10 | types: 11 | - published 12 | workflow_dispatch: 13 | # Allow this workflow to be triggered manually 14 | 15 | jobs: 16 | deploy-image: 17 | # We want to trigger this job ONLY for a published release 18 | # related to the "ibm-openapi-validator" package. 19 | name: deploy-image 20 | if: "contains(github.ref_name, 'ibm-openapi-validator')" 21 | runs-on: ubuntu-latest 22 | 23 | steps: 24 | # Display some info about the build for debugging purposes. 25 | - name: Trigger info 26 | run: | 27 | echo "Build triggered by event: " ${{ github.event_name }} 28 | echo " git ref: " ${{ github.ref }} 29 | echo " git ref_name: " ${{ github.ref_name }} 30 | 31 | - name: Checkout repository 32 | uses: actions/checkout@v4 33 | 34 | # Node is used in the deployment script to extract the numeric version 35 | # from packages/validator/package.json. 36 | - name: Setup Node.js 37 | uses: actions/setup-node@v4 38 | with: 39 | node-version: ${{ matrix.node-version }} 40 | 41 | - name: Build and deploy Docker image 42 | env: 43 | DOCKER_HUB_TOKEN: ${{ secrets.DOCKER_HUB_TOKEN }} 44 | run: ./scripts/deploy-container-image.sh 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | npm-debug.log* 4 | .eslintcache 5 | dist 6 | .validaterc 7 | .validateignore 8 | .idea/ 9 | bin/ 10 | .nyc_output/ 11 | coverage/ 12 | .spectral.json 13 | .spectral.yaml 14 | .spectral.yml 15 | .spectral.js 16 | .nvmrc 17 | .vscode/ -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": ["main"], 3 | "debug": true, 4 | "plugins": [ 5 | "@semantic-release/commit-analyzer", 6 | "@semantic-release/release-notes-generator", 7 | "@semantic-release/changelog", 8 | "@semantic-release/npm", 9 | "@semantic-release/git", 10 | "@semantic-release/github" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | LABEL org.opencontainers.image.source="https://github.com/IBM/openapi-validator" 3 | LABEL org.opencontainers.image.documentation="https://github.com/IBM/openapi-validator#container-image" 4 | LABEL org.opencontainers.image.licenses="Apache-2.0" 5 | LABEL org.opencontainers.image.title="OpenAPI Validator" 6 | LABEL org.opencontainers.image.description="The IBM OpenAPI Validator lets you validate OpenAPI 3.x documents according to the OpenAPI 3.x specification, as well as IBM-defined best practices." 7 | 8 | WORKDIR /src 9 | # Copy and cache dependencies first for faster local rebuilds. 10 | COPY ./package.json ./package-lock.json /src/ 11 | COPY ./packages/ruleset/package.json /src/packages/ruleset/package.json 12 | COPY ./packages/validator/package.json /src/packages/validator/package.json 13 | COPY ./packages/utilities/package.json /src/packages/utilities/package.json 14 | RUN npm ci 15 | 16 | # Add validator to executable $PATH for easy integration in custom downstream images. 17 | ENV PATH=/usr/local/lib/node_modules:$PATH 18 | # Add IBM ruleset to Node module search path for easy volume mounts and ruleset extensions. 19 | ENV NODE_PATH=/usr/local/lib/node_modules 20 | COPY . /src 21 | RUN npm run link 22 | 23 | WORKDIR /data 24 | ENTRYPOINT ["/usr/local/bin/lint-openapi"] 25 | -------------------------------------------------------------------------------- /appscan-config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | test/ 5 | README.md 6 | node_modules/ 7 | packages/ruleset/node_modules/ 8 | packages/validator/node_modules/ 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /packages/ruleset/.npmignore: -------------------------------------------------------------------------------- 1 | .eslintcache 2 | coverage/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /packages/ruleset/src/functions/array-of-arrays.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | isArraySchema, 8 | validateSubschemas, 9 | } = require('@ibm-cloud/openapi-ruleset-utilities'); 10 | const { LoggerFactory } = require('../utils'); 11 | 12 | let ruleId; 13 | let logger; 14 | module.exports = function (schema, _opts, context) { 15 | if (!logger) { 16 | ruleId = context.rule.name; 17 | logger = LoggerFactory.getInstance().getLogger(ruleId); 18 | } 19 | return validateSubschemas(schema, context.path, arrayOfArrays, true, false); 20 | }; 21 | 22 | function arrayOfArrays(schema, path) { 23 | if (isArraySchema(schema) && schema.items) { 24 | logger.debug( 25 | `${ruleId}: checking array schema at location: ${path.join('.')}` 26 | ); 27 | if (isArraySchema(schema.items)) { 28 | logger.debug('Found an array of arrays!'); 29 | return [ 30 | { 31 | message: 'Array schemas should avoid having items of type array', 32 | path, 33 | }, 34 | ]; 35 | } 36 | } 37 | 38 | return []; 39 | } 40 | -------------------------------------------------------------------------------- /packages/ruleset/src/functions/no-superfluous-allof.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { validateSubschemas } = require('@ibm-cloud/openapi-ruleset-utilities'); 7 | const { LoggerFactory } = require('../utils'); 8 | 9 | let ruleId; 10 | let logger; 11 | 12 | module.exports = function (schema, _opts, context) { 13 | if (!logger) { 14 | ruleId = context.rule.name; 15 | logger = LoggerFactory.getInstance().getLogger(ruleId); 16 | } 17 | 18 | return validateSubschemas(schema, context.path, checkForSuperfluousAllOf); 19 | }; 20 | 21 | function checkForSuperfluousAllOf(schema, path) { 22 | // We're interested only in schemas that contain ONLY a single-element allOf list. 23 | if ( 24 | Array.isArray(schema.allOf) && 25 | schema.allOf.length === 1 && 26 | Object.keys(schema).length === 1 27 | ) { 28 | return [ 29 | { 30 | // Use rule description for error message. 31 | message: '', 32 | path, 33 | }, 34 | ]; 35 | } 36 | 37 | return []; 38 | } 39 | -------------------------------------------------------------------------------- /packages/ruleset/src/functions/no-unsupported-keywords.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { LoggerFactory } = require('../utils'); 7 | 8 | let ruleId; 9 | let logger; 10 | 11 | const ErrorMsg = 12 | 'An unsupported OpenAPI 3.1 keyword was found in the OpenAPI document:'; 13 | 14 | module.exports = function (apidef, _opts, context) { 15 | if (!logger) { 16 | ruleId = context.rule.name; 17 | logger = LoggerFactory.getInstance().getLogger(ruleId); 18 | } 19 | return noUnsupportedKeywords(apidef); 20 | }; 21 | 22 | /** 23 | * If 'unevaluatedProperties' is specified within "schema" then it must be set to false. 24 | * 25 | * @param {*} apidef the API definition object 26 | * @returns an array of zero or more errors 27 | */ 28 | function noUnsupportedKeywords(apidef) { 29 | logger.debug(`${ruleId}: checking for unsupported OpenAPI 3.1 keywords`); 30 | 31 | const errors = []; 32 | 33 | if ('jsonSchemaDialect' in apidef) { 34 | logger.debug(`${ruleId}: found 'jsonSchemaDialect'`); 35 | errors.push({ 36 | message: `${ErrorMsg} jsonSchemaDialect`, 37 | path: ['jsonSchemaDialect'], 38 | }); 39 | } 40 | 41 | if ('webhooks' in apidef) { 42 | logger.debug(`${ruleId}: found 'webhooks`); 43 | errors.push({ 44 | message: `${ErrorMsg} webhooks`, 45 | path: ['webhooks'], 46 | }); 47 | } 48 | 49 | if (!errors.length) { 50 | logger.debug(`${ruleId}: PASSED!`); 51 | } 52 | return errors; 53 | } 54 | -------------------------------------------------------------------------------- /packages/ruleset/src/functions/operation-summary-exists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { LoggerFactory } = require('../utils'); 7 | 8 | let ruleId; 9 | let logger; 10 | 11 | module.exports = function (operation, _opts, context) { 12 | if (!logger) { 13 | ruleId = context.rule.name; 14 | logger = LoggerFactory.getInstance().getLogger(ruleId); 15 | } 16 | return operationSummary(operation, context.path); 17 | }; 18 | 19 | function operationSummary(operation, path) { 20 | logger.debug( 21 | `${ruleId}: checking summary for operation at location: ${path.join('.')}` 22 | ); 23 | if (!operationHasSummary(operation)) { 24 | logger.debug(`${ruleId}: no summary found!`); 25 | return [ 26 | { 27 | message: 'Operations must have a non-empty summary', 28 | 29 | // If the 'summary' field is defined, then include it in the error path. 30 | path: 'summary' in operation ? [...path, 'summary'] : path, 31 | }, 32 | ]; 33 | } 34 | 35 | return []; 36 | } 37 | 38 | function operationHasSummary(operation) { 39 | return operation.summary && operation.summary.toString().trim().length; 40 | } 41 | -------------------------------------------------------------------------------- /packages/ruleset/src/functions/operation-summary-length.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { LoggerFactory } = require('../utils'); 7 | 8 | let ruleId; 9 | let logger; 10 | 11 | /** 12 | * The implementation for this rule makes assumptions that are dependent on the 13 | * presence of the following other rules: 14 | * 15 | * - operation-summary: all operations define a non-empty summary 16 | */ 17 | 18 | module.exports = function (summary, _opts, context) { 19 | if (!logger) { 20 | ruleId = context.rule.name; 21 | logger = LoggerFactory.getInstance().getLogger(ruleId); 22 | } 23 | return checkSummaryLength(summary, context.path); 24 | }; 25 | 26 | function checkSummaryLength(summary, path) { 27 | logger.debug( 28 | `${ruleId}: checking operation summary at location: ${path.join('.')}` 29 | ); 30 | 31 | if (summary && summary.length > 80) { 32 | return [ 33 | { 34 | message: 'Operation summaries must be 80 characters or less in length', 35 | path, 36 | }, 37 | ]; 38 | } 39 | 40 | return []; 41 | } 42 | -------------------------------------------------------------------------------- /packages/ruleset/src/functions/parameter-default.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { LoggerFactory } = require('../utils'); 7 | 8 | let ruleId; 9 | let logger; 10 | 11 | module.exports = function (param, _opts, context) { 12 | if (!logger) { 13 | ruleId = context.rule.name; 14 | logger = LoggerFactory.getInstance().getLogger(ruleId); 15 | } 16 | return parameterDefault(param, context.path); 17 | }; 18 | 19 | const errorMsg = 'Required parameters should not define a default value'; 20 | 21 | function parameterDefault(param, path) { 22 | logger.debug(`${ruleId}: checking parameter at location: ${path.join('.')}`); 23 | 24 | if (param.required && paramHasDefault(param)) { 25 | logger.debug(`Parameter is required AND has a default value!`); 26 | return [ 27 | { 28 | message: errorMsg, 29 | path, 30 | }, 31 | ]; 32 | } 33 | 34 | return []; 35 | } 36 | 37 | /** 38 | * Returns true if 'param' has a default value defined in it. 39 | * This needs to take into account the oas2 flavor of a parameter 40 | * where the default value would be defined directly on the param object, 41 | * and the oas3 flavor where it is defined inside the param's schema. 42 | */ 43 | function paramHasDefault(param) { 44 | return param.schema 45 | ? param.schema.default !== undefined 46 | : param.default !== undefined; 47 | } 48 | -------------------------------------------------------------------------------- /packages/ruleset/src/functions/parameter-description-exists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { LoggerFactory } = require('../utils'); 7 | 8 | let ruleId; 9 | let logger; 10 | 11 | module.exports = function (param, _opts, context) { 12 | if (!logger) { 13 | ruleId = context.rule.name; 14 | logger = LoggerFactory.getInstance().getLogger(ruleId); 15 | } 16 | return parameterDescription(param, context.path); 17 | }; 18 | 19 | function parameterDescription(param, path) { 20 | logger.debug( 21 | `${ruleId}: checking parameter '${param.name}' at location: ${path.join( 22 | '.' 23 | )}` 24 | ); 25 | 26 | if (!paramHasDescription(param)) { 27 | logger.debug(`${ruleId}: no description found!`); 28 | return [ 29 | { 30 | message: 'Parameters should have a non-empty description', 31 | path, 32 | }, 33 | ]; 34 | } 35 | 36 | return []; 37 | } 38 | 39 | function paramHasDescription(param) { 40 | return param.description && param.description.toString().trim().length; 41 | } 42 | -------------------------------------------------------------------------------- /packages/ruleset/src/functions/parameter-order.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { LoggerFactory } = require('../utils'); 7 | 8 | let ruleId; 9 | let logger; 10 | 11 | module.exports = function (operation, _opts, context) { 12 | if (!logger) { 13 | ruleId = context.rule.name; 14 | logger = LoggerFactory.getInstance().getLogger(ruleId); 15 | } 16 | return parameterOrder(operation, context.path); 17 | }; 18 | 19 | function parameterOrder(op, path) { 20 | if (op.parameters && op.parameters.length > 0) { 21 | logger.debug( 22 | `${ruleId}: checking parameter order for operation at location: ${path.join( 23 | '.' 24 | )}` 25 | ); 26 | 27 | const errors = []; 28 | 29 | // Walk the list of parameters and report on any required params 30 | // that are listed after the first optional param. 31 | let haveOptional = false; 32 | for (let index = 0; index < op.parameters.length; index++) { 33 | const param = op.parameters[index]; 34 | 35 | if (param.required === true) { 36 | if (haveOptional) { 37 | logger.debug( 38 | `${ruleId}: found required param AFTER an optional param!` 39 | ); 40 | errors.push({ 41 | message: 42 | 'Required parameters should appear before optional parameters', 43 | path: [...path, 'parameters', index.toString()], 44 | }); 45 | } 46 | } else { 47 | haveOptional = true; 48 | } 49 | } 50 | 51 | return errors; 52 | } 53 | 54 | return []; 55 | } 56 | -------------------------------------------------------------------------------- /packages/ruleset/src/functions/property-name-collision.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { validateSubschemas } = require('@ibm-cloud/openapi-ruleset-utilities'); 7 | const { LoggerFactory } = require('../utils'); 8 | 9 | let ruleId; 10 | let logger; 11 | 12 | module.exports = function (schema, _opts, context) { 13 | if (!logger) { 14 | ruleId = context.rule.name; 15 | logger = LoggerFactory.getInstance().getLogger(ruleId); 16 | } 17 | return validateSubschemas(schema, context.path, propertyNameCollision); 18 | }; 19 | 20 | const errorMsg = 21 | 'Avoid duplicate property names within a schema, even if different case conventions are used'; 22 | 23 | function propertyNameCollision(schema, path) { 24 | if (!schema.properties) { 25 | return []; 26 | } 27 | 28 | const errors = []; 29 | 30 | const prevProps = []; 31 | 32 | for (const [propName, prop] of Object.entries(schema.properties)) { 33 | // Skip check for deprecated properties. 34 | if (prop.deprecated === true) continue; 35 | 36 | const caselessPropName = propName.replace(/[_-]/g, '').toLowerCase(); 37 | if (prevProps.includes(caselessPropName)) { 38 | logger.debug( 39 | `${ruleId}: found duplicate property '${propName}' in schema at location: ${path.join( 40 | '.' 41 | )}!` 42 | ); 43 | errors.push({ 44 | message: errorMsg, 45 | path: [...path, 'properties', propName], 46 | }); 47 | } else { 48 | prevProps.push(caselessPropName); 49 | } 50 | } 51 | 52 | return errors; 53 | } 54 | -------------------------------------------------------------------------------- /packages/ruleset/src/functions/response-example-exists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | module.exports = function (response) { 7 | if (!responseLevelExamples(response) && !schemaLevelExample(response)) { 8 | return [ 9 | { 10 | message: 'Response bodies should include an example response', 11 | }, 12 | ]; 13 | } 14 | }; 15 | 16 | function responseLevelExamples(response) { 17 | return response.example || response.examples; 18 | } 19 | 20 | function schemaLevelExample(response) { 21 | return response.schema && response.schema.example; 22 | } 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/functions/schema-or-content-provided.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | module.exports = function (obj) { 7 | if (!obj.schema && !obj.content) { 8 | return [ 9 | { 10 | message: 'Parameters must provide either a schema or content', 11 | }, 12 | ]; 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/accept-and-return-models.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | responseSchemas, 8 | requestBodySchemas, 9 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 10 | const { oas3 } = require('@stoplight/spectral-formats'); 11 | const { acceptAndReturnModels } = require('../functions'); 12 | 13 | module.exports = { 14 | description: 'Request and response bodies must be defined as model instances', 15 | given: [...responseSchemas, ...requestBodySchemas], 16 | message: '{{error}}', 17 | severity: 'error', 18 | formats: [oas3], 19 | resolved: true, 20 | then: { 21 | function: acceptAndReturnModels, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/accept-header.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | parameters, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { disallowedHeaderParameter } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Operations should not explicitly define the Accept header parameter', 15 | message: '{{description}}', 16 | formats: [oas3], 17 | given: parameters, 18 | severity: 'warn', 19 | then: { 20 | function: disallowedHeaderParameter, 21 | functionOptions: { 22 | headerName: 'Accept', 23 | }, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/anchored-patterns.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { anchoredPatterns } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Pattern attributes should be anchored with ^ and $', 14 | message: '{{error}}', 15 | severity: 'warn', 16 | formats: [oas3], 17 | resolved: true, 18 | given: schemas, 19 | then: { 20 | function: anchoredPatterns, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/api-symmetry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { apiSymmetry } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 11 | 'Variations of a resource schema should be graph fragments of the canonical schema', 12 | message: '{{error}}', 13 | given: ['$'], 14 | severity: 'warn', 15 | formats: [oas3], 16 | resolved: true, 17 | then: { 18 | function: apiSymmetry, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/array-attributes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { arrayAttributes } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Array schemas should have certain attributes defined', 14 | message: '{{error}}', 15 | severity: 'warn', 16 | formats: [oas3], 17 | resolved: true, 18 | given: schemas, 19 | then: { 20 | function: arrayAttributes, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/array-of-arrays.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { arrayOfArrays } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Array schema with items of type array should be avoided', 14 | message: '{{error}}', 15 | given: schemas, 16 | severity: 'warn', 17 | formats: [oas3], 18 | resolved: true, 19 | then: { 20 | function: arrayOfArrays, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/array-responses.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | operations, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { arrayResponses } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Operations should not return an array as the top-level structure of a response.', 15 | message: '{{error}}', 16 | given: operations, 17 | severity: 'error', 18 | formats: [oas3], 19 | resolved: true, 20 | then: { 21 | function: arrayResponses, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/authorization-header.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | parameters, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { disallowedHeaderParameter } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Operations should not explicitly define the Authorization header parameter', 15 | message: '{{description}}', 16 | formats: [oas3], 17 | given: parameters, 18 | severity: 'warn', 19 | then: { 20 | function: disallowedHeaderParameter, 21 | functionOptions: { 22 | headerName: 'Authorization', 23 | }, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/avoid-multiple-types.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3_1 } = require('@stoplight/spectral-formats'); 7 | const { avoidMultipleTypes } = require('../functions'); 8 | const { 9 | schemas, 10 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 11 | 12 | module.exports = { 13 | description: 14 | 'OpenAPI 3.1 documents should avoid multiple types in the schema "type" field.', 15 | message: '{{error}}', 16 | given: schemas, 17 | severity: 'error', 18 | formats: [oas3_1], 19 | resolved: true, 20 | then: { 21 | function: avoidMultipleTypes, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/binary-schemas.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { binarySchemas } = require('../functions'); 11 | 12 | // 1. Parameters should not contain binary (type: string, format: binary) values. 13 | // 2. JSON request bodies should not contain binary (type: string, format: binary) values. 14 | // 3. JSON response bodies should not contain binary (type: string, format: binary) values. 15 | module.exports = { 16 | description: 17 | 'Checks that binary schemas are used only in the proper places within an API definition.', 18 | message: '{{error}}', 19 | formats: [oas3], 20 | given: schemas, 21 | severity: 'warn', 22 | resolved: true, 23 | then: { 24 | function: binarySchemas, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/circular-refs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { circularRefs } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 'API definition should not contain circular references.', 11 | message: '{{error}}', 12 | given: '$..$ref', 13 | severity: 'warn', 14 | formats: [oas3], 15 | resolved: true, 16 | then: { 17 | function: circularRefs, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/collection-array-property.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { collectionArrayProperty } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 11 | 'Collection list operation response schema should define array property whose name matches the final path segment of the operation path', 12 | message: '{{error}}', 13 | given: 14 | '$.paths[*].get.responses[?(@property.match(/2\\d\\d/))].content[*].schema', 15 | severity: 'warn', 16 | formats: [oas3], 17 | resolved: true, 18 | then: { 19 | function: collectionArrayProperty, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/consecutive-path-segments.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | paths, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { consecutivePathSegments } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Path strings should not contain two or more consecutive path parameter references', 15 | message: '{{error}}', 16 | formats: [oas3], 17 | given: paths, 18 | severity: 'error', 19 | resolved: true, 20 | then: { 21 | function: consecutivePathSegments, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/content-contains-schema.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { truthy } = require('@stoplight/spectral-functions'); 8 | 9 | module.exports = { 10 | description: 'Content entries must specify a schema', 11 | formats: [oas3], 12 | given: [ 13 | '$.paths[*][post,put,patch].requestBody.content[*]', 14 | '$.paths[*][get,post,put,patch,delete][parameters,responses][*].content[*]', 15 | ], 16 | severity: 'warn', 17 | resolved: true, 18 | then: { 19 | field: 'schema', 20 | function: truthy, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/content-type-header.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | parameters, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { disallowedHeaderParameter } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Operations should not explicitly define the Content-Type header parameter', 15 | message: '{{description}}', 16 | formats: [oas3], 17 | given: parameters, 18 | severity: 'warn', 19 | then: { 20 | function: disallowedHeaderParameter, 21 | functionOptions: { 22 | headerName: 'Content-Type', 23 | }, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/content-type-is-specific.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { falsy } = require('@stoplight/spectral-functions'); 8 | 9 | module.exports = { 10 | description: '*/* should only be used when all content types are supported', 11 | formats: [oas3], 12 | severity: 'warn', 13 | resolved: true, 14 | given: [ 15 | '$.paths[*][*][parameters,responses][*].content', 16 | '$.paths[*][*][requestBody].content', 17 | ], 18 | then: { 19 | field: '*/*', 20 | function: falsy, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/delete-body.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | operations, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { deleteBody } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Delete operations should not contain a requestBody.', 14 | message: '{{error}}', 15 | severity: 'off', 16 | formats: [oas3], 17 | resolved: true, 18 | given: operations, 19 | then: { 20 | function: deleteBody, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/discriminator-property-exists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { discriminatorPropertyExists } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'The discriminator property name must be defined in this schema', 14 | message: '{{error}}', 15 | given: schemas, 16 | severity: 'error', 17 | formats: [oas3], 18 | resolved: true, 19 | then: { 20 | function: discriminatorPropertyExists, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/duplicate-path-parameter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | paths, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { duplicatePathParameter } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Common path parameters should be defined on the path object.', 14 | given: paths, 15 | severity: 'warn', 16 | formats: [oas3], 17 | resolved: true, 18 | then: { 19 | function: duplicatePathParameter, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/enum-casing-convention.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { enumCasingConvention } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Enum values must follow a specified case convention', 14 | message: '{{error}}', 15 | formats: [oas3], 16 | given: schemas, 17 | severity: 'error', 18 | then: { 19 | function: enumCasingConvention, 20 | functionOptions: { 21 | type: 'snake', 22 | }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/error-content-type-is-json.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { truthy } = require('@stoplight/spectral-functions'); 8 | 9 | module.exports = { 10 | description: 'error response should support application/json', 11 | formats: [oas3], 12 | severity: 'warn', 13 | resolved: true, 14 | given: [ 15 | '$.paths[*][*].responses[?(@property >= 400 && @property < 600)].content', 16 | ], 17 | then: { 18 | field: 'application/json', 19 | function: truthy, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/error-response-schemas.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { errorResponseSchemas } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 11 | 'Error response schemas should comply with API Handbook guidance', 12 | message: '{{error}}', 13 | given: 14 | '$.paths[*][*].responses[?(@property >= 400 && @property < 600)].content[*].schema', 15 | severity: 'warn', 16 | formats: [oas3], 17 | resolved: true, 18 | then: { 19 | function: errorResponseSchemas, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/etag-header-exists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | paths, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { etagHeaderExists } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'ETag response header should be defined in GET operation for resources that support If-Match or If-None-Match header parameters', 15 | message: '{{error}}', 16 | given: paths, 17 | severity: 'error', 18 | formats: [oas3], 19 | resolved: true, 20 | then: { 21 | function: etagHeaderExists, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/examples-name-contains-space.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { pattern } = require('@stoplight/spectral-functions'); 7 | const { oas3 } = require('@stoplight/spectral-formats'); 8 | 9 | module.exports = { 10 | description: 'Examples name should not contain space', 11 | message: '{{description}}', 12 | severity: 'warn', 13 | resolved: false, 14 | formats: [oas3], 15 | given: '$.paths[*][*].responses[*][*][*].examples[*]~', 16 | then: { 17 | function: pattern, 18 | functionOptions: { 19 | notMatch: '^(.*\\s+.*)+$', 20 | }, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/ibm-sdk-operations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { schema } = require('@stoplight/spectral-functions'); 8 | 9 | module.exports = { 10 | description: 'Ensures that x-sdk-operations fields are properly structured', 11 | message: '{{error}}', 12 | given: '$.', 13 | severity: 'warn', 14 | formats: [oas3], 15 | resolved: true, 16 | then: { 17 | function: schema, 18 | functionOptions: { 19 | schema: { 20 | $ref: '../schemas/x-sdk-operations.json', 21 | }, 22 | }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/if-modified-since-header.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | parameters, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { disallowedHeaderParameter } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Operations should support the If-None-Match header parameter instead of If-Modified-Since', 15 | message: '{{description}}', 16 | formats: [oas3], 17 | given: parameters, 18 | severity: 'warn', 19 | resolved: true, 20 | then: { 21 | function: disallowedHeaderParameter, 22 | functionOptions: { 23 | headerName: 'If-Modified-Since', 24 | }, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/if-unmodified-since-header.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | parameters, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { disallowedHeaderParameter } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Operations should support the If-Match header parameter instead of If-Unmodified-Since', 15 | message: '{{description}}', 16 | formats: [oas3], 17 | given: parameters, 18 | severity: 'warn', 19 | resolved: true, 20 | then: { 21 | function: disallowedHeaderParameter, 22 | functionOptions: { 23 | headerName: 'If-Unmodified-Since', 24 | }, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/inline-schemas.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | unresolvedSchemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { inlineSchemas } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Nested objects should be defined as a $ref to a named schema', 14 | message: '{{error}}', 15 | formats: [oas3], 16 | given: unresolvedSchemas, 17 | severity: 'warn', 18 | resolved: false, 19 | then: { 20 | function: inlineSchemas, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/integer-attributes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { integerAttributes } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Integer schemas should have certain attributes defined', 14 | message: '{{error}}', 15 | severity: 'error', 16 | formats: [oas3], 17 | resolved: true, 18 | given: schemas, 19 | then: { 20 | function: integerAttributes, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/major-version-in-path.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2025 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { majorVersionInPath } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 11 | 'All paths must contain the API major version as a distinct path segment', 12 | message: '{{error}}', 13 | formats: [oas3], 14 | given: '$', 15 | severity: 'warn', 16 | then: { 17 | function: majorVersionInPath, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/merge-patch-properties.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { mergePatchProperties } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 11 | 'A JSON merge-patch requestBody should have no required properties', 12 | message: '{{description}}', 13 | given: [ 14 | // This expression should visit the request body schema for each "merge-patch" type operation. 15 | '$.paths[*][patch].requestBody.content[?(@property.match(/^application\\/merge-patch\\+json(;.*)*/))].schema', 16 | ], 17 | severity: 'warn', 18 | formats: [oas3], 19 | resolved: true, 20 | then: { 21 | function: mergePatchProperties, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/no-ambiguous-paths.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { noAmbiguousPaths } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 'Avoid ambiguous path strings within an OpenAPI document', 11 | message: '{{error}}', 12 | severity: 'warn', 13 | formats: [oas3], 14 | resolved: true, 15 | given: ['$.paths'], 16 | then: { 17 | function: noAmbiguousPaths, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/no-nullable-properties.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { 8 | schemas, 9 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 10 | const { noNullableProperties } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Nullable properties should exist only in JSON merge-patch request bodies', 15 | message: '{{error}}', 16 | severity: 'warn', 17 | formats: [oas3], 18 | resolved: true, 19 | given: schemas, 20 | then: { 21 | function: noNullableProperties, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/no-operation-requestbody.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | operations, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { noOperationRequestBody } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Certain operations should not contain a requestBody', 14 | message: '{{error}}', 15 | severity: 'warn', 16 | formats: [oas3], 17 | resolved: true, 18 | given: operations, 19 | then: { 20 | function: noOperationRequestBody, 21 | functionOptions: { 22 | // HTTP methods that should NOT have a requestBody: 23 | httpMethods: ['delete', 'get', 'head', 'options'], 24 | }, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/no-ref-in-example.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { noRefInExample } = require('../functions'); 8 | module.exports = { 9 | description: 'The use of $ref is not valid within an example field', 10 | message: '{{description}}', 11 | given: ['$..example'], 12 | severity: 'error', 13 | formats: [oas3], 14 | resolved: false, 15 | then: { 16 | function: noRefInExample, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/no-superfluous-allof.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { noSuperfluousAllOf } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Avoid schemas containing only a single-element allOf', 14 | given: schemas, 15 | severity: 'warn', 16 | formats: [oas3], 17 | resolved: true, 18 | then: { 19 | function: noSuperfluousAllOf, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/no-unsupported-keywords.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3_1 } = require('@stoplight/spectral-formats'); 7 | const { noUnsupportedKeywords } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 11 | 'Verifies that unsupported OpenAPI 3.1 keywords are not used in the API document.', 12 | message: '{{error}}', 13 | given: ['$'], 14 | severity: 'error', 15 | formats: [oas3_1], 16 | resolved: false, 17 | then: { 18 | function: noUnsupportedKeywords, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/operation-responses.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | operations, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3_1 } = require('@stoplight/spectral-formats'); 10 | const { truthy } = require('@stoplight/spectral-functions'); 11 | 12 | module.exports = { 13 | description: 'Verifies that each operation has a "responses" field', 14 | message: 'Operations MUST have a "responses" field', 15 | severity: 'error', 16 | formats: [oas3_1], 17 | resolved: true, 18 | given: operations, 19 | then: { 20 | field: 'responses', 21 | function: truthy, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/operation-summary-exists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | operations, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { operationSummaryExists } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Operations must have a non-empty summary', 14 | given: operations, 15 | severity: 'warn', 16 | formats: [oas3], 17 | resolved: true, 18 | then: { 19 | function: operationSummaryExists, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/operation-summary-length.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | operations, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { operationSummaryLength } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Operation summaries must be 80 characters or less in length', 14 | given: operations.map(op => `${op}.summary`), 15 | severity: 'error', 16 | formats: [oas3], 17 | resolved: true, 18 | then: { 19 | function: operationSummaryLength, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/operationid-casing-convention.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | operations, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { operationIdCasingConvention } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Operation ids must follow a specified case convention', 14 | message: '{{error}}', 15 | formats: [oas3], 16 | given: operations, 17 | severity: 'warn', 18 | then: { 19 | function: operationIdCasingConvention, 20 | functionOptions: { 21 | type: 'snake', 22 | }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/operationid-naming-convention.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas2, oas3 } = require('@stoplight/spectral-formats'); 7 | const { operationIdNamingConvention } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 'Operation ids should follow naming convention', 11 | message: '{{error}}', 12 | given: ['$'], 13 | severity: 'warn', 14 | formats: [oas2, oas3], 15 | resolved: true, 16 | then: { 17 | function: operationIdNamingConvention, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/optional-request-body-deprecated.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { optionalRequestBodyDeprecated } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 11 | 'An optional requestBody with required properties should probably be required', 12 | message: '{{description}}', 13 | given: 14 | "$.paths[*][*][?(@property === 'requestBody' && @.required !== true)].content[*].schema", 15 | severity: 'off', 16 | formats: [oas3], 17 | resolved: true, 18 | then: { 19 | function: optionalRequestBodyDeprecated, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/optional-request-body.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { optionalRequestBody } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 11 | 'An optional requestBody with required properties should probably be required', 12 | message: '{{description}}', 13 | given: 14 | "$.paths[*][*][?(@property === 'requestBody' && @.required !== true)].content[*].schema", 15 | severity: 'info', 16 | formats: [oas3], 17 | resolved: true, 18 | then: { 19 | function: optionalRequestBody, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/pagination-style.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | paths, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { paginationStyle } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'List operations should have correct pagination style', 14 | message: '{{error}}', 15 | given: paths, 16 | severity: 'warn', 17 | formats: [oas3], 18 | resolved: true, 19 | then: { 20 | function: paginationStyle, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/parameter-default.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | parameters, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas2, oas3 } = require('@stoplight/spectral-formats'); 10 | const { parameterDefault } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Required parameters should not define a default value', 14 | message: '{{error}}', 15 | given: parameters, 16 | severity: 'warn', 17 | formats: [oas2, oas3], 18 | resolved: true, 19 | then: { 20 | function: parameterDefault, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/parameter-description-exists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | parameters, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { parameterDescriptionExists } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Parameters should have a non-empty description', 14 | message: '{{error}}', 15 | given: parameters, 16 | severity: 'warn', 17 | formats: [oas3], 18 | resolved: true, 19 | then: { 20 | function: parameterDescriptionExists, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/parameter-order.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | operations, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { parameterOrder } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'All required operation parameters should be listed before any optional parameters.', 15 | message: '{{error}}', 16 | given: operations, 17 | severity: 'warn', 18 | formats: [oas3], 19 | resolved: true, 20 | then: { 21 | function: parameterOrder, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/parameter-schema-or-content-exists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { schemaOrContentProvided } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 'Parameter must provide either a schema or content', 11 | message: '{{error}}', 12 | severity: 'error', 13 | formats: [oas3], 14 | resolved: true, 15 | given: '$.paths[*][*].parameters[*]', 16 | then: { 17 | function: schemaOrContentProvided, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/patch-request-content-type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | patchOperations, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { patchRequestContentType } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'PATCH operations should support content types application/json-patch+json or application/merge-patch+json', 15 | message: '{{description}}', 16 | given: patchOperations, 17 | severity: 'warn', 18 | formats: [oas3], 19 | resolved: true, 20 | then: { 21 | function: patchRequestContentType, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/path-parameter-not-crn.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { pathParameterNotCRN } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 11 | 'Path parameters should not be defined as a CRN (Cloud Resource Name) value', 12 | message: '{{error}}', 13 | formats: [oas3], 14 | given: [ 15 | '$.paths[*].parameters[?(@.in === "path")]', 16 | '$.paths[*][get,put,post,delete,options,head,patch,trace].parameters[?(@.in === "path")]', 17 | ], 18 | severity: 'warn', 19 | resolved: true, 20 | then: { 21 | function: pathParameterNotCRN, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/path-segment-casing-convention.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | paths, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { pathSegmentCasingConvention } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Path segments must follow a specified case convention', 14 | message: '{{error}}', 15 | formats: [oas3], 16 | given: paths, 17 | severity: 'error', 18 | then: { 19 | function: pathSegmentCasingConvention, 20 | functionOptions: { 21 | type: 'snake', 22 | }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/pattern-properties.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3_1 } = require('@stoplight/spectral-formats'); 7 | const { patternPropertiesCheck } = require('../functions'); 8 | const { 9 | schemas, 10 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 11 | 12 | module.exports = { 13 | description: 14 | 'Enforces certain restrictions on the use of "patternProperties" within a schema.', 15 | message: '{{error}}', 16 | given: schemas, 17 | severity: 'error', 18 | formats: [oas3_1], 19 | resolved: true, 20 | then: { 21 | function: patternPropertiesCheck, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/precondition-header.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | operations, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { preconditionHeader } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Operations with `412` response must support at least one conditional header.', 15 | message: '{{error}}', 16 | formats: [oas3], 17 | given: operations, 18 | severity: 'error', 19 | resolved: true, 20 | then: { 21 | function: preconditionHeader, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/prefer-token-pagination.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | paths, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { preferTokenPagination } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Paginated list operations should use token-based pagination, rather than offset/limit pagination.', 15 | message: '{{error}}', 16 | given: paths, 17 | severity: 'warn', 18 | formats: [oas3], 19 | resolved: true, 20 | then: { 21 | function: preferTokenPagination, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/property-attributes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { propertyAttributes } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Performs checks on specific attributes of a schema or schema property', 15 | message: '{{error}}', 16 | given: schemas, 17 | severity: 'error', 18 | formats: [oas3], 19 | resolved: true, 20 | then: { 21 | function: propertyAttributes, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/property-casing-convention.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { propertyCasingConvention } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Property names must follow a specified case convention', 14 | message: '{{error}}', 15 | formats: [oas3], 16 | given: schemas, 17 | severity: 'error', 18 | then: { 19 | function: propertyCasingConvention, 20 | functionOptions: { 21 | type: 'snake', 22 | }, 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/property-consistent-name-and-type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { propertyConsistentNameAndType } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Schema properties that have the same name should also have the same types.', 15 | message: '{{error}}', 16 | formats: [oas3], 17 | given: schemas, 18 | severity: 'off', 19 | resolved: true, 20 | then: { 21 | function: propertyConsistentNameAndType, 22 | functionOptions: { 23 | excludedProperties: ['code', 'default', 'type', 'value'], 24 | }, 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/property-description-exists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { propertyDescriptionExists } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Schema properties should have a non-empty description', 14 | message: '{{error}}', 15 | given: schemas, 16 | severity: 'warn', 17 | formats: [oas3], 18 | resolved: true, 19 | then: { 20 | function: propertyDescriptionExists, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/property-name-collision.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { propertyNameCollision } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Avoid duplicate property names within a schema, even if they differ by case convention', 15 | message: '{{error}}', 16 | formats: [oas3], 17 | given: schemas, 18 | severity: 'error', 19 | resolved: true, 20 | then: { 21 | function: propertyNameCollision, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/ref-pattern.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { refPattern } = require('../functions'); 8 | 9 | module.exports = { 10 | description: '$refs must follow the correct pattern.', 11 | message: '{{error}}', 12 | given: '$..$ref', 13 | severity: 'warn', 14 | formats: [oas3], 15 | resolved: false, 16 | then: { 17 | function: refPattern, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/ref-sibling-duplicate-description.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { refSiblingDuplicateDescription } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Schemas and schema properties should avoid duplicate descriptions within allOf $ref siblings', 15 | message: '{{error}}', 16 | given: schemas, 17 | severity: 'warn', 18 | formats: [oas3], 19 | resolved: true, 20 | then: { 21 | function: refSiblingDuplicateDescription, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/request-and-response-content.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | operations, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { requestAndResponseContent } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Request bodies and non-204 responses should define a content object', 15 | given: operations, 16 | severity: 'warn', 17 | formats: [oas3], 18 | resolved: true, 19 | then: { 20 | function: requestAndResponseContent, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/requestbody-is-object.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { enumeration } = require('@stoplight/spectral-functions'); 7 | 8 | module.exports = { 9 | description: 'All request bodies MUST be structured as an object', 10 | given: 11 | '$.paths[*][*].requestBody.content[?(@property ~= "^application\\\\/json(;.*)*$")].schema', 12 | severity: 'error', 13 | then: { 14 | field: 'type', 15 | function: enumeration, 16 | functionOptions: { 17 | values: ['object'], 18 | }, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/requestbody-name.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | operations, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { requestBodyName } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Verifies that operations have the x-codegen-request-body-name extension set when needed', 15 | message: '{{error}}', 16 | given: operations, 17 | severity: 'off', 18 | formats: [oas3], 19 | resolved: true, 20 | then: { 21 | function: requestBodyName, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/required-array-properties-in-response.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | responseSchemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | 10 | const { requiredArrayPropertiesInResponse } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Array properties defined in a response should be required.', 14 | message: '{{error}}', 15 | given: responseSchemas, 16 | severity: 'error', 17 | resolved: true, 18 | then: { 19 | function: requiredArrayPropertiesInResponse, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/required-enum-properties-in-response.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | responseSchemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | 10 | const { requiredEnumPropertiesInResponse } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Enumeration properties defined in a response must be required.', 14 | message: '{{error}}', 15 | given: responseSchemas, 16 | severity: 'error', 17 | resolved: true, 18 | then: { 19 | function: requiredEnumPropertiesInResponse, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/required-property-missing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { requiredProperty } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'A required property is not in the schema', 14 | message: '{{error}}', 15 | formats: [oas3], 16 | given: schemas, 17 | severity: 'error', 18 | then: { 19 | function: requiredProperty, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/resource-response-consistency.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { resourceResponseConsistency } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 11 | 'Operations that create or update a resource should return the same schema as the "GET" request for the resource.', 12 | message: '{{error}}', 13 | formats: [oas3], 14 | given: [`$.paths[*][put,post,patch]`], 15 | severity: 'warn', 16 | resolved: true, 17 | then: { 18 | function: resourceResponseConsistency, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/response-example-exists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { responseExampleExists } = require('../functions'); 7 | 8 | module.exports = { 9 | description: 'Each response should include an example', 10 | message: '{{error}}', 11 | given: 12 | '$.paths[*][*].responses[?(@property >= 200 && @property < 300)].content.application/json', 13 | severity: 'warn', 14 | resolved: true, 15 | then: { 16 | function: responseExampleExists, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/response-status-codes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | operations, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { responseStatusCodes } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Performs multiple checks on the status codes used in operation responses', 15 | message: '{{error}}', 16 | formats: [oas3], 17 | given: operations, 18 | severity: 'warn', 19 | resolved: true, 20 | then: { 21 | function: responseStatusCodes, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/schema-casing-convention.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { schemaCasingConvention } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 'Schema names must follow a specified case convention', 11 | message: '{{error}}', 12 | formats: [oas3], 13 | given: ['$.components'], 14 | severity: 'warn', 15 | then: { 16 | function: schemaCasingConvention, 17 | functionOptions: { 18 | match: '/^[A-Z]+[a-z0-9]+([A-Z]+[a-z0-9]*)*$/', 19 | }, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/schema-description-exists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { schemaDescriptionExists } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Schemas should have a non-empty description', 14 | message: '{{error}}', 15 | given: schemas, 16 | severity: 'warn', 17 | formats: [oas3], 18 | resolved: true, 19 | then: { 20 | function: schemaDescriptionExists, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/schema-keywords.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3_1 } = require('@stoplight/spectral-formats'); 10 | const { allowedKeywords } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Verifies that schema objects include only allowed-listed keywords', 15 | message: '{{error}}', 16 | severity: 'error', 17 | formats: [oas3_1], 18 | resolved: true, 19 | given: schemas, 20 | then: { 21 | function: allowedKeywords, 22 | functionOptions: { 23 | keywordAllowList: [ 24 | '$ref', 25 | 'additionalProperties', 26 | 'allOf', 27 | 'anyOf', 28 | 'default', 29 | 'description', 30 | 'discriminator', 31 | 'enum', 32 | 'example', 33 | 'exclusiveMaximum', 34 | 'exclusiveMinimum', 35 | 'format', 36 | 'items', 37 | 'maximum', 38 | 'maxItems', 39 | 'maxLength', 40 | 'maxProperties', 41 | 'minimum', 42 | 'minItems', 43 | 'minLength', 44 | 'minProperties', 45 | 'multipleOf', 46 | 'not', 47 | 'oneOf', 48 | 'pattern', 49 | 'patternProperties', 50 | 'properties', 51 | 'readOnly', 52 | 'required', 53 | 'title', 54 | 'type', 55 | 'uniqueItems', 56 | 'unevaluatedProperties', 57 | 'writeOnly', 58 | ], 59 | }, 60 | }, 61 | }; 62 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/schema-naming-convention.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { schemaNamingConvention } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 'Schemas should follow naming conventions in the API Handbook', 11 | message: '{{error}}', 12 | given: ['$'], 13 | severity: 'warn', 14 | formats: [oas3], 15 | resolved: true, 16 | then: { 17 | function: schemaNamingConvention, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/schema-type-exists.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { schemaTypeExists } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Schemas and schema properties should have a non-empty `type` field. **This rule is disabled by default.**', 15 | message: '{{error}}', 16 | given: schemas, 17 | severity: 'off', 18 | formats: [oas3], 19 | resolved: true, 20 | then: { 21 | function: schemaTypeExists, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/schema-type-format.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { schemaTypeFormat } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Schemas and schema properties must use a valid combination of type and format', 15 | message: '{{error}}', 16 | given: schemas, 17 | severity: 'error', 18 | formats: [oas3], 19 | resolved: true, 20 | then: { 21 | function: schemaTypeFormat, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/securityscheme-attributes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | securitySchemes, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { securitySchemeAttributes } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Validates the attributes of security schemes within an OpenAPI 3 document', 15 | message: '{{error}}', 16 | given: securitySchemes, 17 | severity: 'error', 18 | formats: [oas3], 19 | resolved: true, 20 | then: { 21 | function: securitySchemeAttributes, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/securityschemes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { securitySchemes } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 'Validates the security schemes within an OpenAPI 3 document', 11 | message: '{{error}}', 12 | given: ['$'], 13 | severity: 'warn', 14 | formats: [oas3], 15 | resolved: true, 16 | then: { 17 | function: securitySchemes, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/server-variable-default-value.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { truthy } = require('@stoplight/spectral-functions'); 8 | 9 | module.exports = { 10 | description: 'Server variable should have default value', 11 | severity: 'warn', 12 | resolved: false, 13 | formats: [oas3], 14 | given: '$.servers[*][variables][*][default]', 15 | then: { 16 | function: truthy, 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/string-attributes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { stringAttributes } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'String schemas should have certain attributes defined', 14 | message: '{{error}}', 15 | severity: 'warn', 16 | formats: [oas3], 17 | resolved: true, 18 | given: schemas, 19 | then: { 20 | function: stringAttributes, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/summary-sentence-style.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { pattern } = require('@stoplight/spectral-functions'); 8 | 9 | module.exports = { 10 | description: 'Operation summaries should not have a trailing period', 11 | severity: 'warn', 12 | formats: [oas3], 13 | resolved: false, 14 | given: '$.paths[*][*].summary', 15 | then: { 16 | function: pattern, 17 | functionOptions: { 18 | notMatch: '\\.$', 19 | }, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/typed-enum.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas } = require('@stoplight/spectral-rulesets'); 10 | 11 | // Spectral's "typed-enum" rule matches any object that happens to have a 12 | // "type" and "enum" field on it. This results in false positives when 13 | // APIs have metadata, like SDK generation metadata, contained in an 14 | // extension, that contains objects with these keys are are not schemas. 15 | // This solves the issue by replacing the "given" field with our "schemas" 16 | // collection, modified to only give schemas with a "type" and "enum" field, 17 | // while otherwise maintaining Spectral's implementation of the rule. 18 | const typedEnum = oas.rules['typed-enum']; 19 | typedEnum.given = schemas.map(s => 20 | s.replace( 21 | '[*].schema', 22 | '[?(@.schema && @.schema.type && @.schema.enum)].schema' 23 | ) 24 | ); 25 | 26 | module.exports = typedEnum; 27 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/unevaluated-properties.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3_1 } = require('@stoplight/spectral-formats'); 7 | const { unevaluatedProperties } = require('../functions'); 8 | const { 9 | schemas, 10 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 11 | 12 | module.exports = { 13 | description: 14 | 'Enforces certain restrictions on the use of "unevaluatedProperties" within a schema.', 15 | message: '{{error}}', 16 | given: schemas, 17 | severity: 'error', 18 | formats: [oas3_1], 19 | resolved: true, 20 | then: { 21 | function: unevaluatedProperties, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/unique-parameter-request-property-names.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | paths, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { uniqueParameterRequestPropertyNames } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Names of requestBody properties should not be the same as operation parameter names', 15 | message: '{{error}}', 16 | given: paths, 17 | severity: 'error', 18 | formats: [oas3], 19 | resolved: true, 20 | then: { 21 | function: uniqueParameterRequestPropertyNames, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/unused-tags.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { oas3 } = require('@stoplight/spectral-formats'); 7 | const { unusedTags } = require('../functions'); 8 | 9 | module.exports = { 10 | description: 'Checks that each defined tag is actually used', 11 | message: '{{error}}', 12 | given: ['$'], 13 | severity: 'warn', 14 | formats: [oas3], 15 | resolved: true, 16 | then: { 17 | function: unusedTags, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/use-date-based-format.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { useDateBasedFormat } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Heuristically determine when a schema should have a format of "date" or "date-time"', 15 | message: '{{error}}', 16 | severity: 'warn', 17 | formats: [oas3], 18 | resolved: true, 19 | given: schemas, 20 | then: { 21 | function: useDateBasedFormat, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/valid-path-segments.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | paths, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { validatePathSegments } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 'Validates individual path segments within a path string', 14 | message: '{{error}}', 15 | formats: [oas3], 16 | given: paths, 17 | severity: 'error', 18 | resolved: true, 19 | then: { 20 | function: validatePathSegments, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/valid-schema-example.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { validSchemaExample } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Schema examples should validate against the schema they are defined for', 15 | message: '{{error}}', 16 | given: schemas, 17 | severity: 'warn', 18 | formats: [oas3], 19 | resolved: true, 20 | then: { 21 | function: validSchemaExample, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/rules/well-defined-dictionaries.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | schemas, 8 | } = require('@ibm-cloud/openapi-ruleset-utilities/src/collections'); 9 | const { oas3 } = require('@stoplight/spectral-formats'); 10 | const { wellDefinedDictionaries } = require('../functions'); 11 | 12 | module.exports = { 13 | description: 14 | 'Dictionaries must be well defined and all values must share a single type.', 15 | message: '{{error}}', 16 | given: schemas, 17 | severity: 'warn', 18 | formats: [oas3], 19 | resolved: true, 20 | then: { 21 | function: wellDefinedDictionaries, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /packages/ruleset/src/utils/constants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | // Set of fields within a "path item" where we'd expect to find operations. 7 | const operationMethods = [ 8 | 'get', 9 | 'head', 10 | 'post', 11 | 'put', 12 | 'patch', 13 | 'delete', 14 | 'options', 15 | 'trace', 16 | ]; 17 | 18 | module.exports = operationMethods; 19 | -------------------------------------------------------------------------------- /packages/ruleset/src/utils/get-composite-schema-attribute.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { schemaHasConstraint } = require('@ibm-cloud/openapi-ruleset-utilities'); 7 | 8 | /** 9 | * Retrieves the value of "schema"'s attribute named "attrName" either directly from "schema" 10 | * or from one of its composition "children". 11 | * 12 | * This function can be used in scenarios where you need to retrieve a particular schema's 13 | * field (e.g. the pattern field), but the field might exist either in "schema" itself, 14 | * or within one or more allOf/anyOf/oneOf list element schemas. 15 | * @param {*} schema the schema to retrieve the attribute from 16 | * @param {*} attrName the name of the attribute to retrieve 17 | * @returns the value of the attribute or undefined if not present 18 | */ 19 | function getCompositeSchemaAttribute(schema, attrName) { 20 | let value = undefined; 21 | const foundConstraint = schemaHasConstraint(schema, s => { 22 | if (attrName in s && s[attrName] !== undefined && s[attrName] !== null) { 23 | value = s[attrName]; 24 | return true; 25 | } 26 | }); 27 | return foundConstraint ? value : undefined; 28 | } 29 | 30 | module.exports = getCompositeSchemaAttribute; 31 | -------------------------------------------------------------------------------- /packages/ruleset/src/utils/get-resource-specific-sibling-path.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { isObject } = require('@ibm-cloud/openapi-ruleset-utilities'); 7 | 8 | /** 9 | * Given a "generic" resource path string (e.g. '/foo'), look for a 10 | * "specific" resource path that is a sibling (e.g. /foo/{id}); if 11 | * found, return the sibling path string. 12 | * 13 | * @param {*} path the path string (for a generic path e.g. '/foo') 14 | * @param {*} apidef the resolved API spec, as an object 15 | * @returns the specific resource path, as a string 16 | */ 17 | function getResourceSpecificSiblingPath(path, apidef) { 18 | // Paths are expected to be arrays, API def is expected to be an object 19 | if (typeof path !== 'string' || !isObject(apidef)) { 20 | return; 21 | } 22 | 23 | // If this path already ends with a path parameter, return 'undefined'. 24 | // This function should only find a path if it is a sibling of the current path. 25 | if (path.trim().endsWith('}')) { 26 | return; 27 | } 28 | 29 | const siblingPathRE = new RegExp(`^${path}/{[^{}/]+}$`); 30 | return Object.keys(apidef.paths).find(p => siblingPathRE.test(p)); 31 | } 32 | 33 | module.exports = getResourceSpecificSiblingPath; 34 | -------------------------------------------------------------------------------- /packages/ruleset/src/utils/get-response-codes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { isObject } = require('@ibm-cloud/openapi-ruleset-utilities'); 7 | 8 | /** 9 | * For an OpenAPI 'responses' object, gather all of the defined 10 | * status codes, as well as the success (2XX) codes, and return 11 | * them together as a tuple. 12 | */ 13 | function getResponseCodes(responses) { 14 | if (!isObject(responses)) { 15 | return [[], []]; 16 | } 17 | const statusCodes = Object.keys(responses).filter(code => 18 | code.match(/^[1-5][0-9][0-9]$/) 19 | ); 20 | const successCodes = statusCodes.filter(code => code.match(/^2[0-9][0-9]$/)); 21 | return [statusCodes, successCodes]; 22 | } 23 | 24 | module.exports = getResponseCodes; 25 | -------------------------------------------------------------------------------- /packages/ruleset/src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | module.exports = { 7 | computeRefsAtPaths: require('./compute-refs-at-paths'), 8 | getCompositeSchemaAttribute: require('./get-composite-schema-attribute'), 9 | getResourceSpecificSiblingPath: require('./get-resource-specific-sibling-path'), 10 | getResourceOrientedPaths: require('./get-resource-oriented-paths'), 11 | getResponseCodes: require('./get-response-codes'), 12 | getSchemaNameAtPath: require('./get-schema-name-at-path'), 13 | isCreateOperation: require('./is-create-operation'), 14 | isDeprecated: require('./is-deprecated'), 15 | isEmptyObjectSchema: require('./is-empty-object-schema'), 16 | isOperationOfType: require('./is-operation-of-type'), 17 | isRefSiblingSchema: require('./is-ref-sibling-schema'), 18 | isRequestBodyExploded: require('./is-requestbody-exploded'), 19 | LoggerFactory: require('./logger-factory'), 20 | mergeAllOfSchemaProperties: require('./merge-allof-schema-properties'), 21 | operationMethods: require('./constants'), 22 | pathHasMinimallyRepresentedResource: require('./path-has-minimally-represented-resource'), 23 | pathMatchesRegexp: require('./path-matches-regexp'), 24 | ...require('./date-based-utils'), 25 | ...require('./mimetype-utils'), 26 | ...require('./pagination-utils'), 27 | ...require('./path-location-utils'), 28 | ...require('./schema-finding-utils'), 29 | }; 30 | -------------------------------------------------------------------------------- /packages/ruleset/src/utils/is-create-operation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { isObject } = require('@ibm-cloud/openapi-ruleset-utilities'); 7 | const isOperationOfType = require('./is-operation-of-type'); 8 | const getResourceSpecificSiblingPath = require('./get-resource-specific-sibling-path'); 9 | 10 | /** 11 | * Returns `true` if the operation represents a "create" operation. 12 | * 13 | * An operation is considered to be a "create" operation if the operationId starts with "create" 14 | * OR it's a POST request and there is a similar path but with a trailing path parameter reference. 15 | * 16 | * Note: the given path MUST be to an operation object. 17 | */ 18 | function isCreateOperation(operation, path, apidef) { 19 | // Paths are expected to be arrays, API def and operation are expected to be objects 20 | if (!Array.isArray(path) || !isObject(operation) || !isObject(apidef)) { 21 | return false; 22 | } 23 | 24 | // 1. If operationId starts with "create", we'll assume it's a create operation. 25 | if ( 26 | operation.operationId && 27 | operation.operationId.toString().trim().toLowerCase().startsWith('create') 28 | ) { 29 | return true; 30 | } 31 | 32 | // 2. If not a POST, then it's not a create operation. 33 | if (!isOperationOfType('post', path)) { 34 | return false; 35 | } 36 | 37 | // 3. Does this operation's path have a sibling path with a trailing path param reference? 38 | const siblingPath = getResourceSpecificSiblingPath(path.at(-2), apidef); 39 | return !!siblingPath; 40 | } 41 | 42 | module.exports = isCreateOperation; 43 | -------------------------------------------------------------------------------- /packages/ruleset/src/utils/is-deprecated.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { isObject } = require('@ibm-cloud/openapi-ruleset-utilities'); 7 | 8 | /** 9 | * Returns `true` if the input is an object with deprecated=true. 10 | */ 11 | const isDeprecated = obj => { 12 | return isObject(obj) && obj.deprecated === true; 13 | }; 14 | 15 | module.exports = isDeprecated; 16 | -------------------------------------------------------------------------------- /packages/ruleset/src/utils/is-operation-of-type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | /** 7 | * Checks a path to a given operation to determine if it is an 8 | * operation of a given type (e.g. 'post', 'patch', etc.) 9 | * 10 | * Note: the given path MUST be to an operation object. 11 | * 12 | * @param {*} type an operation method type, like 'get' or 'put' 13 | * @param {*} path the array of path segments indicating the "location" of the operation within the API definition 14 | * @returns a boolean value indicating if the operation is of the given type, based on the path 15 | */ 16 | function isOperationOfType(type, path) { 17 | if (typeof type !== 'string' || !Array.isArray(path)) { 18 | return false; 19 | } 20 | 21 | return type === path[path.length - 1].toString().trim().toLowerCase(); 22 | } 23 | 24 | module.exports = isOperationOfType; 25 | -------------------------------------------------------------------------------- /packages/ruleset/src/utils/path-has-minimally-represented-resource.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { isObject } = require('@ibm-cloud/openapi-ruleset-utilities'); 7 | 8 | /** 9 | * This function checks for a "minimally represented resource" (i.e. a resource 10 | * with no body representation) path. The path is considered to have a minimally 11 | * represented resource if the GET request on the path returns a 204 response. 12 | * 13 | * @param {string} path - the resource-specific path to check 14 | * @param {*} apidef - resolved api spec 15 | * @returns {bool} true if the resource on this path is minimally represented 16 | */ 17 | function pathHasMinimallyRepresentedResource(path, apidef) { 18 | // Perform input validation. A string and a paths-containing 19 | // API definition object are expected. 20 | if (typeof path !== 'string') { 21 | return false; 22 | } 23 | 24 | if (!isObject(apidef) || !isObject(apidef.paths)) { 25 | return false; 26 | } 27 | 28 | const pathObj = apidef.paths[path]; 29 | if ( 30 | isObject(pathObj) && 31 | isObject(pathObj.get) && 32 | isObject(pathObj.get.responses) 33 | ) { 34 | if ('204' in pathObj.get.responses) { 35 | return true; 36 | } 37 | } 38 | return false; 39 | } 40 | 41 | module.exports = pathHasMinimallyRepresentedResource; 42 | -------------------------------------------------------------------------------- /packages/ruleset/src/utils/path-matches-regexp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | /** 7 | * This function will return true if the specified path successfully matches the specified regex. 8 | * The elements of "path" are joined using "," as the delimiter, and the resulting 9 | * string is matched against the regex. 10 | * Example: 11 | * path: [ "paths", "/v1/drinks", "post", "parameters", "0", "schema"] 12 | * regex: /^.*parameters,\d+,schema$/ 13 | * result: true 14 | * @param {string[]} path an array of strings, where each string represents an element in the json path 15 | * @param {string|Regexp} regexp a string or RegExp to be matched against the path 16 | * @returns boolean 17 | */ 18 | const pathMatchesRegexp = (path, regexp) => { 19 | if (!Array.isArray(path)) { 20 | throw 'argument "path" must be an array!'; 21 | } 22 | 23 | if (!(regexp instanceof RegExp) && typeof regexp !== 'string') { 24 | throw 'argument "regexp" must be a string or RegExp instance!'; 25 | } 26 | 27 | const pathString = path.join(','); 28 | return pathString.match(regexp) ? true : false; 29 | }; 30 | 31 | module.exports = pathMatchesRegexp; 32 | -------------------------------------------------------------------------------- /packages/ruleset/test/meta/rule-style.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2022 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const rules = require('../../src/rules'); 7 | 8 | // Test cases to enforce rule style 9 | describe.each(Object.entries(rules))( 10 | 'Rules conform to required style', 11 | (name, rule) => { 12 | it(`${name} rule does not contain implicit .[] recursion`, async () => { 13 | const implicitRecursionRegex = /\.(?=\[)/; 14 | 15 | if (Array.isArray(rule.given)) { 16 | // `given` array 17 | rule.given.map(g => expect(g).not.toMatch(implicitRecursionRegex)); 18 | } else { 19 | // `given` string 20 | expect(rule.given).not.toMatch(implicitRecursionRegex); 21 | } 22 | }); 23 | } 24 | ); 25 | -------------------------------------------------------------------------------- /packages/ruleset/test/rules/error-content-type-is-json.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { errorContentTypeIsJson } = require('../../src/rules'); 7 | const { 8 | makeCopy, 9 | rootDocument, 10 | testRule, 11 | severityCodes, 12 | } = require('../test-utils'); 13 | 14 | const ruleId = 'ibm-error-content-type-is-json'; 15 | const rule = errorContentTypeIsJson; 16 | 17 | describe(`Spectral rule: ${ruleId}`, () => { 18 | it('should not error with a clean spec', async () => { 19 | const results = await testRule(ruleId, rule, rootDocument); 20 | expect(results).toHaveLength(0); 21 | }); 22 | 23 | it('should error if a response doesnt support application/json content', async () => { 24 | const testDocument = makeCopy(rootDocument); 25 | testDocument.paths['/v1/movies'].post.responses['500'] = { 26 | content: { 27 | 'text/plain': { 28 | description: 'just error text', 29 | }, 30 | }, 31 | }; 32 | 33 | const results = await testRule(ruleId, rule, testDocument); 34 | 35 | expect(results).toHaveLength(1); 36 | 37 | const validation = results[0]; 38 | expect(validation.code).toBe(ruleId); 39 | expect(validation.message).toBe( 40 | 'error response should support application/json' 41 | ); 42 | expect(validation.path).toStrictEqual([ 43 | 'paths', 44 | '/v1/movies', 45 | 'post', 46 | 'responses', 47 | '500', 48 | 'content', 49 | ]); 50 | expect(validation.severity).toBe(severityCodes.warning); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/ruleset/test/rules/operation-responses.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { operationResponses } = require('../../src/rules'); 7 | const { 8 | makeCopy, 9 | rootDocument, 10 | testRule, 11 | severityCodes, 12 | } = require('../test-utils'); 13 | 14 | const rule = operationResponses; 15 | const ruleId = 'ibm-operation-responses'; 16 | const expectedSeverity = severityCodes.error; 17 | const expectedMsg = 'Operations MUST have a "responses" field'; 18 | 19 | describe(`Spectral rule: ${ruleId}`, () => { 20 | beforeAll(() => { 21 | rootDocument.openapi = '3.1.0'; 22 | }); 23 | 24 | describe('Should not yield errors', () => { 25 | it('Clean spec', async () => { 26 | const results = await testRule(ruleId, rule, rootDocument); 27 | expect(results).toHaveLength(0); 28 | }); 29 | }); 30 | 31 | describe('Should yield errors', () => { 32 | it('No responses', async () => { 33 | const testDocument = makeCopy(rootDocument); 34 | 35 | delete testDocument.paths['/v1/drinks'].post.responses; 36 | 37 | const results = await testRule(ruleId, rule, testDocument); 38 | expect(results).toHaveLength(1); 39 | const expectedPaths = ['paths./v1/drinks.post']; 40 | for (let i = 0; i < results.length; i++) { 41 | expect(results[i].code).toBe(ruleId); 42 | expect(results[i].message).toBe(expectedMsg); 43 | expect(results[i].severity).toBe(expectedSeverity); 44 | expect(results[i].path.join('.')).toBe(expectedPaths[i]); 45 | } 46 | }); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/ruleset/test/rules/operationid-casing-convention.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { operationIdCasingConvention } = require('../../src/rules'); 7 | const { 8 | makeCopy, 9 | rootDocument, 10 | testRule, 11 | severityCodes, 12 | } = require('../test-utils'); 13 | 14 | const rule = operationIdCasingConvention; 15 | const ruleId = 'ibm-operationid-casing-convention'; 16 | const expectedSeverity = severityCodes.warning; 17 | 18 | describe(`Spectral rule: ${ruleId}`, () => { 19 | describe('Should not yield errors', () => { 20 | it('Clean spec', async () => { 21 | const results = await testRule(ruleId, rule, rootDocument); 22 | expect(results).toHaveLength(0); 23 | }); 24 | }); 25 | 26 | describe('Should yield errors', () => { 27 | it('Upper camel case operationId', async () => { 28 | const testDocument = makeCopy(rootDocument); 29 | testDocument.paths['/v1/drinks'].post.operationId = 'CreateDrink'; 30 | 31 | const results = await testRule(ruleId, rule, testDocument); 32 | expect(results).toHaveLength(1); 33 | 34 | expect(results[0].code).toBe(ruleId); 35 | expect(results[0].message).toBe('Operation ids must be snake case'); 36 | expect(results[0].severity).toBe(expectedSeverity); 37 | expect(results[0].path.join('.')).toBe( 38 | 'paths./v1/drinks.post.operationId' 39 | ); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/ruleset/test/rules/summary-sentence-style.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { summarySentenceStyle } = require('../../src/rules'); 7 | const { 8 | makeCopy, 9 | rootDocument, 10 | testRule, 11 | severityCodes, 12 | } = require('../test-utils'); 13 | 14 | const ruleId = 'ibm-summary-sentence-style'; 15 | const rule = summarySentenceStyle; 16 | 17 | describe(`Spectral rule: ${ruleId}`, () => { 18 | it('should not error with a clean spec', async () => { 19 | const results = await testRule(ruleId, rule, rootDocument); 20 | expect(results).toHaveLength(0); 21 | }); 22 | 23 | it('should warn if an operation summary ends with a period', async () => { 24 | const testDocument = makeCopy(rootDocument); 25 | testDocument.paths['/v1/movies'].post.summary = 'Should not be a sentence.'; 26 | 27 | const results = await testRule(ruleId, rule, testDocument); 28 | expect(results).toHaveLength(1); 29 | 30 | const validation = results[0]; 31 | expect(validation.code).toBe(ruleId); 32 | expect(validation.message).toBe( 33 | 'Operation summaries should not have a trailing period' 34 | ); 35 | expect(validation.path).toStrictEqual([ 36 | 'paths', 37 | '/v1/movies', 38 | 'post', 39 | 'summary', 40 | ]); 41 | expect(validation.severity).toBe(severityCodes.warning); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/ruleset/test/rules/unused-tags.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { unusedTags } = require('../../src/rules'); 7 | const { 8 | makeCopy, 9 | rootDocument, 10 | testRule, 11 | severityCodes, 12 | } = require('../test-utils'); 13 | 14 | const rule = unusedTags; 15 | const ruleId = 'ibm-openapi-tags-used'; 16 | const expectedSeverity = severityCodes.warning; 17 | 18 | describe(`Spectral rule: ${ruleId}`, () => { 19 | describe('Should not yield errors', () => { 20 | it('Clean spec', async () => { 21 | const results = await testRule(ruleId, rule, rootDocument); 22 | expect(results).toHaveLength(0); 23 | }); 24 | }); 25 | 26 | describe('Should yield errors', () => { 27 | it('Unreferenced tag', async () => { 28 | const testDocument = makeCopy(rootDocument); 29 | 30 | testDocument.tags.push({ name: 'UnusedTag' }); 31 | 32 | const results = await testRule(ruleId, rule, testDocument); 33 | expect(results).toHaveLength(1); 34 | expect(results[0].code).toBe(ruleId); 35 | expect(results[0].message).toBe( 36 | 'A tag is defined but never used: UnusedTag' 37 | ); 38 | expect(results[0].severity).toBe(expectedSeverity); 39 | expect(results[0].path.join('.')).toBe('tags.1'); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/ruleset/test/test-utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const allSchemasDocument = require('./all-schemas-document'); 7 | const makeCopy = require('./make-copy'); 8 | const testRule = require('./test-rule'); 9 | const rootDocument = require('./root-document'); 10 | const severityCodes = require('./severity-codes'); 11 | const helperArtifacts = require('./helper-artifacts'); 12 | 13 | module.exports = { 14 | allSchemasDocument, 15 | makeCopy, 16 | rootDocument, 17 | testRule, 18 | severityCodes, 19 | helperArtifacts, 20 | }; 21 | -------------------------------------------------------------------------------- /packages/ruleset/test/test-utils/make-copy.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | module.exports = obj => { 7 | return JSON.parse(JSON.stringify(obj)); 8 | }; 9 | -------------------------------------------------------------------------------- /packages/ruleset/test/test-utils/severity-codes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | module.exports = { 7 | error: 0, 8 | warning: 1, 9 | info: 2, 10 | hint: 3, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/ruleset/test/test-utils/test-rule.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { Spectral } = require('@stoplight/spectral-core'); 7 | 8 | /** 9 | * This is a test utility function that uses spectral to invoke the specified rule 10 | * on the specified API definition. 11 | * @param {*} ruleName the name of the rule (e.g. 'ibm-string-attributes') 12 | * @param {*} rule the rule object 13 | * @param {*} apidef the API definition 14 | * @param {*} exceptionIsExpected if true, exceptions are not displayed on console 15 | */ 16 | async function testRule(ruleName, rule, apidef, exceptionIsExpected = false) { 17 | try { 18 | const spectral = new Spectral(); 19 | spectral.setRuleset({ 20 | rules: { 21 | [ruleName]: rule, 22 | }, 23 | }); 24 | 25 | const results = await spectral.run(apidef); 26 | return results; 27 | } catch (err) { 28 | if (!exceptionIsExpected) { 29 | console.error(err); 30 | } 31 | throw err; 32 | } 33 | } 34 | 35 | module.exports = testRule; 36 | -------------------------------------------------------------------------------- /packages/ruleset/test/utils/constants.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { operationMethods } = require('../../src/utils'); 7 | 8 | describe('Utility function: operationMethods()', () => { 9 | it('should include relevant operation method names', async () => { 10 | expect(operationMethods).toEqual([ 11 | 'get', 12 | 'head', 13 | 'post', 14 | 'put', 15 | 'patch', 16 | 'delete', 17 | 'options', 18 | 'trace', 19 | ]); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/ruleset/test/utils/is-deprecated.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { isDeprecated } = require('../../src/utils'); 7 | 8 | describe('Utility function: isDeprecated()', () => { 9 | it('should return false if given a non-object', () => { 10 | expect(isDeprecated('not an object')).toBe(false); 11 | }); 12 | 13 | it('should return false if deprecated field is not set', () => { 14 | expect(isDeprecated({ type: 'string' })).toBe(false); 15 | }); 16 | 17 | it('should return false if deprecated field is set to false', () => { 18 | expect(isDeprecated({ type: 'string', deprecated: false })).toBe(false); 19 | }); 20 | 21 | it('should return true if deprecated field is set to true', () => { 22 | expect(isDeprecated({ type: 'string', deprecated: true })).toBe(true); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/ruleset/test/utils/is-operation-of-type.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { isOperationOfType } = require('../../src/utils'); 7 | 8 | describe('Utility function: isOperationOfType', () => { 9 | it('should return `true` when path matches the given type', () => { 10 | expect(isOperationOfType('get', ['paths', '/v1/things', 'get'])).toBe(true); 11 | }); 12 | 13 | it('should return `false` when path does not match the given type', () => { 14 | expect(isOperationOfType('post', ['paths', '/v1/things', 'get'])).toBe( 15 | false 16 | ); 17 | }); 18 | 19 | it('should return `false` when type is not a string', () => { 20 | expect(isOperationOfType(42, ['paths', '/v1/things', 'get'])).toBe(false); 21 | }); 22 | 23 | it('should return `false` when path is not an array', () => { 24 | expect(isOperationOfType('get', 'not an array')).toBe(false); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/utilities/.npmignore: -------------------------------------------------------------------------------- 1 | .eslintcache 2 | coverage/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /packages/utilities/README.md: -------------------------------------------------------------------------------- 1 | # OpenAPI Ruleset Utilities 2 | This package contains a number of JS functions that may be useful in developing a custom Spectral ruleset for validating OpenAPI documents. 3 | 4 | ## Installation 5 | 6 | `npm install @ibm-cloud/openapi-ruleset-utilities` 7 | 8 | ## Documentation 9 | 10 | See [this page for comprehensive documentation](../../docs/openapi-ruleset-utilities.md) 11 | on all functionality available in this package. 12 | -------------------------------------------------------------------------------- /packages/utilities/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@ibm-cloud/openapi-ruleset-utilities", 3 | "description": "Programmatic utility functions for creating Spectral-formatted OpenAPI Rulesets", 4 | "version": "1.9.0", 5 | "license": "Apache-2.0", 6 | "private": false, 7 | "main": "./src/index.js", 8 | "types": "./types/index.d.ts", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/IBM/openapi-validator.git", 12 | "directory": "packages/utilities" 13 | }, 14 | "scripts": { 15 | "clean": "rm -rf node_modules", 16 | "jest": "jest", 17 | "test": "jest test", 18 | "test-travis": "jest --silent --runInBand --no-colors --testNamePattern='^((?!@skip-ci).)*$' test", 19 | "lint": "eslint --cache --quiet --ext '.js' src test", 20 | "fix": "eslint --fix --ext '.js' src test", 21 | "generate-types": "tsc", 22 | "pkg": "echo no executables to build in this package" 23 | }, 24 | "devDependencies": { 25 | "@stoplight/spectral-core": "^1.19.4", 26 | "jest": "^29.7.0", 27 | "typescript": "^5.8.3" 28 | }, 29 | "engines": { 30 | "node": ">=16.0.0" 31 | }, 32 | "jest": { 33 | "collectCoverage": true, 34 | "coverageDirectory": "./coverage/", 35 | "testEnvironment": "node", 36 | "moduleNameMapper": { 37 | "nimma/legacy": "/../../node_modules/@stoplight/spectral-core/node_modules/nimma/dist/legacy/cjs", 38 | "nimma/fallbacks": "/../../node_modules/@stoplight/spectral-core/node_modules/nimma/dist/legacy/cjs/fallbacks/" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/utilities/src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | /** 7 | * This package provides utilities for working with OpenAPI 3.x definitions 8 | * in the context of a Spectral ruleset. 9 | * @module @ibm-cloud/openapi-ruleset-utilities 10 | */ 11 | module.exports = { 12 | ...require('./utils'), 13 | 14 | collections: { 15 | ...require('./collections'), 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /packages/utilities/src/utils/get-examples-for-schema.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | /** 7 | * @private 8 | */ 9 | const collectFromComposedSchemas = require('./collect-from-composed-schemas'); 10 | 11 | /** 12 | * Returns an array of examples for a simple or composite schema. For each composed schema, if 13 | * `schema.examples` is present (and an array), `schema.example` is ignored. 14 | * @param {object} schema simple or composite OpenAPI 3.x schema object 15 | * @returns {Array} examples 16 | */ 17 | function getExamplesForSchema(schema) { 18 | return collectFromComposedSchemas(schema, s => { 19 | if (Array.isArray(s.examples)) { 20 | return s.examples; 21 | } else if (s.example !== undefined) { 22 | return [s.example]; 23 | } 24 | 25 | return []; 26 | }); 27 | } 28 | 29 | module.exports = getExamplesForSchema; 30 | -------------------------------------------------------------------------------- /packages/utilities/src/utils/get-property-names-for-schema.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | /** 7 | * @private 8 | */ 9 | const isObject = require('./is-object'); 10 | /** 11 | * @private 12 | */ 13 | const collectFromComposedSchemas = require('./collect-from-composed-schemas'); 14 | 15 | /** 16 | * Returns an array of property names for a simple or composite schema, 17 | * optionally filtered by a lambda function. 18 | * @param {object} schema simple or composite OpenAPI 3.x schema object 19 | * @param {Function} propertyFilter a `(propertyName, propertySchema) => boolean` function to perform filtering 20 | * @returns {Array} property names 21 | */ 22 | function getPropertyNamesForSchema(schema, propertyFilter = () => true) { 23 | return collectFromComposedSchemas(schema, s => { 24 | const propertyNames = []; 25 | 26 | if (isObject(s.properties)) { 27 | for (const propertyName of Object.keys(s.properties)) { 28 | if (propertyFilter(propertyName, s.properties[propertyName])) { 29 | propertyNames.push(propertyName); 30 | } 31 | } 32 | } 33 | 34 | return propertyNames; 35 | }); 36 | } 37 | 38 | module.exports = getPropertyNamesForSchema; 39 | -------------------------------------------------------------------------------- /packages/utilities/src/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2025 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | module.exports = { 7 | collectFromComposedSchemas: require('./collect-from-composed-schemas'), 8 | getExamplesForSchema: require('./get-examples-for-schema'), 9 | getPropertyNamesForSchema: require('./get-property-names-for-schema'), 10 | ...require('./get-schema-type'), 11 | isObject: require('./is-object'), 12 | schemaHasConstraint: require('./schema-has-constraint'), 13 | schemaHasProperty: require('./schema-has-property'), 14 | schemaLooselyHasConstraint: require('./schema-loosely-has-constraint'), 15 | schemaRequiresProperty: require('./schema-requires-property'), 16 | ...require('./spectral-context-utils'), 17 | validateComposedSchemas: require('./validate-composed-schemas'), 18 | validateNestedSchemas: require('./validate-nested-schemas'), 19 | validateSubschemas: require('./validate-subschemas'), 20 | }; 21 | -------------------------------------------------------------------------------- /packages/utilities/src/utils/is-object.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | /** 7 | * Returns `true` if `thing` is a non-`null` object and not an array. 8 | * @param {any} thing input to check 9 | * @returns {boolean} 10 | */ 11 | function isObject(thing) { 12 | return typeof thing === 'object' && thing !== null && !Array.isArray(thing); 13 | } 14 | 15 | module.exports = isObject; 16 | -------------------------------------------------------------------------------- /packages/utilities/src/utils/schema-has-property.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | /** 7 | * @private 8 | */ 9 | const schemaHasConstraint = require('./schema-has-constraint'); 10 | /** 11 | * @private 12 | */ 13 | const isObject = require('./is-object'); 14 | 15 | /** 16 | * This function will return `true` if all possible variations of a (possibly composite) schema 17 | * define a property with the specified name. 18 | * @param {object} schema simple or composite OpenAPI 3.x schema object 19 | * @param {string} propertyName name of the object schema property to check for 20 | * @returns {boolean} 21 | */ 22 | function schemaHasProperty(schema, propertyName) { 23 | return schemaHasConstraint( 24 | schema, 25 | s => 'properties' in s && isObject(s.properties[propertyName]) 26 | ); 27 | } 28 | 29 | module.exports = schemaHasProperty; 30 | -------------------------------------------------------------------------------- /packages/utilities/src/utils/schema-loosely-has-constraint.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | /** 7 | * @private 8 | */ 9 | const isObject = require('./is-object'); 10 | 11 | /** 12 | * This function is a looser adaptation of the `schemaHasConstraint()` function. 13 | * Here, we process `oneOf` and `anyOf` lists the same as `allOf`, returning `true` if: 14 | * 15 | * - a schema has the constraint itself, or 16 | * - any of the schemas it composes with `allOf` has the constraint, or 17 | * - any of the schemas it composes with `oneOf` has the constraint, or 18 | * - any of the schemas it composes with `anyOf` has the constraint 19 | * @param {object} schema simple or composite OpenAPI 3.x schema object 20 | * @param {Function} hasConstraint a `(schema) => boolean` function to check for a constraint 21 | * @returns {boolean} 22 | */ 23 | function schemaLooselyHasConstraint(schema, hasConstraint) { 24 | if (!isObject(schema)) { 25 | return false; 26 | } 27 | 28 | if (hasConstraint(schema)) { 29 | return true; 30 | } 31 | 32 | const anySchemaHasConstraintReducer = (previousResult, currentSchema) => { 33 | return ( 34 | previousResult || schemaLooselyHasConstraint(currentSchema, hasConstraint) 35 | ); 36 | }; 37 | 38 | for (const applicator of ['allOf', 'oneOf', 'anyOf']) { 39 | if ( 40 | Array.isArray(schema[applicator]) && 41 | schema[applicator].length > 0 && 42 | schema[applicator].reduce(anySchemaHasConstraintReducer, false) 43 | ) { 44 | return true; 45 | } 46 | } 47 | 48 | return false; 49 | } 50 | 51 | module.exports = schemaLooselyHasConstraint; 52 | -------------------------------------------------------------------------------- /packages/utilities/src/utils/schema-path.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | // Necessary to get exceptions thrown for attempts to modify frozen objects 7 | 'use strict'; 8 | 9 | /** 10 | * @private 11 | */ 12 | class SchemaPath extends Array { 13 | constructor(physical, logical = []) { 14 | super(...physical); 15 | this.logical = Object.freeze([...logical]); 16 | Object.freeze(this); 17 | } 18 | 19 | withProperty(name) { 20 | return new SchemaPath( 21 | [...this, 'properties', name], 22 | [...this.logical, name] 23 | ); 24 | } 25 | 26 | withAdditionalProperty() { 27 | return new SchemaPath( 28 | [...this, 'additionalProperties'], 29 | [...this.logical, '*'] 30 | ); 31 | } 32 | 33 | withPatternProperty(pattern) { 34 | return new SchemaPath( 35 | [...this, 'patternProperties', pattern], 36 | [...this.logical, '*'] 37 | ); 38 | } 39 | 40 | withArrayItem() { 41 | return new SchemaPath([...this, 'items'], [...this.logical, '[]']); 42 | } 43 | 44 | withApplicator(applicator, index) { 45 | return new SchemaPath([...this, applicator, String(index)], this.logical); 46 | } 47 | 48 | withNot() { 49 | return new SchemaPath([...this, 'not'], this.logical); 50 | } 51 | } 52 | 53 | module.exports = SchemaPath; 54 | -------------------------------------------------------------------------------- /packages/utilities/src/utils/schema-requires-property.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | /** 7 | * @private 8 | */ 9 | const schemaHasConstraint = require('./schema-has-constraint'); 10 | /** 11 | * @private 12 | */ 13 | const schemaHasProperty = require('./schema-has-property'); 14 | 15 | /** 16 | * This function will return `true` if all possible variations of a (possibly composite) schema 17 | * require a property with the specified name. Note that this method may not behave as expected 18 | * for OpenAPI documents where a `required` property is not defined under the `properties` keyword. 19 | * @param {object} schema simple or composite OpenAPI 3.x schema object 20 | * @param {object} propertyName name of the object schema property to check for 21 | * @returns {boolean} 22 | */ 23 | function schemaRequiresProperty(schema, propertyName) { 24 | return ( 25 | schemaHasConstraint( 26 | schema, 27 | s => Array.isArray(s.required) && s.required.includes(propertyName) 28 | ) && schemaHasProperty(schema, propertyName) 29 | ); 30 | } 31 | 32 | module.exports = schemaRequiresProperty; 33 | -------------------------------------------------------------------------------- /packages/utilities/src/utils/spectral-context-utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | /** 7 | * Returns the programmatic representation of an OpenAPI document, stored in the 8 | * Spectral-created "context" object, with all non-circular references resolved. 9 | * @param {object} context passed as an argument to Spectral-based rule functions 10 | * @returns {object} the resolved version of an OpenAPI document 11 | */ 12 | function getResolvedSpec(context) { 13 | return context.documentInventory.resolved; 14 | } 15 | 16 | /** 17 | * Returns the programmatic representation of an OpenAPI document, stored in 18 | * the Spectral-created "context" object, with all references still intact. 19 | * @param {object} context passed as an argument to Spectral-based rule functions 20 | * @returns {object} the unresolved version of an OpenAPI document 21 | */ 22 | function getUnresolvedSpec(context) { 23 | return context.document.parserResult.data; 24 | } 25 | 26 | /** 27 | * Returns the graph nodes, with information about references and the locations 28 | * they resolve to, that are computed by the Spectral resolver. 29 | * @param {object} context passed as an argument to Spectral-based rule functions 30 | * @returns {object} the graph nodes 31 | */ 32 | function getNodes(context) { 33 | return context.documentInventory.graph.nodes; 34 | } 35 | 36 | module.exports = { 37 | getNodes, 38 | getResolvedSpec, 39 | getUnresolvedSpec, 40 | }; 41 | -------------------------------------------------------------------------------- /packages/utilities/test/is-boolean-schema.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { isBooleanSchema } = require('../src'); 7 | 8 | describe('Utility function: isBooleanSchema()', () => { 9 | it('should return `false` for `undefined`', async () => { 10 | expect(isBooleanSchema(undefined)).toBe(false); 11 | }); 12 | 13 | it('should return `false` for `null`', async () => { 14 | expect(isBooleanSchema(null)).toBe(false); 15 | }); 16 | 17 | it('should return `false` for an array', async () => { 18 | expect(isBooleanSchema([])).toBe(false); 19 | }); 20 | 21 | it('should return `false` for an empty object', async () => { 22 | expect(isBooleanSchema({})).toBe(false); 23 | }); 24 | 25 | it('should return `true` for an object with `type` of "boolean"', async () => { 26 | expect(isBooleanSchema({ type: 'boolean' })).toBe(true); 27 | }); 28 | 29 | // Skipped: debatable whether this test ought to pass, but maybe for OAS 3.1 support 30 | it.skip('should return `true` for an object with `type` of ["boolean"]', async () => { 31 | expect(isBooleanSchema({ type: ['boolean'] })).toBe(true); 32 | }); 33 | 34 | it('should recurse through `oneOf` and `allOf` and `anyOf`', async () => { 35 | expect( 36 | isBooleanSchema({ 37 | oneOf: [ 38 | { 39 | allOf: [{ anyOf: [{ type: 'boolean' }, { type: 'boolean' }] }, {}], 40 | }, 41 | { type: 'boolean' }, 42 | ], 43 | }) 44 | ).toBe(true); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/utilities/test/is-integer-schema.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { isIntegerSchema } = require('../src'); 7 | 8 | describe('Utility function: isIntegerSchema()', () => { 9 | it('should return `false` for `undefined`', async () => { 10 | expect(isIntegerSchema(undefined)).toBe(false); 11 | }); 12 | 13 | it('should return `false` for `null`', async () => { 14 | expect(isIntegerSchema(null)).toBe(false); 15 | }); 16 | 17 | it('should return `false` for an array', async () => { 18 | expect(isIntegerSchema([])).toBe(false); 19 | }); 20 | 21 | it('should return `false` for an empty object', async () => { 22 | expect(isIntegerSchema({})).toBe(false); 23 | }); 24 | 25 | it('should return `true` for an object with `type` of "integer"', async () => { 26 | expect(isIntegerSchema({ type: 'integer' })).toBe(true); 27 | }); 28 | 29 | // Skipped: debatable whether this test ought to pass, but maybe for OAS 3.1 support 30 | it.skip('should return `true` for an object with `type` of ["integer"]', async () => { 31 | expect(isIntegerSchema({ type: ['integer'] })).toBe(true); 32 | }); 33 | 34 | it('should recurse through `oneOf` and `allOf` and `anyOf`', async () => { 35 | expect( 36 | isIntegerSchema({ 37 | oneOf: [ 38 | { 39 | allOf: [{ anyOf: [{ type: 'integer' }, { type: 'integer' }] }, {}], 40 | }, 41 | { type: 'integer' }, 42 | ], 43 | }) 44 | ).toBe(true); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/utilities/test/is-number-schema.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { isNumberSchema } = require('../src'); 7 | 8 | describe('Utility function: isNumberSchema()', () => { 9 | it('should return `false` for `undefined`', async () => { 10 | expect(isNumberSchema(undefined)).toBe(false); 11 | }); 12 | 13 | it('should return `false` for `null`', async () => { 14 | expect(isNumberSchema(null)).toBe(false); 15 | }); 16 | 17 | it('should return `false` for an array', async () => { 18 | expect(isNumberSchema([])).toBe(false); 19 | }); 20 | 21 | it('should return `false` for an empty object', async () => { 22 | expect(isNumberSchema({})).toBe(false); 23 | }); 24 | 25 | it('should return `true` for an object with `type` of "number"', async () => { 26 | expect(isNumberSchema({ type: 'number' })).toBe(true); 27 | }); 28 | 29 | // Skipped: debatable whether this test ought to pass, but maybe for OAS 3.1 support 30 | it.skip('should return `true` for an object with `type` of ["number"]', async () => { 31 | expect(isNumberSchema({ type: ['number'] })).toBe(true); 32 | }); 33 | 34 | it('should recurse through `oneOf` and `allOf` and `anyOf`', async () => { 35 | expect( 36 | isNumberSchema({ 37 | oneOf: [ 38 | { 39 | allOf: [{ anyOf: [{ type: 'number' }, { type: 'number' }] }, {}], 40 | }, 41 | { type: 'number' }, 42 | ], 43 | }) 44 | ).toBe(true); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/utilities/test/is-object.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { isObject } = require('../src'); 7 | 8 | describe('Utility function: isObject()', () => { 9 | it('should return `false` for `undefined`', async () => { 10 | expect(isObject(undefined)).toBe(false); 11 | }); 12 | 13 | it('should return `false` for `null`', async () => { 14 | expect(isObject(null)).toBe(false); 15 | }); 16 | 17 | it('should return `false` for an array', async () => { 18 | expect(isObject([])).toBe(false); 19 | }); 20 | 21 | it('should return `true` for a non-`null`, non-array object', async () => { 22 | expect(isObject({})).toBe(true); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/utilities/test/is-string-schema.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { isStringSchema } = require('../src'); 7 | 8 | describe('Utility function: isStringSchema()', () => { 9 | it('should return `false` for `undefined`', async () => { 10 | expect(isStringSchema(undefined)).toBe(false); 11 | }); 12 | 13 | it('should return `false` for `null`', async () => { 14 | expect(isStringSchema(null)).toBe(false); 15 | }); 16 | 17 | it('should return `false` for an array', async () => { 18 | expect(isStringSchema([])).toBe(false); 19 | }); 20 | 21 | it('should return `false` for an empty object', async () => { 22 | expect(isStringSchema({})).toBe(false); 23 | }); 24 | 25 | it('should return `true` for an object with `type` of "string"', async () => { 26 | expect(isStringSchema({ type: 'string' })).toBe(true); 27 | }); 28 | 29 | // Skipped: debatable whether this test ought to pass, but maybe for OAS 3.1 support 30 | it.skip('should return `true` for an object with `type` of ["string"]', async () => { 31 | expect(isStringSchema({ type: ['string'] })).toBe(true); 32 | }); 33 | 34 | it('should recurse through `oneOf` and `allOf` and `anyOf`', async () => { 35 | expect( 36 | isStringSchema({ 37 | oneOf: [ 38 | { 39 | allOf: [{ anyOf: [{ type: 'string' }, { type: 'string' }] }, {}], 40 | }, 41 | { type: 'string' }, 42 | ], 43 | }) 44 | ).toBe(true); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /packages/utilities/test/spectral-context-utils.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { getNodes, getResolvedSpec, getUnresolvedSpec } = require('../src'); 7 | 8 | describe('Utility functions: Spectral Context Utils', () => { 9 | const mockSpectralContextObject = { 10 | documentInventory: { 11 | resolved: 'resolved spec', 12 | graph: { 13 | nodes: 'graph nodes', 14 | }, 15 | }, 16 | document: { 17 | parserResult: { 18 | data: 'unresolved spec', 19 | }, 20 | }, 21 | }; 22 | 23 | describe('getNodes', () => { 24 | it('should return graph nodes from context object', async () => { 25 | expect(getNodes(mockSpectralContextObject)).toBe('graph nodes'); 26 | }); 27 | }); 28 | 29 | describe('getResolvedSpec', () => { 30 | it('should return graph nodes from context object', async () => { 31 | expect(getResolvedSpec(mockSpectralContextObject)).toBe('resolved spec'); 32 | }); 33 | }); 34 | 35 | describe('getUnresolvedSpec', () => { 36 | it('should return graph nodes from context object', async () => { 37 | expect(getUnresolvedSpec(mockSpectralContextObject)).toBe( 38 | 'unresolved spec' 39 | ); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/utilities/test/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | // NOTE: Duplicated from ruleset package. Need to revisit 7 | const allSchemasDocument = require('./all-schemas-document'); 8 | const testRule = require('./test-rule'); 9 | 10 | module.exports = { 11 | allSchemasDocument, 12 | testRule, 13 | }; 14 | -------------------------------------------------------------------------------- /packages/utilities/test/utils/test-rule-paths.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const testRule = require('./test-rule'); 7 | 8 | module.exports = async (given, doc) => { 9 | const visitedPaths = []; 10 | const rule = { 11 | given, 12 | then: { 13 | function: (input, options, context) => { 14 | visitedPaths.push(context.path); 15 | return []; 16 | }, 17 | }, 18 | }; 19 | 20 | await testRule(rule, doc); 21 | 22 | return visitedPaths; 23 | }; 24 | -------------------------------------------------------------------------------- /packages/utilities/test/utils/test-rule.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | // NOTE: Duplicated from ruleset package. Need to revisit 7 | const { Spectral } = require('@stoplight/spectral-core'); 8 | 9 | module.exports = async (rule, doc) => { 10 | const spectral = new Spectral(); 11 | 12 | spectral.setRuleset({ 13 | rules: { 14 | 'mock-rule': rule, 15 | }, 16 | }); 17 | 18 | return await spectral.run(doc); 19 | }; 20 | -------------------------------------------------------------------------------- /packages/utilities/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // Used to auto-generate TS types for this utilities package, 3 | // so that it can be imported into other rulesets developed in TS. 4 | 5 | "include": ["src/**/*"], 6 | "compilerOptions": { 7 | // Tells TypeScript to read JS files, as 8 | // normally they are ignored as source files. 9 | "allowJs": true, 10 | // Generate d.ts files. 11 | "declaration": true, 12 | // This compiler run should 13 | // only output d.ts files. 14 | "emitDeclarationOnly": true, 15 | // Types should go into this directory. 16 | // Removing this would place the .d.ts files 17 | // next to the .js files. 18 | "outDir": "types", 19 | // Go to js file when using IDE functions like 20 | // "Go to Definition" in VSCode. 21 | "declarationMap": true 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/utilities/types/collections/index.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | export const operations: string[]; 6 | export const requestBodySchemas: string[]; 7 | export const responseSchemas: string[]; 8 | export const unresolvedRequestBodySchemas: string[]; 9 | export const unresolvedResponseSchemas: string[]; 10 | export declare let parameters: string[]; 11 | export declare let patchOperations: string[]; 12 | export declare let paths: string[]; 13 | export declare let schemas: string[]; 14 | export declare let securitySchemes: string[]; 15 | export declare let unresolvedSchemas: string[]; 16 | //# sourceMappingURL=index.d.ts.map -------------------------------------------------------------------------------- /packages/utilities/types/collections/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/collections/index.js"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,kCAAgF;AAEhF,0CAA+E;AAE/E,uCAA6E;AAE7E,oDAGE;AAEF,iDAGE"} -------------------------------------------------------------------------------- /packages/utilities/types/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.js"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /packages/utilities/types/utils/collect-from-composed-schemas.d.ts: -------------------------------------------------------------------------------- 1 | export = collectFromComposedSchemas; 2 | /** 3 | * Copyright 2024 IBM Corporation. 4 | * SPDX-License-Identifier: Apache2.0 5 | */ 6 | /** 7 | * Returns an array of items collected by the provided `collector(schema) => item[]` function for a 8 | * simple or composite schema, and deduplicates primitives in the result. The collector function is 9 | * not run for `null` or `undefined` schemas. 10 | * @param {object} schema simple or composite OpenAPI 3.x schema object 11 | * @param {Function} collector a `(schema) => item[]` function to collect items from each simple schema 12 | * @param {boolean} includeSelf collect from the provided schema in addition to its composed schemas (defaults to `true`) 13 | * @param {boolean} includeNot collect from schemas composed with `not` (defaults to `false`) 14 | * @returns {Array} collected items 15 | */ 16 | declare function collectFromComposedSchemas(schema: object, collector: Function, includeSelf?: boolean, includeNot?: boolean): any[]; 17 | //# sourceMappingURL=collect-from-composed-schemas.d.ts.map -------------------------------------------------------------------------------- /packages/utilities/types/utils/collect-from-composed-schemas.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"collect-from-composed-schemas.d.ts","sourceRoot":"","sources":["../../src/utils/collect-from-composed-schemas.js"],"names":[],"mappings":";AAAA;;;GAGG;AAEH;;;;;;;;;GASG;AACH,oDANW,MAAM,qCAEN,OAAO,eACP,OAAO,SAyCjB"} -------------------------------------------------------------------------------- /packages/utilities/types/utils/get-examples-for-schema.d.ts: -------------------------------------------------------------------------------- 1 | export = getExamplesForSchema; 2 | /** 3 | * Returns an array of examples for a simple or composite schema. For each composed schema, if 4 | * `schema.examples` is present (and an array), `schema.example` is ignored. 5 | * @param {object} schema simple or composite OpenAPI 3.x schema object 6 | * @returns {Array} examples 7 | */ 8 | declare function getExamplesForSchema(schema: object): any[]; 9 | //# sourceMappingURL=get-examples-for-schema.d.ts.map -------------------------------------------------------------------------------- /packages/utilities/types/utils/get-examples-for-schema.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"get-examples-for-schema.d.ts","sourceRoot":"","sources":["../../src/utils/get-examples-for-schema.js"],"names":[],"mappings":";AAUA;;;;;GAKG;AACH,8CAHW,MAAM,SAahB"} -------------------------------------------------------------------------------- /packages/utilities/types/utils/get-property-names-for-schema.d.ts: -------------------------------------------------------------------------------- 1 | export = getPropertyNamesForSchema; 2 | /** 3 | * Returns an array of property names for a simple or composite schema, 4 | * optionally filtered by a lambda function. 5 | * @param {object} schema simple or composite OpenAPI 3.x schema object 6 | * @param {Function} propertyFilter a `(propertyName, propertySchema) => boolean` function to perform filtering 7 | * @returns {Array} property names 8 | */ 9 | declare function getPropertyNamesForSchema(schema: object, propertyFilter?: Function): any[]; 10 | //# sourceMappingURL=get-property-names-for-schema.d.ts.map -------------------------------------------------------------------------------- /packages/utilities/types/utils/get-property-names-for-schema.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"get-property-names-for-schema.d.ts","sourceRoot":"","sources":["../../src/utils/get-property-names-for-schema.js"],"names":[],"mappings":";AAcA;;;;;;GAMG;AACH,mDAJW,MAAM,oCAkBhB"} -------------------------------------------------------------------------------- /packages/utilities/types/utils/get-schema-type.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"get-schema-type.d.ts","sourceRoot":"","sources":["../../src/utils/get-schema-type.js"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AA0FA;;;;;;;;;;;GAWG;AACH,sCAHW,MAAM,GACJ,MAAM,CA2ClB;AAoCD;;;;GAIG;AACH,sCAHW,MAAM,GACJ,OAAO,CAMnB;AAED;;;;GAIG;AACH,uCAHW,MAAM,GACJ,OAAO,CAOnB;AAED;;;;GAIG;AACH,wCAHW,MAAM,GACJ,OAAO,CAInB;AAED;;;;GAIG;AACH,qCAHW,MAAM,GACJ,OAAO,CAOnB;AAED;;;;GAIG;AACH,qCAHW,MAAM,GACJ,OAAO,CAOnB;AAED;;;;GAIG;AACH,yCAHW,MAAM,GACJ,OAAO,CAOnB;AAED;;;;GAIG;AACH,uCAHW,MAAM,GACJ,OAAO,CAQnB;AAED;;;;GAIG;AACH,4CAHW,MAAM,GACJ,OAAO,CAOnB;AAED;;;;GAIG;AACH,sCAHW,MAAM,GACJ,OAAO,CAQnB;AAED;;;;GAIG;AACH,sCAHW,MAAM,GACJ,OAAO,CAOnB;AAED;;;;GAIG;AACH,sCAHW,MAAM,GACJ,OAAO,CAOnB;AAED;;;;GAIG;AACH,wCAHW,MAAM,GACJ,OAAO,CAInB;AAED;;;;GAIG;AACH,uCAHW,MAAM,GACJ,OAAO,CAInB;AAED;;;;GAIG;AACH,uCAHW,MAAM,GACJ,OAAO,CAanB;AAWD;;;;GAIG;AACH,0CAHW,MAAM,GACJ,OAAO,CAUnB;AAtBD;;;;GAIG;AACH,uCAHW,MAAM,GACJ,OAAO,CAInB;AA/MD;;;;;;;GAOG;AACH,uCAJW,MAAM,QACN,MAAM,GACJ,OAAO,CAQnB"} -------------------------------------------------------------------------------- /packages/utilities/types/utils/index.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.js"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /packages/utilities/types/utils/is-object.d.ts: -------------------------------------------------------------------------------- 1 | export = isObject; 2 | /** 3 | * Copyright 2017 - 2023 IBM Corporation. 4 | * SPDX-License-Identifier: Apache2.0 5 | */ 6 | /** 7 | * Returns `true` if `thing` is a non-`null` object and not an array. 8 | * @param {any} thing input to check 9 | * @returns {boolean} 10 | */ 11 | declare function isObject(thing: any): boolean; 12 | //# sourceMappingURL=is-object.d.ts.map -------------------------------------------------------------------------------- /packages/utilities/types/utils/is-object.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"is-object.d.ts","sourceRoot":"","sources":["../../src/utils/is-object.js"],"names":[],"mappings":";AAAA;;;GAGG;AAEH;;;;GAIG;AACH,iCAHW,GAAG,GACD,OAAO,CAInB"} -------------------------------------------------------------------------------- /packages/utilities/types/utils/schema-has-constraint.d.ts: -------------------------------------------------------------------------------- 1 | export = schemaHasConstraint; 2 | /** 3 | * This function will return `true` if all possible variations of a (possibly composite) schema 4 | * enforce a constraint, as checked by a `(schema) => boolean` function which checks for the 5 | * constraint on a simple (non-composite) schema. It does this by recursively checking if: 6 | * 7 | * - a schema has the constraint itself, or 8 | * - any of the schemas it composes with `allOf` has the constraint, or 9 | * - all of the schemas it composes with `oneOf` have the constraint, or 10 | * - all of the schemas it composes with `anyOf` have the constraint 11 | * @param {object} schema simple or composite OpenAPI 3.x schema object 12 | * @param {Function} hasConstraint a `(schema) => boolean` function to check for a constraint 13 | * @returns {boolean} 14 | */ 15 | declare function schemaHasConstraint(schema: object, hasConstraint: Function): boolean; 16 | //# sourceMappingURL=schema-has-constraint.d.ts.map -------------------------------------------------------------------------------- /packages/utilities/types/utils/schema-has-constraint.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"schema-has-constraint.d.ts","sourceRoot":"","sources":["../../src/utils/schema-has-constraint.js"],"names":[],"mappings":";AAUA;;;;;;;;;;;;GAYG;AACH,6CAJW,MAAM,4BAEJ,OAAO,CA8CnB"} -------------------------------------------------------------------------------- /packages/utilities/types/utils/schema-has-property.d.ts: -------------------------------------------------------------------------------- 1 | export = schemaHasProperty; 2 | /** 3 | * This function will return `true` if all possible variations of a (possibly composite) schema 4 | * define a property with the specified name. 5 | * @param {object} schema simple or composite OpenAPI 3.x schema object 6 | * @param {string} propertyName name of the object schema property to check for 7 | * @returns {boolean} 8 | */ 9 | declare function schemaHasProperty(schema: object, propertyName: string): boolean; 10 | //# sourceMappingURL=schema-has-property.d.ts.map -------------------------------------------------------------------------------- /packages/utilities/types/utils/schema-has-property.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"schema-has-property.d.ts","sourceRoot":"","sources":["../../src/utils/schema-has-property.js"],"names":[],"mappings":";AAcA;;;;;;GAMG;AACH,2CAJW,MAAM,gBACN,MAAM,GACJ,OAAO,CAOnB"} -------------------------------------------------------------------------------- /packages/utilities/types/utils/schema-loosely-has-constraint.d.ts: -------------------------------------------------------------------------------- 1 | export = schemaLooselyHasConstraint; 2 | /** 3 | * This function is a looser adaptation of the `schemaHasConstraint()` function. 4 | * Here, we process `oneOf` and `anyOf` lists the same as `allOf`, returning `true` if: 5 | * 6 | * - a schema has the constraint itself, or 7 | * - any of the schemas it composes with `allOf` has the constraint, or 8 | * - any of the schemas it composes with `oneOf` has the constraint, or 9 | * - any of the schemas it composes with `anyOf` has the constraint 10 | * @param {object} schema simple or composite OpenAPI 3.x schema object 11 | * @param {Function} hasConstraint a `(schema) => boolean` function to check for a constraint 12 | * @returns {boolean} 13 | */ 14 | declare function schemaLooselyHasConstraint(schema: object, hasConstraint: Function): boolean; 15 | //# sourceMappingURL=schema-loosely-has-constraint.d.ts.map -------------------------------------------------------------------------------- /packages/utilities/types/utils/schema-loosely-has-constraint.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"schema-loosely-has-constraint.d.ts","sourceRoot":"","sources":["../../src/utils/schema-loosely-has-constraint.js"],"names":[],"mappings":";AAUA;;;;;;;;;;;GAWG;AACH,oDAJW,MAAM,4BAEJ,OAAO,CA4BnB"} -------------------------------------------------------------------------------- /packages/utilities/types/utils/schema-path.d.ts: -------------------------------------------------------------------------------- 1 | export = SchemaPath; 2 | /** 3 | * @private 4 | */ 5 | declare class SchemaPath extends Array { 6 | constructor(physical: any, logical?: any[]); 7 | logical: readonly any[]; 8 | withProperty(name: any): SchemaPath; 9 | withAdditionalProperty(): SchemaPath; 10 | withPatternProperty(pattern: any): SchemaPath; 11 | withArrayItem(): SchemaPath; 12 | withApplicator(applicator: any, index: any): SchemaPath; 13 | withNot(): SchemaPath; 14 | } 15 | //# sourceMappingURL=schema-path.d.ts.map -------------------------------------------------------------------------------- /packages/utilities/types/utils/schema-path.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"schema-path.d.ts","sourceRoot":"","sources":["../../src/utils/schema-path.js"],"names":[],"mappings":";AAQA;;GAEG;AACH;IACE,4CAIC;IAFC,wBAA0C;IAI5C,oCAKC;IAED,qCAKC;IAED,8CAKC;IAED,4BAEC;IAED,wDAEC;IAED,sBAEC;CACF"} -------------------------------------------------------------------------------- /packages/utilities/types/utils/schema-requires-property.d.ts: -------------------------------------------------------------------------------- 1 | export = schemaRequiresProperty; 2 | /** 3 | * This function will return `true` if all possible variations of a (possibly composite) schema 4 | * require a property with the specified name. Note that this method may not behave as expected 5 | * for OpenAPI documents where a `required` property is not defined under the `properties` keyword. 6 | * @param {object} schema simple or composite OpenAPI 3.x schema object 7 | * @param {object} propertyName name of the object schema property to check for 8 | * @returns {boolean} 9 | */ 10 | declare function schemaRequiresProperty(schema: object, propertyName: object): boolean; 11 | //# sourceMappingURL=schema-requires-property.d.ts.map -------------------------------------------------------------------------------- /packages/utilities/types/utils/schema-requires-property.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"schema-requires-property.d.ts","sourceRoot":"","sources":["../../src/utils/schema-requires-property.js"],"names":[],"mappings":";AAcA;;;;;;;GAOG;AACH,gDAJW,MAAM,gBACN,MAAM,GACJ,OAAO,CASnB"} -------------------------------------------------------------------------------- /packages/utilities/types/utils/spectral-context-utils.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns the graph nodes, with information about references and the locations 3 | * they resolve to, that are computed by the Spectral resolver. 4 | * @param {object} context passed as an argument to Spectral-based rule functions 5 | * @returns {object} the graph nodes 6 | */ 7 | export function getNodes(context: object): object; 8 | /** 9 | * Copyright 2025 IBM Corporation. 10 | * SPDX-License-Identifier: Apache2.0 11 | */ 12 | /** 13 | * Returns the programmatic representation of an OpenAPI document, stored in the 14 | * Spectral-created "context" object, with all non-circular references resolved. 15 | * @param {object} context passed as an argument to Spectral-based rule functions 16 | * @returns {object} the resolved version of an OpenAPI document 17 | */ 18 | export function getResolvedSpec(context: object): object; 19 | /** 20 | * Returns the programmatic representation of an OpenAPI document, stored in 21 | * the Spectral-created "context" object, with all references still intact. 22 | * @param {object} context passed as an argument to Spectral-based rule functions 23 | * @returns {object} the unresolved version of an OpenAPI document 24 | */ 25 | export function getUnresolvedSpec(context: object): object; 26 | //# sourceMappingURL=spectral-context-utils.d.ts.map -------------------------------------------------------------------------------- /packages/utilities/types/utils/spectral-context-utils.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"spectral-context-utils.d.ts","sourceRoot":"","sources":["../../src/utils/spectral-context-utils.js"],"names":[],"mappings":"AAyBA;;;;;GAKG;AACH,kCAHW,MAAM,GACJ,MAAM,CAIlB;AAjCD;;;GAGG;AAEH;;;;;GAKG;AACH,yCAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;;;;GAKG;AACH,2CAHW,MAAM,GACJ,MAAM,CAIlB"} -------------------------------------------------------------------------------- /packages/utilities/types/utils/validate-composed-schemas.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"validate-composed-schemas.d.ts","sourceRoot":"","sources":["../../src/utils/validate-composed-schemas.js"],"names":[],"mappings":";AAUA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,iDAPW,MAAM,4CAGN,OAAO,eACP,OAAO,SAgDjB"} -------------------------------------------------------------------------------- /packages/utilities/types/utils/validate-nested-schemas.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"validate-nested-schemas.d.ts","sourceRoot":"","sources":["../../src/utils/validate-nested-schemas.js"],"names":[],"mappings":";AAcA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,+CAPW,MAAM,4CAGN,OAAO,eACP,OAAO,SAqHjB"} -------------------------------------------------------------------------------- /packages/utilities/types/utils/validate-subschemas.d.ts.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"validate-subschemas.d.ts","sourceRoot":"","sources":["../../src/utils/validate-subschemas.js"],"names":[],"mappings":";AAkBA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,4CAPW,MAAM,iDAGN,OAAO,eACP,OAAO,SAwBjB"} -------------------------------------------------------------------------------- /packages/validator/.npmignore: -------------------------------------------------------------------------------- 1 | .eslintcache 2 | coverage/ 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /packages/validator/.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": "main", 3 | "debug": true, 4 | "plugins": [ 5 | "@semantic-release/commit-analyzer", 6 | "@semantic-release/release-notes-generator", 7 | "@semantic-release/changelog", 8 | "@semantic-release/npm", 9 | "@semantic-release/git", 10 | [ 11 | "@semantic-release/exec", 12 | { 13 | "publishCmd": "npm run pkg" 14 | } 15 | ], 16 | [ 17 | "@semantic-release/github", 18 | { 19 | "assets": [ 20 | "bin/*" 21 | ] 22 | } 23 | ] 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /packages/validator/src/cli-validator/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /** 3 | * Copyright 2017 - 2023 IBM Corporation. 4 | * SPDX-License-Identifier: Apache2.0 5 | */ 6 | 7 | // this module enforces that the user is running a supported version 8 | // of Node by exiting the process if the version is less than 9 | // the passed in argument (currently 16.0.0) 10 | require('./utils/check-version')('16.0.0'); 11 | 12 | const runValidator = require('./run-validator'); 13 | runValidator(process.argv) 14 | .then(exitCode => { 15 | process.exitCode = exitCode; 16 | return exitCode; 17 | }) 18 | .catch(err => { 19 | // if err is 2, it is because the message was caught 20 | // and printed already 21 | if (err !== 2) { 22 | console.log(err); 23 | } 24 | process.exitCode = 2; 25 | return 2; 26 | }); 27 | 28 | // 29 | // exitCode/err guide: 30 | // 31 | // exitCode 32 | // 0: the validator finished and passed with no errors/warnings 33 | // 1: the validator finished but there were errors or warnings 34 | // in the Swagger file 35 | // err 36 | // 2: the program encountered an error that prevented 37 | // the validator from running on all the files 38 | -------------------------------------------------------------------------------- /packages/validator/src/cli-validator/utils/check-ruleset-version.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const semver = require('semver'); 7 | const getDefaultRulesetVersion = require('./get-default-ruleset-version'); 8 | 9 | module.exports = checkRulesetVersion; 10 | 11 | /** 12 | * Checks the locally installed version of the IBM Cloud OpenAPI 13 | * Ruleset (if there is one) against what the default version 14 | * would be for the current version of the validator tool. 15 | * 16 | * If the installed version is older than the default version that 17 | * comes with the tool, this function returns a warning alerting 18 | * the user to this fact. We do this because it is possible for a 19 | * user to continue updating their validator tool version but never 20 | * seeing the new behavior from later versions of the ruleset because 21 | * they've fixed their ruleset version locally. This happens silently, 22 | * so the user may end up in this scenario without being aware of it. 23 | * 24 | * @param string local - the semantic version of the locally installed ruleset 25 | * @returns string|undefined - the warning message, if relevant 26 | */ 27 | function checkRulesetVersion(local) { 28 | if (!local || typeof local !== 'string') { 29 | return; 30 | } 31 | 32 | const defaultVersion = getDefaultRulesetVersion(); 33 | if (semver.lt(local, defaultVersion)) { 34 | return `Note: local version of the IBM OpenAPI Ruleset is behind the default version, which is ${defaultVersion}.`; 35 | } 36 | 37 | return; 38 | } 39 | -------------------------------------------------------------------------------- /packages/validator/src/cli-validator/utils/check-version.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const semver = require('semver'); 7 | const chalk = require('chalk'); 8 | 9 | // this module can be used to handle any version-specific functionality 10 | // it will be called immediately when the program is run 11 | 12 | module.exports = function (requiredVersion) { 13 | // this is called since the code uses features that require `requiredVersion` 14 | const isSupportedVersion = semver.gte(process.version, requiredVersion); 15 | if (!isSupportedVersion) { 16 | console.log( 17 | '\n' + 18 | chalk.red('[Error]') + 19 | ` Node version must be ${requiredVersion} or above.` + 20 | ` Your current version is ${process.version}\n` 21 | ); 22 | process.exit(2); 23 | } 24 | 25 | // print deprecation warnings for specific node versions that will no longer be supported 26 | const isNodeTen = semver.satisfies(process.version, '10.x'); 27 | if (isNodeTen) { 28 | console.log( 29 | '\n' + 30 | chalk.yellow('[Warning]') + 31 | ` Support for Node 10.x is deprecated. Support will be officially dropped when it reaches end of life` + 32 | ` (30 April 2021).\n` 33 | ); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /packages/validator/src/cli-validator/utils/file-extension-validator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const last = require('lodash/last'); 7 | 8 | const getExtension = filename => { 9 | return last(filename.split('.')).toLowerCase(); 10 | }; 11 | 12 | const validateExtension = (filename, supportedFileTypes) => { 13 | const fileExtension = getExtension(filename); 14 | const hasExtension = filename.includes('.'); 15 | const goodExtension = 16 | hasExtension && supportedFileTypes.includes(fileExtension); 17 | 18 | return goodExtension; 19 | }; 20 | 21 | module.exports = { 22 | supportedFileExtension: validateExtension, 23 | getFileExtension: getExtension, 24 | }; 25 | -------------------------------------------------------------------------------- /packages/validator/src/cli-validator/utils/get-copyright-string.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2025 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const getVersionString = require('./get-version-string'); 7 | 8 | module.exports = function () { 9 | return `IBM OpenAPI Validator (${getVersionString()}), @Copyright IBM Corporation 2017, ${new Date().getFullYear()}.`; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/validator/src/cli-validator/utils/get-default-ruleset-version.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const packageConfig = require('../../../package.json'); 7 | 8 | module.exports = getDefaultRulesetVersion; 9 | 10 | /** 11 | * Looks at the validator tool's declared dependencies and 12 | * returns the semantic version of the IBM Cloud OpenAPI 13 | * Ruleset package that ships by default with the current 14 | * version of the validator tool. 15 | * 16 | * @returns string - the default ruleset version 17 | */ 18 | function getDefaultRulesetVersion() { 19 | return packageConfig.dependencies['@ibm-cloud/openapi-ruleset']; 20 | } 21 | -------------------------------------------------------------------------------- /packages/validator/src/cli-validator/utils/get-version-string.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const packageConfig = require('../../../package.json'); 7 | 8 | module.exports = function () { 9 | return `validator: ${packageConfig.version}`; 10 | }; 11 | -------------------------------------------------------------------------------- /packages/validator/src/cli-validator/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | module.exports = { 7 | checkRulesetVersion: require('./check-ruleset-version'), 8 | createCLIOptions: require('./cli-options'), 9 | getCopyrightString: require('./get-copyright-string'), 10 | getDefaultRulesetVersion: require('./get-default-ruleset-version'), 11 | getLocalRulesetVersion: require('./get-local-ruleset-version'), 12 | getVersionString: require('./get-version-string'), 13 | preprocessFile: require('./preprocess-file'), 14 | printJson: require('./print-json'), 15 | printResults: require('./print-results'), 16 | printVersions: require('./print-versions'), 17 | readYaml: require('./read-yaml'), 18 | validateSchema: require('./validate-schema'), 19 | ...require('./configuration-manager'), 20 | ...require('./file-extension-validator'), 21 | }; 22 | -------------------------------------------------------------------------------- /packages/validator/src/cli-validator/utils/preprocess-file.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | module.exports = function (originalFile) { 7 | let processedFile; 8 | 9 | // replace all tabs characters (\t) in the original file with 2 spaces 10 | // for whatever reason, the `getLineNumberForPath` module will crash if 11 | // the swagger contains any tab characters 12 | const tabExpression = /\t/g; 13 | const twoSpaces = ' '; 14 | processedFile = originalFile.replace(tabExpression, twoSpaces); 15 | 16 | // sanitize all instances of a solidus preceded by 1 or more escape characters 17 | // the `yaml-js` package crashes if there is an escaped solidus 18 | const escapedSolidus = /\\+\//g; 19 | const solidus = '/'; 20 | processedFile = processedFile.replace(escapedSolidus, solidus); 21 | 22 | // Another problematic character is #9d - replace with space 23 | processedFile = processedFile.replace(/\x9d/g, ' '); 24 | 25 | return processedFile; 26 | }; 27 | -------------------------------------------------------------------------------- /packages/validator/src/cli-validator/utils/print-json.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | function printJson(context, results) { 7 | console.log(JSON.stringify(results, null, 2)); 8 | } 9 | 10 | module.exports = printJson; 11 | -------------------------------------------------------------------------------- /packages/validator/src/cli-validator/utils/read-yaml.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const fs = require('fs'); 7 | const util = require('util'); 8 | const jsYaml = require('js-yaml'); 9 | 10 | async function readYaml(path) { 11 | // Use a "promisified" version of fs.readFile(). 12 | const readFile = util.promisify(fs.readFile); 13 | const fileContents = await readFile(path, 'utf8'); 14 | return jsYaml.safeLoad(fileContents); 15 | } 16 | 17 | module.exports = readYaml; 18 | -------------------------------------------------------------------------------- /packages/validator/src/cli-validator/utils/validate-schema.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const Ajv = require('ajv'); 7 | 8 | /** 9 | * This function verifies that "data" complies with "schema". 10 | * @param {*} data the data to be validated 11 | * @param {*} schema the schema used to perform the validation 12 | * @returns [] if no validation errors were detected, or an array strings 13 | * containing the error messages. 14 | */ 15 | function validateSchema(data, schema) { 16 | const ajv = new Ajv({ allErrors: false }); 17 | 18 | const validate = ajv.compile(schema); 19 | const valid = validate(data); 20 | 21 | const messages = []; 22 | if (!valid) { 23 | const errors = validate.errors || []; 24 | errors.forEach(e => { 25 | const instancePath = e.instancePath ? `'${e.instancePath}': ` : ''; 26 | messages.push(`schema validation error: ${instancePath}${e.message}`); 27 | }); 28 | } 29 | 30 | return messages; 31 | } 32 | 33 | module.exports = validateSchema; 34 | -------------------------------------------------------------------------------- /packages/validator/src/markdown-report/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const getReport = require('./report'); 7 | const writeReportToFile = require('./write-file'); 8 | 9 | function printMarkdownReport(context, results) { 10 | const report = getReport(context, results); 11 | return writeReportToFile(context, report); 12 | } 13 | 14 | module.exports = { printMarkdownReport }; 15 | -------------------------------------------------------------------------------- /packages/validator/src/markdown-report/markdown-table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | class MarkdownTable { 7 | #headerRow; // String, with the header row. 8 | #rows = []; // Array of strings, all rows of the table body. 9 | #length; // Integer, the number of columns. 10 | 11 | constructor(...columnHeaders) { 12 | this.#length = columnHeaders.length; 13 | this.#headerRow = this.#createTableRow(...columnHeaders); 14 | } 15 | 16 | addRow(...data) { 17 | if (data.length !== this.#length) { 18 | console.error( 19 | `Error: addRow expected ${this.#length} arguments but received ${ 20 | data.length 21 | }` 22 | ); 23 | return; 24 | } 25 | 26 | // Any "blank" cells will be represented as 'undefined' in the 27 | // row data at this point. Convert those values to empty strings. 28 | data = data.map(v => v || ''); 29 | 30 | this.#rows.push(this.#createTableRow(...data)); 31 | } 32 | 33 | render() { 34 | const table = [this.#headerRow]; 35 | table.push(this.#getSeparatorRow(this.#length)); 36 | table.push(...this.#rows); 37 | return table.join('\n'); 38 | } 39 | 40 | #createTableRow(...data) { 41 | return `| ${data.join(' | ')} |`; 42 | } 43 | 44 | #getSeparatorRow(cols) { 45 | let row = '|'; 46 | for (let i = 0; i < cols; i++) { 47 | row += ' --- |'; 48 | } 49 | return row; 50 | } 51 | } 52 | 53 | module.exports = MarkdownTable; 54 | -------------------------------------------------------------------------------- /packages/validator/src/markdown-report/report.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | categorizedScores, 8 | primary, 9 | ruleViolationDetails, 10 | errorSummary, 11 | scoringData, 12 | warningSummary, 13 | } = require('./tables'); 14 | 15 | function getReport({ apiDefinition }, results) { 16 | return `# ${apiDefinition.info.title} 17 | 18 | Version: ${apiDefinition.info.version} 19 | 20 | ## Quick view 21 | ${primary(results)} 22 | 23 | The API impact score, also known as the "Automated Quality Screening" score, is calculated 24 | by the IBM OpenAPI Validator to help users understand the impact of the rule violations 25 | reported by the validator. The scores are designed to help users evaluate risk and make 26 | decisions about investing in the quality of their service's API. 27 | For more information, see [the AQS documentation](https://github.com/IBM/openapi-validator/blob/main/docs/automated-quality-screening.md). 28 | 29 | ## Breakdown by category 30 | ${categorizedScores(results)} 31 | 32 | The "overall" impact score is the average (mean) of the categorized scores. The categorized scores are 33 | inherently weighted by the scoring algorithm, so that security violations are 5 times as severe 34 | as usability violations, evolution 3 times, and robustness 2 times. 35 | 36 | ## Scoring information 37 | ${scoringData(results)} 38 | 39 | ## Error summary 40 | ${errorSummary(results)} 41 | 42 | ## Warning summary 43 | ${warningSummary(results)} 44 | 45 | ## Detailed results 46 | ${ruleViolationDetails(results)} 47 | `; 48 | } 49 | 50 | module.exports = getReport; 51 | -------------------------------------------------------------------------------- /packages/validator/src/markdown-report/tables/categorized-scores.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const MarkdownTable = require('../markdown-table'); 7 | 8 | function getTable({ impactScore }) { 9 | const { categorizedSummary } = impactScore; 10 | const table = new MarkdownTable('Category', 'Impact Score'); 11 | 12 | for (const [category, score] of Object.entries(categorizedSummary)) { 13 | // Bold the "overall" score. 14 | if (category === 'overall') { 15 | table.addRow(`**${category}**`, `**${score} / 100**`); 16 | } else { 17 | table.addRow(category, `${score} / 100`); 18 | } 19 | } 20 | 21 | return table.render(); 22 | } 23 | 24 | module.exports = getTable; 25 | -------------------------------------------------------------------------------- /packages/validator/src/markdown-report/tables/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | module.exports = { 7 | categorizedScores: require('./categorized-scores'), 8 | primary: require('./primary'), 9 | ruleViolationDetails: require('./rule-violation-details'), 10 | ...require('./rule-violation-summary'), 11 | scoringData: require('./scoring-data'), 12 | }; 13 | -------------------------------------------------------------------------------- /packages/validator/src/markdown-report/tables/primary.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const MarkdownTable = require('../markdown-table'); 7 | 8 | function getTable({ impactScore, error, warning }) { 9 | const table = new MarkdownTable( 10 | 'Impact Score', 11 | 'Error Count', 12 | 'Warning Count' 13 | ); 14 | 15 | table.addRow( 16 | `${impactScore.categorizedSummary.overall} / 100`, 17 | error.summary.total, 18 | warning.summary.total 19 | ); 20 | 21 | return table.render(); 22 | } 23 | 24 | module.exports = getTable; 25 | -------------------------------------------------------------------------------- /packages/validator/src/markdown-report/tables/rule-violation-details.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const MarkdownTable = require('../markdown-table'); 7 | 8 | function getTable({ error, warning }) { 9 | const table = new MarkdownTable( 10 | 'Rule', 11 | 'Message', 12 | 'Path', 13 | 'Line', 14 | 'Severity' 15 | ); 16 | 17 | error.results.forEach(({ message, path, rule, line }) => { 18 | table.addRow(rule, message, path.join('.'), line, 'error'); 19 | }); 20 | 21 | warning.results.forEach(({ message, path, rule, line }) => { 22 | table.addRow(rule, message, path.join('.'), line, 'warning'); 23 | }); 24 | 25 | return table.render(); 26 | } 27 | 28 | module.exports = getTable; 29 | -------------------------------------------------------------------------------- /packages/validator/src/markdown-report/tables/rule-violation-summary.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const MarkdownTable = require('../markdown-table'); 7 | 8 | function errorSummary({ error }) { 9 | return getTable(error); 10 | } 11 | 12 | function warningSummary({ warning }) { 13 | return getTable(warning); 14 | } 15 | 16 | function getTable({ summary }) { 17 | const table = new MarkdownTable('Count', 'Percentage', 'Generalized Message'); 18 | 19 | summary.entries.forEach(({ count, percentage, generalizedMessage }) => { 20 | table.addRow(count, `${percentage}%`, generalizedMessage); 21 | }); 22 | 23 | return table.render(); 24 | } 25 | 26 | module.exports = { 27 | errorSummary, 28 | warningSummary, 29 | }; 30 | -------------------------------------------------------------------------------- /packages/validator/src/markdown-report/tables/scoring-data.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const MarkdownTable = require('../markdown-table'); 7 | 8 | function getTable({ impactScore }) { 9 | const { scoringData } = impactScore; 10 | const table = new MarkdownTable( 11 | 'Rule', 12 | 'Count', 13 | 'Func', 14 | 'Usability Impact', 15 | 'Security Impact', 16 | 'Robustness Impact', 17 | 'Evolution Impact', 18 | 'Rule Total' 19 | ); 20 | 21 | scoringData.forEach(({ rule, count, func, demerits }) => { 22 | const { usability, security, robustness, evolution, total } = demerits; 23 | table.addRow( 24 | rule, 25 | count, 26 | func, 27 | usability, 28 | security, 29 | robustness, 30 | evolution, 31 | total 32 | ); 33 | }); 34 | 35 | return table.render(); 36 | } 37 | 38 | module.exports = getTable; 39 | -------------------------------------------------------------------------------- /packages/validator/src/markdown-report/write-file.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { writeFileSync } = require('fs'); 7 | const path = require('path'); 8 | 9 | function writeReportToFile({ currentFilename }, report) { 10 | // For now, only a default filename is supported, which 11 | // is based on the name of the API definition file. 12 | const { name } = path.parse(currentFilename); 13 | const reportFilename = `${name}-validator-report.md`; 14 | 15 | // Write the output to a file. It will overwrite an existing file. 16 | writeFileSync(reportFilename, report); 17 | 18 | // Return the filename as a confirmation of the success and for 19 | // later use in the logs shown to the user. 20 | return path.resolve(process.cwd(), reportFilename); 21 | } 22 | 23 | module.exports = writeReportToFile; 24 | -------------------------------------------------------------------------------- /packages/validator/src/schemas/rubric-entry.yaml: -------------------------------------------------------------------------------- 1 | $schema: http://json-schema.org/draft-07/schema# 2 | title: IBM OpenAPI Validator Scoring Rubric Entry 3 | description: An entry in the scoring rubric defined for a given rule 4 | type: object 5 | additionalProperties: false 6 | required: 7 | - categories 8 | properties: 9 | categories: 10 | type: array 11 | description: A list of API categories the rule violation impacts 12 | items: 13 | type: string 14 | description: The specific category of impact for a given rule violation 15 | # This enum needs to match the categories defined in 'scoring-tool/categories.js'. 16 | enum: 17 | - usability 18 | - security 19 | - robustness 20 | - evolution 21 | coefficient: 22 | type: integer 23 | description: A multiplier that increases the severity of a rule violation 24 | denominator: 25 | type: string 26 | description: A scaling factor that decreases the severity of a rule violation based on the context of the entire API 27 | -------------------------------------------------------------------------------- /packages/validator/src/scoring-tool/categories.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | // Define the list of categories, along with their scoring coefficients. 7 | const categories = { 8 | usability: { 9 | coefficient: 1, 10 | }, 11 | security: { 12 | coefficient: 5, 13 | }, 14 | robustness: { 15 | coefficient: 2, 16 | }, 17 | evolution: { 18 | coefficient: 3, 19 | }, 20 | }; 21 | 22 | function getCategories() { 23 | return Object.keys(categories); 24 | } 25 | 26 | function getCategoryCoefficient(category) { 27 | return categories[category].coefficient; 28 | } 29 | 30 | module.exports = { 31 | getCategories, 32 | getCategoryCoefficient, 33 | }; 34 | -------------------------------------------------------------------------------- /packages/validator/src/scoring-tool/get-title.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { basename } = require('node:path'); 7 | 8 | // Get the title for an API. 9 | function getTitle(apiDef, filename) { 10 | let title = ''; 11 | if (apiDef.info) { 12 | title = apiDef.info.title || apiDef.info['x-alternate-name']; 13 | 14 | // If title is from API definition, it doesn't help us prevent collisions 15 | // with other APIs of the same name (different versions of same API, etc.) 16 | if (title && apiDef.info.version) { 17 | title += ` ${apiDef.info.version}`; 18 | } 19 | } 20 | 21 | // Fallback to the name of the file. 22 | if (!title) { 23 | // The file may be a full path - if so, extract the name of the file itself. 24 | title = basename(filename); 25 | } 26 | 27 | return title; 28 | } 29 | 30 | module.exports = { 31 | getTitle, 32 | }; 33 | -------------------------------------------------------------------------------- /packages/validator/src/scoring-tool/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | printCategorizedScoresTable, 8 | printScoringDataTable, 9 | } = require('./output'); 10 | const { scoreResults } = require('./score'); 11 | const { computeMetrics } = require('./compute-metrics'); 12 | 13 | async function produceImpactScore(validatorResults, { apiDefinition, logger }) { 14 | const metrics = await computeMetrics(apiDefinition); 15 | logger.debug(`API scaling metrics: ${metrics.toString()}`); 16 | 17 | // Execute scoring logic. 18 | return scoreResults(validatorResults, metrics, logger); 19 | } 20 | 21 | function printScoreTables({ config }, { impactScore }) { 22 | printCategorizedScoresTable(impactScore); 23 | 24 | if (!config.summaryOnly) { 25 | printScoringDataTable(impactScore); 26 | } 27 | 28 | // Print a new line at the end to be consistent with other validator output. 29 | console.log(); 30 | } 31 | 32 | module.exports = { 33 | produceImpactScore, 34 | printScoreTables, 35 | }; 36 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/alternate-spectral-configs/extends-default.yaml: -------------------------------------------------------------------------------- 1 | extends: '@ibm-cloud/openapi-ruleset' 2 | rules: 3 | # Turn off a rule that is on in the default ruleset 4 | openapi-tags: off 5 | # Turn on a rule that is off in the default ruleset 6 | no-eval-in-markdown : error 7 | # Change the severity of a rule in the default ruleset 8 | oas3-valid-schema-example: warn 9 | oas3-schema: off 10 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/bad-json.json: -------------------------------------------------------------------------------- 1 | { 2 | "hi there"; "its me, a bad json object" 3 | } -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/config/config1.yaml: -------------------------------------------------------------------------------- 1 | colorizeOutput: true 2 | errorsOnly: true 3 | files: 4 | - file1.yaml 5 | - file2.json 6 | limits: 7 | warnings: 5 8 | ignoreFiles: 9 | - 'ignored/file1' 10 | logLevels: 11 | root: 'info' 12 | 'ibm-schema-description-exists': 'debug' 13 | outputFormat: 'text' 14 | summaryOnly: false 15 | produceImpactScore: false 16 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/config/five-warnings.json: -------------------------------------------------------------------------------- 1 | { 2 | "limits": { 3 | "warnings": 5 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/config/invalid-config.yaml: -------------------------------------------------------------------------------- 1 | limits: 2 | errors: 10 # unexpected property 3 | colorizeOutput: false 4 | summaryOnly: 'uh-huh' # should be boolean 5 | errorsOnly: 3 # should be boolean 6 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/config/invalid-json.json: -------------------------------------------------------------------------------- 1 | [ 2 | : } 3 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/config/invalid-values.json: -------------------------------------------------------------------------------- 1 | { 2 | "errors": 0, 3 | "warnings": "text", 4 | "population": 10 5 | } 6 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/config/valid-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | limits: { 3 | warnings: 10, 4 | }, 5 | ignoreFiles: ['ignored/file/numero_uno', 'another/ignored/file'], 6 | logLevels: { 7 | logger1: 'debug', 8 | root: 'info', 9 | logger2: 'error', 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/config/valid-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "limits": { 3 | "warnings": 10 4 | }, 5 | "ignoreFiles": [ 6 | "ignored/file/numero_uno", 7 | "another/ignored/file" 8 | ], 9 | "logLevels": { 10 | "logger1": "debug", 11 | "root": "info", 12 | "logger2": "error" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/config/valid-config.yaml: -------------------------------------------------------------------------------- 1 | limits: 2 | warnings: 10 3 | ignoreFiles: 4 | - 'ignored/file/numero_uno' 5 | - 'another/ignored/file' 6 | logLevels: 7 | logger1: 'debug' 8 | root: 'info' 9 | logger2: 'error' 10 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/config/zero-warnings.json: -------------------------------------------------------------------------------- 1 | { 2 | "limits": { 3 | "warnings": 0 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/multi-file-spec/city.yaml: -------------------------------------------------------------------------------- 1 | schema: 2 | properties: 3 | name: 4 | type: string 5 | primary_team: 6 | type: object 7 | $ref: ./sports-team.yaml#/schema 8 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/multi-file-spec/main.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | servers: 3 | - url: https://my-service.com/api 4 | paths: 5 | /example: 6 | get: 7 | summary: Summary 8 | responses: 9 | "200": 10 | description: OK 11 | content: 12 | application/json: 13 | schema: 14 | $ref: "./schema.yaml#/components/schemas/SchemaDef" 15 | /circular_example: 16 | get: 17 | summary: Summary 18 | responses: 19 | "200": 20 | description: OK 21 | content: 22 | application/json: 23 | schema: 24 | $ref: "./schema.yaml#/components/schemas/CircularSchema" 25 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/multi-file-spec/schema.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | paths: {} 3 | components: 4 | schemas: 5 | SchemaDef: 6 | type: object 7 | CircularSchema: 8 | type: object 9 | properties: 10 | id: 11 | type: string 12 | city: 13 | $ref: ./city.yaml#/schema 14 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/multi-file-spec/sports-team.yaml: -------------------------------------------------------------------------------- 1 | schema: 2 | properties: 3 | name: 4 | type: string 5 | location: 6 | type: object 7 | $ref: ./city.yaml#/schema 8 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/oas3/complex-test-compose-model.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | components: 3 | schemas: 4 | A: 5 | description: a schema 6 | type: string 7 | format: byte 8 | complexOneOfError: 9 | description: second oneOf should be array 10 | oneOf: 11 | - oneOf: 12 | $ref: '#/components/schemas/A' 13 | complexAllOfError: 14 | description: allOf should be array 15 | oneOf: 16 | - allOf: 17 | type: string 18 | complexAnyOfError: 19 | description: anyOf should be array 20 | oneOf: 21 | - anyOf: 22 | $ref: '#/components/schemas/A' 23 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/oas3/compose-model-items.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | components: 3 | schemas: 4 | one_of_array: 5 | type: array 6 | description: a oneOf array schema 7 | items: 8 | oneOf: 9 | type: string 10 | all_of_array: 11 | type: array 12 | description: an allOf array schema 13 | items: 14 | allOf: 15 | type: string 16 | any_of_array: 17 | type: array 18 | description: an anyOf array schema 19 | items: 20 | anyOf: 21 | type: string 22 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/oas3/compose-model-props.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | components: 3 | schemas: 4 | object1: 5 | type: object 6 | description: an object schema 7 | properties: 8 | one_of_error_prop: 9 | description: oneOf should be array 10 | schema: 11 | oneOf: 12 | type: string 13 | all_of_error_prop: 14 | description: allOf should be array 15 | schema: 16 | allOf: 17 | type: string 18 | any_of_error_prop: 19 | description: anyOf should be array 20 | schema: 21 | anyOf: 22 | type: string 23 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/oas3/compose-models-use-array.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | components: 3 | schemas: 4 | object1: 5 | type: object 6 | description: an object schema 7 | properties: 8 | one_of_error_prop: 9 | description: oneOf should be array 10 | schema: 11 | oneOf: 12 | - type: string 13 | all_of_error_prop: 14 | description: allOf should be array 15 | schema: 16 | allOf: 17 | - type: string 18 | any_of_error_prop: 19 | description: anyOf should be array 20 | schema: 21 | anyOf: 22 | - type: string 23 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/oas3/duplicate-keys.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0", 3 | "info": { 4 | "title": "JSON document for test.", 5 | "version": "1.0.0", 6 | "version": "1.0.1", 7 | "description": "This document should not have two version keys." 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/oas3/test-compose-model.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | components: 3 | schemas: 4 | A: 5 | description: a schema 6 | type: string 7 | format: byte 8 | oneOfError: 9 | description: should be array but instead just a ref 10 | oneOf: 11 | $ref: '#/components/schemas/A' 12 | allOfError: 13 | description: should be array but instead just a type 14 | allOf: 15 | type: string 16 | anyOfError: 17 | description: should be array but instead just a ref 18 | anyOf: 19 | $ref: '#/components/schemas/A' 20 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/oas3/trailing-comma.json: -------------------------------------------------------------------------------- 1 | { 2 | "openapi": "3.0.0", 3 | "info": { 4 | "title": "Trailing comma Testcase (Test)", 5 | "description": "API doc with trailing commas", 6 | "version": "1.0.0" 7 | }, 8 | "paths": { 9 | "/v1/thing": { 10 | "get": { 11 | "operationId": "get_foo", 12 | "parameters": [ 13 | { 14 | "name": "name", 15 | "in": "query", 16 | "schema": { 17 | "type": "string" 18 | }, 19 | "required": true 20 | }, 21 | { 22 | "name": "type", 23 | "in": "header", 24 | "schema": { 25 | "type": "string" 26 | }, 27 | "required": true 28 | }, 29 | ], 30 | "responses": { 31 | "201": { 32 | "description": "Created" 33 | }, 34 | "default": { 35 | "description": "An error occurred." 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/openapi-3-1.json: -------------------------------------------------------------------------------- 1 | { "openapi": "3.1.0" } 2 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/mock-files/swagger-2.json: -------------------------------------------------------------------------------- 1 | { "swagger": "2.0" } 2 | -------------------------------------------------------------------------------- /packages/validator/test/cli-validator/tests/utils/read-yaml.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { readYaml } = require('../../../../src/cli-validator/utils'); 7 | 8 | describe('Read YAML tests', function () { 9 | it('should read a yaml file and return an object representing the contents', async () => { 10 | const filepath = __dirname + '/../../../../src/schemas/results-object.yaml'; 11 | const obj = await readYaml(filepath); 12 | 13 | // check some known keys 14 | expect(obj.title).toBeDefined(); 15 | expect(obj.title).toBe('IBM OpenAPI Validator Results Schema'); 16 | expect(obj.additionalProperties).toBeDefined(); 17 | expect(typeof obj.additionalProperties).toBe('boolean'); 18 | expect(obj.additionalProperties).toBe(false); 19 | expect(obj.required).toBeDefined(); 20 | expect(Array.isArray(obj.required)).toBe(true); 21 | expect(obj.properties).toBeDefined(); 22 | expect(typeof obj.properties).toBe('object'); 23 | }); 24 | 25 | it('should throw an exception if the file does not exist', async () => { 26 | await expect(readYaml('../no-file')).rejects.toThrow( 27 | 'ENOENT: no such file or directory' 28 | ); 29 | }); 30 | 31 | it('should throw an exception if the file is invalid yaml', async () => { 32 | const filepath = __dirname + '/../../mock-files/bad-json.json'; 33 | await expect(readYaml(filepath)).rejects.toThrow(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/validator/test/markdown-report/report.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const getReport = require('../../src/markdown-report/report'); 7 | const validatorResults = require('../test-utils/mock-json-output.json'); 8 | 9 | describe('getReport tests', function () { 10 | it('should create a markdown report from the results', function () { 11 | const report = getReport({ apiDefinition: getMockAPI() }, validatorResults); 12 | 13 | // Check main title. 14 | expect(report.startsWith('# Swagger Petstore\n\nVersion: 1.0.0')).toBe( 15 | true 16 | ); 17 | 18 | // Check all subtitle-level headers. 19 | const headers = report 20 | .split('\n') 21 | .filter(l => l.startsWith('##')) 22 | .map(l => l.slice(3)); 23 | expect(headers).toEqual([ 24 | 'Quick view', 25 | 'Breakdown by category', 26 | 'Scoring information', 27 | 'Error summary', 28 | 'Warning summary', 29 | 'Detailed results', 30 | ]); 31 | 32 | // Check that it ends with an emptly line. 33 | expect(report.endsWith('\n')).toBe(true); 34 | }); 35 | }); 36 | 37 | function getMockAPI() { 38 | // These are the only fields actually used in the code. 39 | return { 40 | info: { 41 | title: 'Swagger Petstore', 42 | version: '1.0.0', 43 | }, 44 | }; 45 | } 46 | -------------------------------------------------------------------------------- /packages/validator/test/markdown-report/tables/categorized-scores.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { categorizedScores } = require('../../../src/markdown-report/tables'); 7 | const validatorResults = require('../../test-utils/mock-json-output.json'); 8 | 9 | describe('categorizedScores table tests', function () { 10 | it('should produce a table with categorized scores from the results', function () { 11 | const tableRows = categorizedScores(validatorResults).split('\n'); 12 | 13 | expect(tableRows).toHaveLength(7); 14 | expect(tableRows[0]).toBe('| Category | Impact Score |'); 15 | expect(tableRows[1]).toBe('| --- | --- |'); 16 | expect(tableRows[2]).toBe('| usability | 94 / 100 |'); 17 | expect(tableRows[3]).toBe('| security | 96 / 100 |'); 18 | expect(tableRows[4]).toBe('| robustness | 97 / 100 |'); 19 | expect(tableRows[5]).toBe('| evolution | 98 / 100 |'); 20 | expect(tableRows[6]).toBe('| **overall** | **96 / 100** |'); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/validator/test/markdown-report/tables/primary.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { primary } = require('../../../src/markdown-report/tables'); 7 | const validatorResults = require('../../test-utils/mock-json-output.json'); 8 | 9 | describe('primary table tests', function () { 10 | it('should produce a table with the primary data from the results', function () { 11 | const tableRows = primary(validatorResults).split('\n'); 12 | 13 | expect(tableRows).toHaveLength(3); 14 | expect(tableRows[0]).toBe('| Impact Score | Error Count | Warning Count |'); 15 | expect(tableRows[1]).toBe('| --- | --- | --- |'); 16 | expect(tableRows[2]).toBe('| 96 / 100 | 2 | 1 |'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /packages/validator/test/markdown-report/tables/rule-violation-details.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { ruleViolationDetails } = require('../../../src/markdown-report/tables'); 7 | const validatorResults = require('../../test-utils/mock-json-output.json'); 8 | 9 | describe('ruleViolationDetails table tests', function () { 10 | it('should produce a table with all rule violations from the results', function () { 11 | const tableRows = ruleViolationDetails(validatorResults).split('\n'); 12 | 13 | expect(tableRows).toHaveLength(5); 14 | expect(tableRows[0]).toBe('| Rule | Message | Path | Line | Severity |'); 15 | expect(tableRows[1]).toBe('| --- | --- | --- | --- | --- |'); 16 | expect(tableRows[2]).toBe( 17 | '| ibm-no-consecutive-path-parameter-segments | Path contains two or more consecutive path parameter references: /pets/{pet_id}/{id} | paths./pets/{pet_id}/{id} | 84 | error |' 18 | ); 19 | expect(tableRows[3]).toBe( 20 | "| ibm-integer-attributes | Integer schemas should define property 'minimum' | components.schemas.Pet.properties.id | 133 | error |" 21 | ); 22 | expect(tableRows[4]).toBe( 23 | "| ibm-anchored-patterns | A regular expression used in a 'pattern' attribute should be anchored with ^ and $ | components.schemas.Error.properties.message.pattern | 233 | warning |" 24 | ); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /packages/validator/test/markdown-report/tables/rule-violation-summary.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | errorSummary, 8 | warningSummary, 9 | } = require('../../../src/markdown-report/tables'); 10 | const validatorResults = require('../../test-utils/mock-json-output.json'); 11 | 12 | describe('ruleViolationSummary table tests', function () { 13 | it('should produce a table with a summary of the error results', function () { 14 | const tableRows = errorSummary(validatorResults).split('\n'); 15 | 16 | expect(tableRows).toHaveLength(4); 17 | expect(tableRows[0]).toBe('| Count | Percentage | Generalized Message |'); 18 | expect(tableRows[1]).toBe('| --- | --- | --- |'); 19 | expect(tableRows[2]).toBe( 20 | '| 1 | 50% | Path contains two or more consecutive path parameter references |' 21 | ); 22 | expect(tableRows[3]).toBe( 23 | "| 1 | 50% | Integer schemas should define property 'minimum' |" 24 | ); 25 | }); 26 | 27 | it('should produce a table with a summary of the warning results', function () { 28 | const tableRows = warningSummary(validatorResults).split('\n'); 29 | 30 | expect(tableRows).toHaveLength(3); 31 | expect(tableRows[0]).toBe('| Count | Percentage | Generalized Message |'); 32 | expect(tableRows[1]).toBe('| --- | --- | --- |'); 33 | expect(tableRows[2]).toBe( 34 | "| 1 | 100% | A regular expression used in a 'pattern' attribute should be anchored with ^ and $ |" 35 | ); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/validator/test/markdown-report/tables/scoring-data.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { scoringData } = require('../../../src/markdown-report/tables'); 7 | const validatorResults = require('../../test-utils/mock-json-output.json'); 8 | 9 | describe('scoringData table tests', function () { 10 | it('should produce a table with scoring information from the results', function () { 11 | const tableRows = scoringData(validatorResults).split('\n'); 12 | 13 | expect(tableRows).toHaveLength(5); 14 | expect(tableRows[0]).toBe( 15 | '| Rule | Count | Func | Usability Impact | Security Impact | Robustness Impact | Evolution Impact | Rule Total |' 16 | ); 17 | expect(tableRows[1]).toBe( 18 | '| --- | --- | --- | --- | --- | --- | --- | --- |' 19 | ); 20 | expect(tableRows[2]).toBe( 21 | '| ibm-no-consecutive-path-parameter-segments | 1 | 1×10÷operations | 3.33 | | | | 3.33 |' 22 | ); 23 | expect(tableRows[3]).toBe( 24 | '| ibm-integer-attributes | 1 | 1×2÷integer-schemas | 0.5 | 2.5 | 1 | 1.5 | 5.5 |' 25 | ); 26 | expect(tableRows[4]).toBe( 27 | '| ibm-anchored-patterns | 1 | 1×1÷operations | | | 0.67 | | 0.67 |' 28 | ); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/validator/test/markdown-report/write-file.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { existsSync, readFileSync, unlinkSync } = require('fs'); 7 | const writeReportToFile = require('../../src/markdown-report/write-file'); 8 | 9 | describe('writeReportToFile tests', function () { 10 | const baseFilename = 'test-api-def-file'; 11 | const expectedFilename = `${baseFilename}-validator-report.md`; 12 | 13 | // Clean up by deleting the file. 14 | afterEach(function () { 15 | unlinkSync(expectedFilename); 16 | }); 17 | 18 | it('should write contents to a file and return its resolved path', function () { 19 | const mockReportContents = 'Not a very detailed report.'; 20 | const mockContext = { currentFilename: `${baseFilename}.json` }; 21 | 22 | // File should not exist before report is written. 23 | expect(existsSync(expectedFilename)).toBe(false); 24 | 25 | // Write the report. 26 | const resolvedPath = writeReportToFile(mockContext, mockReportContents); 27 | 28 | // Check for a successful write. 29 | expect(existsSync(expectedFilename)).toBe(true); 30 | expect(resolvedPath).toMatch( 31 | new RegExp(`.*/openapi-validator/packages/validator/${baseFilename}`) 32 | ); 33 | expect(readFileSync(expectedFilename, { encoding: 'utf-8' })).toBe( 34 | mockReportContents 35 | ); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/validator/test/scoring-tool/categories.test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { 7 | getCategories, 8 | getCategoryCoefficient, 9 | } = require('../../src/scoring-tool/categories'); 10 | 11 | describe('scoring-tool categories tests', function () { 12 | it('should return the list of supported categories', function () { 13 | const expectedCategories = [ 14 | 'usability', 15 | 'security', 16 | 'robustness', 17 | 'evolution', 18 | ]; 19 | const actualCategories = getCategories(); 20 | 21 | expect(actualCategories.length).toEqual(expectedCategories.length); 22 | expectedCategories.forEach(c => { 23 | expect(actualCategories.includes(c)).toBe(true); 24 | }); 25 | }); 26 | 27 | it('should return the correct coefficient for each ', function () { 28 | expect(getCategoryCoefficient('usability')).toBe(1); 29 | expect(getCategoryCoefficient('security')).toBe(5); 30 | expect(getCategoryCoefficient('robustness')).toBe(2); 31 | expect(getCategoryCoefficient('evolution')).toBe(3); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/validator/test/test-utils/get-captured-text.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const { stripAnsi } = require('./strip-ansi'); 7 | 8 | module.exports.getCapturedText = callsToLog => 9 | formatCapturedText(callsToLog, false); 10 | 11 | module.exports.getCapturedTextWithColor = callsToLog => 12 | formatCapturedText(callsToLog, true); 13 | 14 | function formatCapturedText(callsToLog, preserveColors) { 15 | return callsToLog.map(args => { 16 | // the tests expect `console.log()` to be interpreted as a newline 17 | // but the mock captures the info as `undefined` 18 | const output = args[0] === undefined ? '\n' : args[0]; 19 | 20 | // the validator only ever uses the first arg in consolg.log 21 | return preserveColors ? output : stripAnsi(output); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /packages/validator/test/test-utils/get-message-and-path-from-captured-text.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | module.exports.getMessageAndPathFromCapturedText = 7 | getMessageAndPathFromCapturedText; 8 | 9 | function getMessageAndPathFromCapturedText(pattern, capturedText) { 10 | const messages = []; 11 | for (let i = 0; i < capturedText.length; i++) { 12 | if (capturedText[i].includes(pattern)) { 13 | const aMessage = []; 14 | 15 | const messageMap = new Map(); 16 | const messageSplit = capturedText[i].split(':'); 17 | messageMap.set(messageSplit[0].trim(), messageSplit[1].trim()); 18 | aMessage.push(messageMap); 19 | 20 | const pathMap = new Map(); 21 | const pathSplit = capturedText[i + 1].split(':'); 22 | pathMap.set(pathSplit[0].trim(), pathSplit[1].trim()); 23 | aMessage.push(pathMap); 24 | 25 | messages.push(aMessage); 26 | // we jump the index by one due to that i+1 entry is already processed 27 | i++; 28 | } 29 | } 30 | return messages; 31 | } 32 | -------------------------------------------------------------------------------- /packages/validator/test/test-utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | module.exports = { 7 | testValidator: require('./test-validator'), 8 | ...require('./get-captured-text'), 9 | ...require('./get-captured-text'), 10 | ...require('./get-message-and-path-from-captured-text'), 11 | ...require('./strip-ansi'), 12 | ...require('./parse-scoring-table'), 13 | }; 14 | -------------------------------------------------------------------------------- /packages/validator/test/test-utils/parse-scoring-table.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2024 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | function extractValuesFromTable(table) { 7 | return table 8 | .replaceAll('\x1B[37m', '') 9 | .replaceAll('\x1B[0m', '') 10 | .replaceAll('\x1B[01m', '') 11 | .split('\n') 12 | .map(row => 13 | row 14 | .split('│') 15 | .map(s => s.trim()) 16 | .slice(1, -1) 17 | ); 18 | } 19 | 20 | module.exports = { 21 | extractValuesFromTable, 22 | }; 23 | -------------------------------------------------------------------------------- /packages/validator/test/test-utils/strip-ansi.js: -------------------------------------------------------------------------------- 1 | const ansiRegex = require('ansi-regex'); 2 | 3 | const regex = ansiRegex(); 4 | 5 | function stripAnsi(string) { 6 | if (typeof string !== 'string') { 7 | throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``); 8 | } 9 | 10 | // Even though the regex is global, we don't need to reset the `.lastIndex` 11 | // because unlike `.exec()` and `.test()`, `.replace()` does it automatically 12 | // and doing it manually has a performance penalty. 13 | return string.replace(regex, ''); 14 | } 15 | 16 | module.exports.stripAnsi = stripAnsi; 17 | -------------------------------------------------------------------------------- /packages/validator/test/test-utils/test-validator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 - 2023 IBM Corporation. 3 | * SPDX-License-Identifier: Apache2.0 4 | */ 5 | 6 | const runValidator = require('../../src/cli-validator/run-validator'); 7 | 8 | /** 9 | * This function is used by testcases to run the validator via CLI options. 10 | * It is simply a wrapper that passes on the provided "cliArgs", along 11 | * with the parse options that will prevent Commander from doing its 12 | * normal CLI interpretation (i.e. argv[0] is either 'node' or 'electron', etc.). 13 | * @param {[string]} cliArgs an array of strings representing command-line arguments 14 | * @returns the exit code returned by the validator 15 | */ 16 | async function testValidator(cliArgs) { 17 | // Leaving this here for debugging purposes. 18 | // console.log(`cliArgs: ${cliArgs}`); 19 | return runValidator(cliArgs, { from: 'user' }); 20 | } 21 | 22 | module.exports = testValidator; 23 | -------------------------------------------------------------------------------- /scripts/create-binaries.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Enable shell debug mode so we get a few additional details in the build log. 4 | set -x 5 | 6 | # This script will be run when "npm run pkg" is executed from within 7 | # the "packages/validator" directory. 8 | # The commands below assume that the current directory is packages/validator. 9 | 10 | # Before creating the executables, we need to remove the "openapi-ruleset" 11 | # and "openapi-ruleset-utilities" dependencies from the packages/validator/node_modules 12 | # and packages/ruleset/node_modules directories (respectively). 13 | # We need to do this because those locations will actually contain the prior version 14 | # of the dependency if we've published a new release of it during the same build. 15 | # By removing them from the packages/[validator,ruleset] directories, we ensure that 16 | # the correct version of these dependencies is obtained from the project's top-level 17 | # node_modules directory instead. 18 | if [[ -e "node_modules/@ibm-cloud" ]]; then 19 | rm -fr "node_modules/@ibm-cloud" 20 | fi 21 | 22 | if [[ -e "../ruleset/node_modules/@ibm-cloud" ]]; then 23 | rm -fr "../ruleset/node_modules/@ibm-cloud" 24 | fi 25 | 26 | # Create the executables 27 | ../../node_modules/.bin/pkg --out-path=./bin -t node18-linux,node18-win,node18-macos ./package.json 28 | 29 | # Rename the executables and set their execute bit. 30 | cd ./bin 31 | mv ibm-openapi-validator-macos lint-openapi-macos 32 | mv ibm-openapi-validator-linux lint-openapi-linux 33 | mv ibm-openapi-validator-win.exe lint-openapi-win.exe 34 | chmod +x lint-openapi-win.exe 35 | -------------------------------------------------------------------------------- /scripts/deploy-container-image.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | deploy() { 6 | login "$DOCKER_HUB_TOKEN" 7 | deploy_docker 8 | } 9 | 10 | login() { 11 | local token=$1 12 | if [[ -z "$token" ]]; then 13 | echo 'No Docker Hub token available' >&2 14 | exit 2 15 | fi 16 | docker login --username ibmdevxsdk --password-stdin <<<"$token" 17 | } 18 | 19 | deploy_docker() { 20 | local new_version=`node -p "require('./packages/validator/package.json').version"` 21 | 22 | # Ensure version is present and has semver format 23 | [[ "$new_version" =~ [[:digit:]]+\.[[:digit:]]+\.[[:digit:]]+ ]] 24 | new_version=${BASH_REMATCH[0]} 25 | 26 | if [ -z "$new_version" ] 27 | then 28 | echo "Next release version missing or incorrectly formatted:" 29 | echo $new_version 30 | exit 2 31 | fi 32 | 33 | npm run build-docker 34 | docker tag ibmdevxsdk/openapi-validator:latest ibmdevxsdk/openapi-validator:"$new_version" 35 | 36 | docker push ibmdevxsdk/openapi-validator:"$new_version" 37 | docker push ibmdevxsdk/openapi-validator:latest 38 | } 39 | 40 | deploy "$@" 41 | -------------------------------------------------------------------------------- /scripts/generate-utilities-docs.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const mustache = require('mustache'); 4 | const { readFileSync, writeFileSync } = require('fs'); 5 | const { execSync } = require('child_process'); 6 | 7 | const template = readFileSync('scripts/templates/package.mustache', { 8 | encoding: 'utf8', 9 | }); 10 | 11 | const raw = JSON.parse( 12 | execSync( 13 | 'jsdoc -X packages/utilities/src/index.js packages/utilities/src/**/*.js', 14 | { encoding: 'utf8' } 15 | ) 16 | ); 17 | 18 | const docs = raw.filter(d => d.comment !== '' && d.access !== 'private'); 19 | 20 | const package = docs.find(d => d.kind === 'module'); 21 | 22 | const constants = docs 23 | .filter(d => d.kind === 'constant') 24 | .map(c => { 25 | const members = docs.filter( 26 | d => d.kind === 'member' && d.memberof === c.longname 27 | ); 28 | 29 | return { 30 | name: c.name, 31 | description: c.description, 32 | members, 33 | hasMembers: members.length > 0, 34 | }; 35 | }); 36 | 37 | const functions = docs 38 | .filter(d => d.kind === 'function') 39 | .map(f => ({ 40 | signature: `${f.name}(${f.params.map(p => p.name).join(', ')})`, 41 | params: f.params.map(p => ({ 42 | name: p.name, 43 | type: p.type?.names?.join(' | '), 44 | description: p.description, 45 | })), 46 | description: f.description, 47 | returns: f.returns?.map(r => ({ 48 | type: r.type?.names?.join(' | '), 49 | description: r.description, 50 | })), 51 | })); 52 | 53 | const rendered = mustache.render(template, { package, constants, functions }); 54 | 55 | writeFileSync('docs/openapi-ruleset-utilities.md', rendered); 56 | -------------------------------------------------------------------------------- /scripts/templates/package.mustache: -------------------------------------------------------------------------------- 1 | # {{{package.name}}} 2 | 3 | {{{package.description}}} 4 | 5 | ## Constants 6 | 7 | {{#constants}} 8 | ### `{{{name}}}` 9 | 10 | {{{description}}} 11 | 12 | {{#hasMembers}} 13 | #### Members 14 | {{#members}} 15 | - **`{{{longname}}}`**: {{{description}}} 16 | {{/members}} 17 | {{/hasMembers}} 18 | 19 | {{/constants}} 20 | 21 | ## Functions 22 | 23 | {{#functions}} 24 | ### `{{{signature}}}` 25 | 26 | {{{description}}} 27 | 28 | #### Parameters 29 | 30 | {{#params}} 31 | - **`{{{name}}}`** {{#type}}`<{{{.}}}>`{{/type}}{{#description}}: {{{.}}}{{/description}} 32 | {{/params}} 33 | 34 | {{#returns}} 35 | #### Returns {{#type}}`{{{.}}}`{{/type}}{{#description}}: {{{.}}}{{/description}} 36 | {{/returns}} 37 | 38 | {{/functions}} 39 | --------------------------------------------------------------------------------