From c46fb2179dbe82b5a0d458588d32b68f7176c583 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Thu, 5 Mar 2026 10:54:58 -0500 Subject: [PATCH] Refactor spectral rules to distinguish between backend and frontend type requirements; add new test specifications for valid compositions. --- .spectral.backend.yaml | 8 +-- .spectral.frontend.yaml | 8 +-- .workleap.rules.yaml | 67 ++++++++++++------- ...ms-must-have-a-type-valid-composition.yaml | 21 ++++++ ...es-must-have-a-type-valid-composition.yaml | 25 +++++++ ...ct-must-have-a-type-valid-composition.yaml | 37 ++++++++++ ...es-must-have-a-type-valid-composition.yaml | 18 +++++ test.ps1 | 49 ++++++++++---- 8 files changed, 186 insertions(+), 47 deletions(-) create mode 100644 TestSpecs/items-must-have-a-type-valid-composition.yaml create mode 100644 TestSpecs/path-schema-properties-must-have-a-type-valid-composition.yaml create mode 100644 TestSpecs/schema-object-must-have-a-type-valid-composition.yaml create mode 100644 TestSpecs/schemas-properties-must-have-a-type-valid-composition.yaml diff --git a/.spectral.backend.yaml b/.spectral.backend.yaml index 33234d2..b63091a 100644 --- a/.spectral.backend.yaml +++ b/.spectral.backend.yaml @@ -15,10 +15,10 @@ rules: oas3-schema: true oas3-operation-security-defined: true schema-ids-must-have-alphanumeric-characters-only: true - schemas-properties-must-have-a-type: true - path-schema-properties-must-have-a-type: true - schema-object-must-have-a-type: true - items-must-have-a-type: true + schemas-properties-must-have-a-type-backend: true + path-schema-properties-must-have-a-type-backend: true + schema-object-must-have-a-type-backend: true + items-must-have-a-type-backend: true schema-name-length-must-be-short: true must-not-use-base-server-url: true # Backend to Backend specific rules diff --git a/.spectral.frontend.yaml b/.spectral.frontend.yaml index dfb9169..f447d36 100644 --- a/.spectral.frontend.yaml +++ b/.spectral.frontend.yaml @@ -15,10 +15,10 @@ rules: oas3-schema: true oas3-operation-security-defined: true schema-ids-must-have-alphanumeric-characters-only: true - schemas-properties-must-have-a-type: true - path-schema-properties-must-have-a-type: true - schema-object-must-have-a-type: true - items-must-have-a-type: true + schemas-properties-must-have-a-type-frontend: true + path-schema-properties-must-have-a-type-frontend: true + schema-object-must-have-a-type-frontend: true + items-must-have-a-type-frontend: true schema-name-length-must-be-short: true must-not-use-base-server-url: true must-use-get-post-methods: true diff --git a/.workleap.rules.yaml b/.workleap.rules.yaml index 9777822..b6f2f45 100644 --- a/.workleap.rules.yaml +++ b/.workleap.rules.yaml @@ -25,12 +25,12 @@ rules: functionOptions: match: '^[a-zA-Z0-9]+$' - schemas-properties-must-have-a-type: + schemas-properties-must-have-a-type-backend: description: "All schemas properties must have a type or schema reference. Refer to: https://gsoftdev.atlassian.net/wiki/spaces/TEC/pages/3858235678/IDP+OpenAPI+Rulesets#schemas-properties-must-have-a-type-and-path-schema-properties-must-have-a-type" recommended: true severity: warn given: $..schemas.*.properties.* - then: + then: &type-or-ref-check - function: truthy - function: schema functionOptions: @@ -39,12 +39,12 @@ rules: - required: ["type"] - required: ["$ref"] - path-schema-properties-must-have-a-type: - description: "All path schema properties must have a type or schema reference. Refer to: https://gsoftdev.atlassian.net/wiki/spaces/TEC/pages/3858235678/IDP+OpenAPI+Rulesets#schemas-properties-must-have-a-type-and-path-schema-properties-must-have-a-type" + schemas-properties-must-have-a-type-frontend: + description: "All schemas properties must have a type, schema reference, or composition keyword (allOf/anyOf/oneOf). Refer to: https://gsoftdev.atlassian.net/wiki/spaces/TEC/pages/3858235678/IDP+OpenAPI+Rulesets#schemas-properties-must-have-a-type-and-path-schema-properties-must-have-a-type" recommended: true severity: warn - given: $..schema.properties.* - then: + given: $..schemas.*.properties.* + then: &type-or-composition-check - function: truthy - function: schema functionOptions: @@ -52,34 +52,51 @@ rules: anyOf: - required: ["type"] - required: ["$ref"] + - required: ["allOf"] + - required: ["anyOf"] + - required: ["oneOf"] + + path-schema-properties-must-have-a-type-backend: + description: "All path schema properties must have a type or schema reference. Refer to: https://gsoftdev.atlassian.net/wiki/spaces/TEC/pages/3858235678/IDP+OpenAPI+Rulesets#schemas-properties-must-have-a-type-and-path-schema-properties-must-have-a-type" + recommended: true + severity: warn + given: $..schema.properties.* + then: *type-or-ref-check - schema-object-must-have-a-type: + path-schema-properties-must-have-a-type-frontend: + description: "All path schema properties must have a type, schema reference, or composition keyword (allOf/anyOf/oneOf). Refer to: https://gsoftdev.atlassian.net/wiki/spaces/TEC/pages/3858235678/IDP+OpenAPI+Rulesets#schemas-properties-must-have-a-type-and-path-schema-properties-must-have-a-type" + recommended: true + severity: warn + given: $..schema.properties.* + then: *type-or-composition-check + + schema-object-must-have-a-type-backend: description: "All schema objects must have a type or schema reference. Refer to: https://gsoftdev.atlassian.net/wiki/spaces/TEC/pages/3858235678/IDP+OpenAPI+Rulesets#schema-object-must-have-a-type" recommended: true severity: warn given: $.components.schemas.* - then: - - function: truthy - - function: schema - functionOptions: - schema: - anyOf: - - required: ["type"] - - required: ["$ref"] + then: *type-or-ref-check + + schema-object-must-have-a-type-frontend: + description: "All schema objects must have a type, schema reference, or composition keyword (allOf/anyOf/oneOf). Refer to: https://gsoftdev.atlassian.net/wiki/spaces/TEC/pages/3858235678/IDP+OpenAPI+Rulesets#schema-object-must-have-a-type" + recommended: true + severity: warn + given: $.components.schemas.* + then: *type-or-composition-check - items-must-have-a-type: + items-must-have-a-type-backend: description: "All items must have a type or schema reference. Refer to: https://gsoftdev.atlassian.net/wiki/spaces/TEC/pages/3858235678/IDP+OpenAPI+Rulesets#items-must-have-a-type" recommended: true severity: warn given: $.components.schemas..items - then: - - function: truthy - - function: schema - functionOptions: - schema: - anyOf: - - required: ["type"] - - required: ["$ref"] + then: *type-or-ref-check + + items-must-have-a-type-frontend: + description: "All items must have a type, schema reference, or composition keyword (allOf/anyOf/oneOf). Refer to: https://gsoftdev.atlassian.net/wiki/spaces/TEC/pages/3858235678/IDP+OpenAPI+Rulesets#items-must-have-a-type" + recommended: true + severity: warn + given: $.components.schemas..items + then: *type-or-composition-check schema-name-length-must-be-short: description: "Schema name must not be too long to support client generation. Current limitation comes from Ruby packages which uses tar and has a limit of 100 characters. Refer to: https://gsoftdev.atlassian.net/wiki/spaces/TEC/pages/3858235678/IDP+OpenAPI+Rulesets#schema-name-length-must-be-short" @@ -147,4 +164,4 @@ rules: functionOptions: values: - get - - post \ No newline at end of file + - post diff --git a/TestSpecs/items-must-have-a-type-valid-composition.yaml b/TestSpecs/items-must-have-a-type-valid-composition.yaml new file mode 100644 index 0000000..914dcfc --- /dev/null +++ b/TestSpecs/items-must-have-a-type-valid-composition.yaml @@ -0,0 +1,21 @@ +openapi: 3.0.1 +info: + title: dummy +components: + schemas: + Signal: + type: object + properties: + id: + type: string + ReviewSignal: + type: object + properties: + reviewType: + type: string + SignalList: + type: array + items: + oneOf: + - $ref: '#/components/schemas/Signal' + - $ref: '#/components/schemas/ReviewSignal' diff --git a/TestSpecs/path-schema-properties-must-have-a-type-valid-composition.yaml b/TestSpecs/path-schema-properties-must-have-a-type-valid-composition.yaml new file mode 100644 index 0000000..608493d --- /dev/null +++ b/TestSpecs/path-schema-properties-must-have-a-type-valid-composition.yaml @@ -0,0 +1,25 @@ +openapi: 3.0.1 +info: + title: dummy +paths: + /test: + get: + operationId: getTest + responses: + '200': + description: Success + content: + application/json: + schema: + type: object + properties: + composedProperty: + oneOf: + - type: object + properties: + option1: + type: string + - type: object + properties: + option2: + type: integer diff --git a/TestSpecs/schema-object-must-have-a-type-valid-composition.yaml b/TestSpecs/schema-object-must-have-a-type-valid-composition.yaml new file mode 100644 index 0000000..dabe8d3 --- /dev/null +++ b/TestSpecs/schema-object-must-have-a-type-valid-composition.yaml @@ -0,0 +1,37 @@ +openapi: 3.0.1 +info: + title: dummy +components: + schemas: + BaseSchema: + type: object + properties: + id: + type: string + AllOfSchema: + allOf: + - $ref: '#/components/schemas/BaseSchema' + - type: object + properties: + extra: + type: string + AnyOfSchema: + anyOf: + - type: object + properties: + option1: + type: string + - type: object + properties: + option2: + type: integer + OneOfSchema: + oneOf: + - type: object + properties: + option1: + type: string + - type: object + properties: + option2: + type: integer diff --git a/TestSpecs/schemas-properties-must-have-a-type-valid-composition.yaml b/TestSpecs/schemas-properties-must-have-a-type-valid-composition.yaml new file mode 100644 index 0000000..a317d98 --- /dev/null +++ b/TestSpecs/schemas-properties-must-have-a-type-valid-composition.yaml @@ -0,0 +1,18 @@ +openapi: 3.0.1 +info: + title: dummy +components: + schemas: + SampleObject: + type: object + properties: + composedProperty: + allOf: + - type: object + properties: + field1: + type: string + - type: object + properties: + field2: + type: string diff --git a/test.ps1 b/test.ps1 index a593698..37f3a8f 100644 --- a/test.ps1 +++ b/test.ps1 @@ -8,9 +8,14 @@ spectral --version $ruleset = Join-Path $PSScriptRoot ".workleap.rules.yaml" $testSpecs = @( - @{ rule = "items-must-have-a-type"; expectError = $false; filename = "items-must-have-a-type-valid.yaml" }, - @{ rule = "items-must-have-a-type"; expectError = $false; filename = "items-must-have-a-type-valid-ref.yaml" }, - @{ rule = "items-must-have-a-type"; expectError = $true; filename = "items-must-have-a-type-invalid.yaml" }, + @{ rule = "items-must-have-a-type-backend"; expectError = $false; filename = "items-must-have-a-type-valid.yaml" }, + @{ rule = "items-must-have-a-type-backend"; expectError = $false; filename = "items-must-have-a-type-valid-ref.yaml" }, + @{ rule = "items-must-have-a-type-backend"; expectError = $true; filename = "items-must-have-a-type-valid-composition.yaml" }, + @{ rule = "items-must-have-a-type-backend"; expectError = $true; filename = "items-must-have-a-type-invalid.yaml" }, + @{ rule = "items-must-have-a-type-frontend"; expectError = $false; filename = "items-must-have-a-type-valid.yaml" }, + @{ rule = "items-must-have-a-type-frontend"; expectError = $false; filename = "items-must-have-a-type-valid-ref.yaml" }, + @{ rule = "items-must-have-a-type-frontend"; expectError = $false; filename = "items-must-have-a-type-valid-composition.yaml" }, + @{ rule = "items-must-have-a-type-frontend"; expectError = $true; filename = "items-must-have-a-type-invalid.yaml" }, @{ rule = "must-accept-content-types"; expectError = $false; filename = "must-accept-content-types-valid.yaml" }, @{ rule = "must-accept-content-types"; expectError = $true; filename = "must-accept-content-types-invalid.yaml" }, @{ rule = "must-not-use-base-server-url"; expectError = $false; filename = "must-not-use-base-server-url-valid.yaml" }, @@ -21,17 +26,32 @@ $testSpecs = @( @{ rule = "must-support-client-credentials-oauth2"; expectError = $true; filename = "must-support-client-credentials-oauth2-invalid.yaml" }, @{ rule = "must-use-get-post-methods"; expectError = $false; filename = "must-use-get-post-methods-valid.yaml" }, @{ rule = "must-use-get-post-methods"; expectError = $true; filename = "must-use-get-post-methods-invalid.yaml" }, - @{ rule = "path-schema-properties-must-have-a-type"; expectError = $false; filename = "path-schema-properties-must-have-a-type-valid.yaml" }, - @{ rule = "path-schema-properties-must-have-a-type"; expectError = $false; filename = "path-schema-properties-must-have-a-type-valid-ref.yaml" }, - @{ rule = "path-schema-properties-must-have-a-type"; expectError = $true; filename = "path-schema-properties-must-have-a-type-invalid.yaml" }, - @{ rule = "schemas-properties-must-have-a-type"; expectError = $false; filename = "schemas-properties-must-have-a-type-valid.yaml" }, - @{ rule = "schemas-properties-must-have-a-type"; expectError = $false; filename = "schemas-properties-must-have-a-type-valid-ref.yaml" }, - @{ rule = "schemas-properties-must-have-a-type"; expectError = $true; filename = "schemas-properties-must-have-a-type-invalid.yaml" }, + @{ rule = "path-schema-properties-must-have-a-type-backend"; expectError = $false; filename = "path-schema-properties-must-have-a-type-valid.yaml" }, + @{ rule = "path-schema-properties-must-have-a-type-backend"; expectError = $false; filename = "path-schema-properties-must-have-a-type-valid-ref.yaml" }, + @{ rule = "path-schema-properties-must-have-a-type-backend"; expectError = $true; filename = "path-schema-properties-must-have-a-type-valid-composition.yaml" }, + @{ rule = "path-schema-properties-must-have-a-type-backend"; expectError = $true; filename = "path-schema-properties-must-have-a-type-invalid.yaml" }, + @{ rule = "path-schema-properties-must-have-a-type-frontend"; expectError = $false; filename = "path-schema-properties-must-have-a-type-valid.yaml" }, + @{ rule = "path-schema-properties-must-have-a-type-frontend"; expectError = $false; filename = "path-schema-properties-must-have-a-type-valid-ref.yaml" }, + @{ rule = "path-schema-properties-must-have-a-type-frontend"; expectError = $false; filename = "path-schema-properties-must-have-a-type-valid-composition.yaml" }, + @{ rule = "path-schema-properties-must-have-a-type-frontend"; expectError = $true; filename = "path-schema-properties-must-have-a-type-invalid.yaml" }, + @{ rule = "schemas-properties-must-have-a-type-backend"; expectError = $false; filename = "schemas-properties-must-have-a-type-valid.yaml" }, + @{ rule = "schemas-properties-must-have-a-type-backend"; expectError = $false; filename = "schemas-properties-must-have-a-type-valid-ref.yaml" }, + @{ rule = "schemas-properties-must-have-a-type-backend"; expectError = $true; filename = "schemas-properties-must-have-a-type-valid-composition.yaml" }, + @{ rule = "schemas-properties-must-have-a-type-backend"; expectError = $true; filename = "schemas-properties-must-have-a-type-invalid.yaml" }, + @{ rule = "schemas-properties-must-have-a-type-frontend"; expectError = $false; filename = "schemas-properties-must-have-a-type-valid.yaml" }, + @{ rule = "schemas-properties-must-have-a-type-frontend"; expectError = $false; filename = "schemas-properties-must-have-a-type-valid-ref.yaml" }, + @{ rule = "schemas-properties-must-have-a-type-frontend"; expectError = $false; filename = "schemas-properties-must-have-a-type-valid-composition.yaml" }, + @{ rule = "schemas-properties-must-have-a-type-frontend"; expectError = $true; filename = "schemas-properties-must-have-a-type-invalid.yaml" }, @{ rule = "schema-ids-must-have-alphanumeric-characters-only"; expectError = $false; filename = "schema-ids-must-have-alphanumeric-characters-only-valid.yaml" }, @{ rule = "schema-ids-must-have-alphanumeric-characters-only"; expectError = $true; filename = "schema-ids-must-have-alphanumeric-characters-only-invalid.yaml" }, - @{ rule = "schema-object-must-have-a-type"; expectError = $false; filename = "schema-object-must-have-a-type-valid.yaml" }, - @{ rule = "schema-object-must-have-a-type"; expectError = $false; filename = "schema-object-must-have-a-type-valid-ref.yaml" }, - @{ rule = "schema-object-must-have-a-type"; expectError = $true; filename = "schema-object-must-have-a-type-invalid.yaml" }, + @{ rule = "schema-object-must-have-a-type-backend"; expectError = $false; filename = "schema-object-must-have-a-type-valid.yaml" }, + @{ rule = "schema-object-must-have-a-type-backend"; expectError = $false; filename = "schema-object-must-have-a-type-valid-ref.yaml" }, + @{ rule = "schema-object-must-have-a-type-backend"; expectError = $true; filename = "schema-object-must-have-a-type-valid-composition.yaml" }, + @{ rule = "schema-object-must-have-a-type-backend"; expectError = $true; filename = "schema-object-must-have-a-type-invalid.yaml" }, + @{ rule = "schema-object-must-have-a-type-frontend"; expectError = $false; filename = "schema-object-must-have-a-type-valid.yaml" }, + @{ rule = "schema-object-must-have-a-type-frontend"; expectError = $false; filename = "schema-object-must-have-a-type-valid-ref.yaml" }, + @{ rule = "schema-object-must-have-a-type-frontend"; expectError = $false; filename = "schema-object-must-have-a-type-valid-composition.yaml" }, + @{ rule = "schema-object-must-have-a-type-frontend"; expectError = $true; filename = "schema-object-must-have-a-type-invalid.yaml" }, @{ rule = "schema-name-length-must-be-short"; expectError = $false; filename = "schema-name-length-must-be-short-valid.yaml" }, @{ rule = "schema-name-length-must-be-short"; expectError = $true; filename = "schema-name-length-must-be-short-invalid.yaml" } ) @@ -39,8 +59,9 @@ $testSpecs = @( function RunSpectralTests($ruleset, $tests, $testSpecsPath) { $fileCount = Get-ChildItem (Join-Path $PSScriptRoot $testSpecsPath) | Measure-Object | Select-Object -ExpandProperty Count - if ($tests.Count -ne $fileCount) { - throw "Number of tests does not match number of specs. Add the missing specs to the `$tests variable. Files: $fileCount, Tests: $($tests.Count)" + $uniqueTestFiles = $tests | ForEach-Object { $_.filename } | Sort-Object -Unique | Measure-Object | Select-Object -ExpandProperty Count + if ($uniqueTestFiles -ne $fileCount) { + throw "Number of unique test spec files does not match number of specs. Add the missing specs to the `$tests variable. Files: $fileCount, Unique test files: $uniqueTestFiles" } # We expect spectral to fail, so we need to capture the error