diff --git a/README.md b/README.md index bfbbe86..d82ed5b 100644 --- a/README.md +++ b/README.md @@ -228,7 +228,7 @@ npm run lint-fix # format source files | [#229][#229] | [SHOULD consider to design `POST` and `PATCH` idempotent][#229] | :grey_exclamation: | :grey_exclamation: | - | | [#230][#230] | [MAY consider to support `Idempotency-Key` header][#230] | :grey_exclamation: | :grey_exclamation: | - | | [#231][#231] | [Should use secondary key for idempotent `POST` design][#231] | :grey_exclamation: | :grey_exclamation: | - | -| [#233a][#233a] | [MUST request must use b3 tracing headers][#233a] | - | :heavy_check_mark: | - | +| [#233a][#233a] | [MUST request must use tracing headers][#233a] | - | :heavy_check_mark: | - | | [#234][#234] | [MUST only use durable and immutable remote references][#234] | :grey_exclamation: | :grey_exclamation: | - | | [#235][#235] | [SHOULD name date/time properties with `_at` suffix][#235] | - | - | - | | [#236][#236] | [SHOULD design simple query languages using query parameters][#236] | :grey_exclamation: | :grey_exclamation: | - | @@ -368,7 +368,7 @@ npm run lint-fix # format source files [#230]: https://opensource.zalando.com/restful-api-guidelines/#230 [#231]: https://opensource.zalando.com/restful-api-guidelines/#231 [#233]: https://opensource.zalando.com/restful-api-guidelines/#233 -[#233a]: ./doc/rules/requests-must-use-b3-tracing.md +[#233a]: ./doc/rules/requests-must-use-tracing.md [#234]: https://opensource.zalando.com/restful-api-guidelines/#234 [#235]: https://opensource.zalando.com/restful-api-guidelines/#235 [#236]: https://opensource.zalando.com/restful-api-guidelines/#236 diff --git a/baloise.yml b/baloise.yml index 78a6d8f..f7d7136 100644 --- a/baloise.yml +++ b/baloise.yml @@ -1,7 +1,7 @@ extends: [./zalando.yml] # We're extending the zalando ruleset by replacing or adding baloise specific rules. functions: - - validate-b3-tracing + - validate-tracing - assert-http-codes-for-operation rules: @@ -149,21 +149,21 @@ rules: 'default': [ ALL ] - # MUST request must provide b3 tracing [233a] + # MUST request must provide tracing [233a] # => Alternative to https://opensource.zalando.com/restful-api-guidelines/#233 - must-use-b3-tracing: - message: B3 header X-B3-Traceid or X-B3-Spanid missing - description: MUST use b3 tracing [233a] - documentationUrl: https://github.com/baloise-incubator/spectral-ruleset/blob/main/doc/rules/requests-must-use-b3-tracing.md + must-use-tracing: + message: Header X-B3-Traceid, X-B3-Spanid or traceparent (w3c) missing + description: MUST use b3 or w3c tracing [233a] + documentationUrl: https://github.com/baloise-incubator/spectral-ruleset/blob/main/doc/rules/requests-must-use-tracing.md severity: error given: $.paths.* then: - function: validate-b3-tracing + function: validate-tracing - # MUST provide API audience [219] - # => https://opensource.zalando.com/restful-api-guidelines/#219 + # MUST provide API audience [219] + # => https://opensource.zalando.com/restful-api-guidelines/#219 must-provide-api-audience: off diff --git a/doc/rules/requests-must-use-b3-tracing.md b/doc/rules/requests-must-use-tracing.md similarity index 66% rename from doc/rules/requests-must-use-b3-tracing.md rename to doc/rules/requests-must-use-tracing.md index f545f52..138b1ce 100644 --- a/doc/rules/requests-must-use-b3-tracing.md +++ b/doc/rules/requests-must-use-tracing.md @@ -1,8 +1,10 @@ -# MUST request must provide b3 tracing headers +# MUST request must provide tracing headers The following header parameter must be provided for any request: - `X-B3-Traceid` - `X-B3-Spanid` +or +- `traceparent` -Specification: You can find the specification for b3 [here](https://github.com/openzipkin/b3-propagation) +Specification: You can find the specification for b3 [here](https://github.com/openzipkin/b3-propagation) and w3c [here](https://www.w3.org/TR/trace-context/) Rationale: We have an internal survey with all developers and the decision was b3 [Internal link](https://confluence.baloisenet.com/atlassian/x/LxWJj). \ No newline at end of file diff --git a/functions/validate-b3-tracing.js b/functions/validate-tracing.js similarity index 58% rename from functions/validate-b3-tracing.js rename to functions/validate-tracing.js index 1ebff30..8e8d19e 100644 --- a/functions/validate-b3-tracing.js +++ b/functions/validate-tracing.js @@ -17,8 +17,22 @@ function isB3Valid(targetValue) { return !(b3Params.length !== 2 || !b3Params.every((param) => param.in === 'header')); } +function isW3cValid(targetValue) { + if (!Array.isArray(targetValue)) { + return [ + { + message: `No array given, provide $.paths.*.*.parameters`, + }, + ]; + } + + const w3cParams = targetValue.filter((param) => param.name && param.name.toLowerCase() === 'traceparent'); + + return !(w3cParams.length !== 1 || !w3cParams.every((param) => param.in === 'header')); +} + export default (targetValue) => { - if (targetValue.parameters && isB3Valid(targetValue.parameters)) { + if (targetValue.parameters && (isB3Valid(targetValue.parameters) || isW3cValid(targetValue.parameters))) { return []; } @@ -27,12 +41,12 @@ export default (targetValue) => { ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'].includes(verb.toLowerCase()), ) .map((verb) => targetValue[verb]) - .some((operation) => operation.parameters && !isB3Valid(operation.parameters)); + .some((operation) => operation.parameters && !isB3Valid(operation.parameters) && !isW3cValid(operation.parameters)); if (hasInvalidEntry) { return [ { - message: `B3 header X-B3-Traceid or X-B3-Spanid missing`, + message: `Header X-B3-Traceid, X-B3-Spanid or traceparent (w3c) missing`, }, ]; } diff --git a/tests/233a-MUST-request-must-provide-b3-tracing.test.ts b/tests/233a-MUST-request-must-provide-b3-tracing.test.ts index aba4479..0aaf45c 100644 --- a/tests/233a-MUST-request-must-provide-b3-tracing.test.ts +++ b/tests/233a-MUST-request-must-provide-b3-tracing.test.ts @@ -11,8 +11,8 @@ describe('MUST request must provide b3 tracing [233a]', () => { const result = await lint(openApi, 'baloise'); expect(result).toEqual([ expect.objectContaining({ - code: 'must-use-b3-tracing', - message: 'B3 header X-B3-Traceid or X-B3-Spanid missing', + code: 'must-use-tracing', + message: 'Header X-B3-Traceid, X-B3-Spanid or traceparent (w3c) missing', severity: DiagnosticSeverity.Error, }), ]); @@ -27,8 +27,8 @@ describe('MUST request must provide b3 tracing [233a]', () => { const result = await lint(openApi, 'baloise'); expect(result).toEqual([ expect.objectContaining({ - code: 'must-use-b3-tracing', - message: 'B3 header X-B3-Traceid or X-B3-Spanid missing', + code: 'must-use-tracing', + message: 'Header X-B3-Traceid, X-B3-Spanid or traceparent (w3c) missing', severity: DiagnosticSeverity.Error, }), ]); @@ -59,4 +59,30 @@ describe('MUST request must provide b3 tracing [233a]', () => { const result = await lint(openApi, 'baloise'); expect(result).toEqual([]); }); + + test('Assert w3c tracing headers valid', async () => { + const openApi = await loadOpenApiSpec('base-openapi.yml'); + openApi.paths['/example'].get.parameters = openApi.paths['/example'].get.parameters.filter( + (param: { $ref: string }) => + !param['$ref'] && + param['$ref'] !== '#/components/parameters/HeaderB3TraceId' && + param['$ref'] !== '#/components/parameters/HeaderB3SpanId', + ); + openApi.paths['/example'].get.parameters.push({ + $ref: '#/components/parameters/HeaderW3cTraceparent', + }); + + const result = await lint(openApi, 'baloise'); + expect(result).toEqual([]); + }); + + test('Assert w3c and b3 tracing headers valid', async () => { + const openApi = await loadOpenApiSpec('base-openapi.yml'); + openApi.paths['/example'].get.parameters.push({ + $ref: '#/components/parameters/HeaderW3cTraceparent', + }); + + const result = await lint(openApi, 'baloise'); + expect(result).toEqual([]); + }); }); diff --git a/tests/fixtures/base-openapi.yml b/tests/fixtures/base-openapi.yml index 18133f6..8410b7f 100644 --- a/tests/fixtures/base-openapi.yml +++ b/tests/fixtures/base-openapi.yml @@ -120,6 +120,12 @@ components: required: false schema: type: string + HeaderW3cTraceparent: + in: header + name: traceparent + required: true + schema: + type: string schemas: Example: type: object