Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/olive-pugs-repeat.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@redocly/openapi-core': minor
'@redocly/cli': minor
---

Added an `allowDefault` option to the `operation-2xx-response` rule, which controls whether a `default` response satisfies the rule. Defaults to `true`, matching the previous behavior.
23 changes: 23 additions & 0 deletions docs/@v2/rules/oas/operation-2xx-response.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ You can greatly improve the developer and user experience of your APIs by making
| ---------------- | ------- | ----------------------------------------------------------------------------------------- |
| severity | string | Possible values: `off`, `warn`, `error`. Default `warn` (in `recommended` configuration). |
| validateWebhooks | boolean | Determines if responses inside webhooks are validated. Default `false`. |
| allowDefault | boolean | Determines if a `default` response satisfies the rule. Default `true`. |

An example configuration:

Expand All @@ -45,6 +46,28 @@ rules:
validateWebhooks: true
```

By default, a `default` response counts as a successful response.
Set `allowDefault: false` to require an explicit 2xx status code:

```yaml
rules:
operation-2xx-response:
severity: error
allowDefault: false
```

With `allowDefault: false`, the following operation is reported, because `default` describes the responses the operation does not list rather than what a successful call returns:

```yaml
post:
responses:
default:
$ref: ../components/responses/Problem.yaml
```

This matters for code generation: a generator reads the 2xx response to produce the return type of the operation.
A `default`-only operation gives it no success shape to model, so the generated client falls back to an untyped or empty result.

## Examples

Given this configuration:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,50 @@ describe('Oas3 operation-2xx-response', () => {
expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`);
});

it('should report for present default when allowDefault is false', async () => {
const document = parseYamlToDocument(
outdent`
openapi: 3.0.0
paths:
'/test/':
put:
responses:
default:
description: ok
`,
'foobar.yaml'
);

const results = await lintDocument({
externalRefResolver: new BaseResolver(),
document,
config: await createConfig({
rules: {
'operation-2xx-response': { severity: 'error', allowDefault: false },
},
}),
});

expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
[
{
"location": [
{
"pointer": "#/paths/~1test~1/put/responses",
"reportOnKey": true,
"source": "foobar.yaml",
},
],
"message": "Operation must have at least one \`2XX\` response.",
"reference": "https://redocly.com/docs/cli/rules/oas/operation-2xx-response",
"ruleId": "operation-2xx-response",
"severity": "error",
"suggest": [],
},
]
`);
});

it('should report even if the responses are null', async () => {
const document = parseYamlToDocument(
outdent`
Expand Down
7 changes: 6 additions & 1 deletion packages/core/src/rules/common/operation-2xx-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ import type { Oas3Rule, Oas2Rule } from '../../visitors.js';
import type { UserContext } from '../../walk.js';
import { validateResponseCodes } from '../utils.js';

export const Operation2xxResponse: Oas3Rule | Oas2Rule = ({ validateWebhooks }) => {
export const Operation2xxResponse: Oas3Rule | Oas2Rule = ({
validateWebhooks,
allowDefault = true,
}) => {
return {
Paths: {
Responses(responses: Record<string, object>, { report }: UserContext) {
Expand All @@ -13,6 +16,7 @@ export const Operation2xxResponse: Oas3Rule | Oas2Rule = ({ validateWebhooks })
codeRange: '2XX',
report: report as UserContext['report'],
reference: 'https://redocly.com/docs/cli/rules/oas/operation-2xx-response',
allowDefault,
});
},
},
Expand All @@ -27,6 +31,7 @@ export const Operation2xxResponse: Oas3Rule | Oas2Rule = ({ validateWebhooks })
codeRange: '2XX',
report: report as UserContext['report'],
reference: 'https://redocly.com/docs/cli/rules/oas/operation-2xx-response',
allowDefault,
});
},
},
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/rules/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,17 +390,19 @@ export function validateResponseCodes({
codeRange,
report,
reference,
allowDefault = true,
}: {
responseCodes: string[];
codeRange: string;
report: UserContext['report'];
reference?: string;
allowDefault?: boolean;
}) {
const responseCodeRegexp = new RegExp(`^${codeRange[0]}[0-9Xx]{2}$`);

const containsNeededCode = responseCodes.some(
(code) =>
(codeRange === '2XX' && code === 'default') || // It's OK to replace 2xx codes with the default
(allowDefault && codeRange === '2XX' && code === 'default') || // It's OK to replace 2xx codes with the default
responseCodeRegexp.test(code)
);

Expand Down
Loading