├── .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 |
--------------------------------------------------------------------------------