@redocly/openapi-core 1.0.0-beta.108 → 1.0.0-beta.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/lib/benchmark/benches/resolve-with-no-external.bench.js +1 -1
  2. package/lib/bundle.d.ts +1 -1
  3. package/lib/bundle.js +4 -4
  4. package/lib/config/all.js +3 -1
  5. package/lib/config/config-resolvers.js +1 -1
  6. package/lib/config/minimal.js +3 -1
  7. package/lib/config/recommended.js +3 -1
  8. package/lib/config/rules.js +1 -1
  9. package/lib/config/types.d.ts +7 -0
  10. package/lib/config/utils.d.ts +2 -2
  11. package/lib/config/utils.js +42 -4
  12. package/lib/decorators/common/registry-dependencies.js +1 -1
  13. package/lib/format/format.d.ts +1 -1
  14. package/lib/format/format.js +22 -1
  15. package/lib/lint.js +2 -2
  16. package/lib/redocly/registry-api.d.ts +0 -1
  17. package/lib/redocly/registry-api.js +5 -4
  18. package/lib/resolve.js +3 -1
  19. package/lib/rules/common/no-ambiguous-paths.js +1 -1
  20. package/lib/rules/common/no-identical-paths.js +1 -1
  21. package/lib/rules/common/operation-2xx-response.js +1 -1
  22. package/lib/rules/common/operation-4xx-response.js +1 -1
  23. package/lib/rules/common/operation-operationId.js +1 -1
  24. package/lib/rules/common/operation-tag-defined.js +1 -1
  25. package/lib/rules/common/path-not-include-query.js +1 -1
  26. package/lib/rules/common/security-defined.d.ts +2 -0
  27. package/lib/rules/common/{operation-security-defined.js → security-defined.js} +18 -4
  28. package/lib/rules/common/spec.js +12 -1
  29. package/lib/rules/common/tags-alphabetical.js +1 -1
  30. package/lib/rules/oas2/index.d.ts +1 -1
  31. package/lib/rules/oas2/index.js +2 -2
  32. package/lib/rules/oas2/remove-unused-components.js +1 -1
  33. package/lib/rules/oas2/request-mime-type.js +1 -1
  34. package/lib/rules/oas2/response-mime-type.js +1 -1
  35. package/lib/rules/oas3/index.js +6 -2
  36. package/lib/rules/oas3/no-empty-servers.js +1 -1
  37. package/lib/rules/oas3/no-server-variables-empty-enum.js +1 -1
  38. package/lib/rules/oas3/no-unused-components.js +1 -1
  39. package/lib/rules/oas3/operation-4xx-problem-details-rfc7807.d.ts +5 -0
  40. package/lib/rules/oas3/operation-4xx-problem-details-rfc7807.js +36 -0
  41. package/lib/rules/oas3/remove-unused-components.js +1 -1
  42. package/lib/rules/oas3/request-mime-type.js +1 -1
  43. package/lib/rules/oas3/response-mime-type.js +1 -1
  44. package/lib/rules/oas3/spec-components-invalid-map-name.d.ts +2 -0
  45. package/lib/rules/oas3/spec-components-invalid-map-name.js +46 -0
  46. package/lib/rules/other/stats.d.ts +2 -2
  47. package/lib/rules/other/stats.js +2 -2
  48. package/lib/types/oas2.js +5 -5
  49. package/lib/types/oas3.js +27 -20
  50. package/lib/types/oas3_1.js +3 -3
  51. package/lib/types/redocly-yaml.js +46 -55
  52. package/lib/utils.d.ts +3 -3
  53. package/lib/utils.js +5 -5
  54. package/lib/visitors.d.ts +11 -11
  55. package/lib/visitors.js +13 -1
  56. package/package.json +1 -1
  57. package/src/__tests__/__snapshots__/bundle.test.ts.snap +3 -3
  58. package/src/__tests__/fixtures/extension.js +3 -3
  59. package/src/__tests__/format.test.ts +76 -0
  60. package/src/__tests__/lint.test.ts +106 -131
  61. package/src/__tests__/resolve-http.test.ts +1 -1
  62. package/src/__tests__/resolve.test.ts +9 -9
  63. package/src/__tests__/walk.test.ts +78 -10
  64. package/src/benchmark/benches/resolve-with-no-external.bench.ts +1 -1
  65. package/src/bundle.ts +4 -4
  66. package/src/config/__tests__/__snapshots__/config-resolvers.test.ts.snap +6 -2
  67. package/src/config/__tests__/fixtures/plugin-config.yaml +2 -3
  68. package/src/config/__tests__/fixtures/resolve-config/api/nested-config.yaml +11 -12
  69. package/src/config/__tests__/fixtures/resolve-config/local-config-with-circular.yaml +7 -8
  70. package/src/config/__tests__/fixtures/resolve-config/local-config-with-file.yaml +18 -19
  71. package/src/config/__tests__/fixtures/resolve-config/local-config.yaml +9 -10
  72. package/src/config/__tests__/fixtures/resolve-remote-configs/nested-remote-config.yaml +3 -4
  73. package/src/config/__tests__/fixtures/resolve-remote-configs/remote-config.yaml +4 -5
  74. package/src/config/__tests__/load.test.ts +12 -15
  75. package/src/config/__tests__/utils.test.ts +64 -4
  76. package/src/config/all.ts +3 -1
  77. package/src/config/config-resolvers.ts +2 -2
  78. package/src/config/load.ts +3 -2
  79. package/src/config/minimal.ts +3 -1
  80. package/src/config/recommended.ts +3 -1
  81. package/src/config/rules.ts +2 -2
  82. package/src/config/types.ts +11 -0
  83. package/src/config/utils.ts +102 -13
  84. package/src/decorators/common/registry-dependencies.ts +1 -1
  85. package/src/format/format.ts +32 -2
  86. package/src/lint.ts +2 -2
  87. package/src/redocly/registry-api.ts +5 -4
  88. package/src/resolve.ts +3 -1
  89. package/src/rules/__tests__/utils.test.ts +1 -1
  90. package/src/rules/common/__tests__/no-enum-type-mismatch.test.ts +1 -0
  91. package/src/rules/common/__tests__/operation-2xx-response.test.ts +1 -1
  92. package/src/rules/common/__tests__/operation-4xx-response.test.ts +26 -3
  93. package/src/rules/common/__tests__/security-defined.test.ts +175 -0
  94. package/src/rules/common/__tests__/spec.test.ts +79 -0
  95. package/src/rules/common/assertions/__tests__/utils.test.ts +2 -2
  96. package/src/rules/common/no-ambiguous-paths.ts +1 -1
  97. package/src/rules/common/no-identical-paths.ts +1 -1
  98. package/src/rules/common/operation-2xx-response.ts +1 -1
  99. package/src/rules/common/operation-4xx-response.ts +1 -1
  100. package/src/rules/common/operation-operationId.ts +1 -1
  101. package/src/rules/common/operation-tag-defined.ts +1 -1
  102. package/src/rules/common/path-not-include-query.ts +1 -1
  103. package/src/rules/common/{operation-security-defined.ts → security-defined.ts} +19 -4
  104. package/src/rules/common/spec.ts +15 -1
  105. package/src/rules/common/tags-alphabetical.ts +1 -1
  106. package/src/rules/oas2/index.ts +2 -2
  107. package/src/rules/oas2/remove-unused-components.ts +1 -1
  108. package/src/rules/oas2/request-mime-type.ts +1 -1
  109. package/src/rules/oas2/response-mime-type.ts +1 -1
  110. package/src/rules/oas3/__tests__/operation-4xx-problem-details-rfc7807.test.ts +145 -0
  111. package/src/rules/oas3/__tests__/spec/spec.test.ts +10 -0
  112. package/src/rules/oas3/__tests__/spec-components-invalid-map-name.test.ts +217 -0
  113. package/src/rules/oas3/index.ts +6 -2
  114. package/src/rules/oas3/no-empty-servers.ts +1 -1
  115. package/src/rules/oas3/no-server-variables-empty-enum.ts +1 -1
  116. package/src/rules/oas3/no-unused-components.ts +1 -1
  117. package/src/rules/oas3/operation-4xx-problem-details-rfc7807.ts +36 -0
  118. package/src/rules/oas3/remove-unused-components.ts +1 -1
  119. package/src/rules/oas3/request-mime-type.ts +1 -1
  120. package/src/rules/oas3/response-mime-type.ts +1 -1
  121. package/src/rules/oas3/spec-components-invalid-map-name.ts +53 -0
  122. package/src/rules/other/stats.ts +2 -2
  123. package/src/types/index.ts +2 -2
  124. package/src/types/oas2.ts +5 -5
  125. package/src/types/oas3.ts +27 -20
  126. package/src/types/oas3_1.ts +3 -3
  127. package/src/types/redocly-yaml.ts +52 -40
  128. package/src/utils.ts +11 -7
  129. package/src/visitors.ts +29 -13
  130. package/tsconfig.tsbuildinfo +1 -1
  131. package/lib/rules/common/operation-security-defined.d.ts +0 -2
  132. package/src/rules/common/__tests__/operation-security-defined.test.ts +0 -69
@@ -24,7 +24,7 @@ export default {
24
24
  'operation-operationId-unique': 'warn',
25
25
  'operation-parameters-unique': 'warn',
26
26
  'operation-tag-defined': 'off',
27
- 'operation-security-defined': 'warn',
27
+ 'security-defined': 'warn',
28
28
  'operation-operationId-url-safe': 'warn',
29
29
  'operation-singular-tag': 'off',
30
30
  'no-unresolved-refs': 'error',
@@ -45,6 +45,7 @@ export default {
45
45
  'no-unused-components': 'warn',
46
46
  'no-undefined-server-variable': 'warn',
47
47
  'no-server-variables-empty-enum': 'error',
48
+ 'spec-components-invalid-map-name': 'warn',
48
49
  },
49
50
  oas3_1Rules: {
50
51
  'no-invalid-media-type-examples': 'warn',
@@ -55,5 +56,6 @@ export default {
55
56
  'no-unused-components': 'warn',
56
57
  'no-undefined-server-variable': 'warn',
57
58
  'no-server-variables-empty-enum': 'error',
59
+ 'spec-components-invalid-map-name': 'warn',
58
60
  },
59
61
  } as PluginStyleguideConfig;
@@ -25,7 +25,7 @@ export default {
25
25
  'operation-operationId-url-safe': 'error',
26
26
  'operation-parameters-unique': 'error',
27
27
  'operation-tag-defined': 'off',
28
- 'operation-security-defined': 'error',
28
+ 'security-defined': 'error',
29
29
  'operation-singular-tag': 'off',
30
30
  'no-unresolved-refs': 'error',
31
31
  'no-enum-type-mismatch': 'error',
@@ -45,6 +45,7 @@ export default {
45
45
  'no-unused-components': 'warn',
46
46
  'no-undefined-server-variable': 'error',
47
47
  'no-server-variables-empty-enum': 'error',
48
+ 'spec-components-invalid-map-name': 'error',
48
49
  },
49
50
  oas3_1Rules: {
50
51
  'no-invalid-media-type-examples': 'warn',
@@ -55,5 +56,6 @@ export default {
55
56
  'no-unused-components': 'warn',
56
57
  'no-undefined-server-variable': 'error',
57
58
  'no-server-variables-empty-enum': 'error',
59
+ 'spec-components-invalid-map-name': 'error',
58
60
  },
59
61
  } as PluginStyleguideConfig;
@@ -1,6 +1,6 @@
1
1
  import { RuleSet, OasVersion } from '../oas-types';
2
2
  import { StyleguideConfig } from './config';
3
- import { notUndefined } from '../utils';
3
+ import { isDefined } from '../utils';
4
4
 
5
5
  export function initRules<T extends Function, P extends RuleSet<T>>(
6
6
  rules: P[],
@@ -42,5 +42,5 @@ export function initRules<T extends Function, P extends RuleSet<T>>(
42
42
  })
43
43
  )
44
44
  .flatMap((visitor) => visitor)
45
- .filter(notUndefined);
45
+ .filter(isDefined);
46
46
  }
@@ -110,6 +110,7 @@ export type ResolveHeader =
110
110
 
111
111
  export type RawResolveConfig = {
112
112
  http?: Partial<HttpResolveConfig>;
113
+ doNotResolveExamples?: boolean;
113
114
  };
114
115
 
115
116
  export type HttpResolveConfig = {
@@ -128,6 +129,7 @@ export type AccessTokens = { [region in Region]?: string };
128
129
  export type DeprecatedInRawConfig = {
129
130
  apiDefinitions?: Record<string, string>;
130
131
  lint?: StyleguideRawConfig;
132
+ styleguide?: StyleguideRawConfig;
131
133
  referenceDocs?: Record<string, any>;
132
134
  apis?: Record<string, Api & DeprecatedInApi>;
133
135
  };
@@ -153,6 +155,15 @@ export type RawConfig = {
153
155
  organization?: string;
154
156
  } & FeaturesConfig;
155
157
 
158
+ export type FlatApi = Omit<Api, 'styleguide'> &
159
+ Omit<ApiStyleguideRawConfig, 'doNotResolveExamples'>;
160
+
161
+ export type FlatRawConfig = Omit<RawConfig, 'styleguide' | 'resolve' | 'apis'> &
162
+ Omit<StyleguideRawConfig, 'doNotResolveExamples'> & {
163
+ resolve?: RawResolveConfig;
164
+ apis?: Record<string, FlatApi>;
165
+ };
166
+
156
167
  export type ResolvedConfig = Omit<RawConfig, 'apis' | 'styleguide'> & {
157
168
  apis: Record<string, ResolvedApi>;
158
169
  styleguide: ResolvedStyleguideConfig;
@@ -1,21 +1,24 @@
1
1
  import {
2
2
  assignExisting,
3
+ isDefined,
3
4
  isTruthy,
4
5
  showErrorForDeprecatedField,
5
6
  showWarningForDeprecatedField,
6
7
  } from '../utils';
7
8
  import { Config } from './config';
8
-
9
9
  import type {
10
10
  Api,
11
11
  DeprecatedInApi,
12
12
  DeprecatedInRawConfig,
13
+ FlatApi,
14
+ FlatRawConfig,
13
15
  Plugin,
14
16
  RawConfig,
15
17
  RawResolveConfig,
16
18
  ResolveConfig,
17
19
  ResolvedStyleguideConfig,
18
20
  RulesFields,
21
+ StyleguideRawConfig,
19
22
  } from './types';
20
23
  import { logger, colorize } from '../logger';
21
24
 
@@ -39,13 +42,86 @@ export function transformApiDefinitionsToApis(
39
42
  return apis;
40
43
  }
41
44
 
45
+ function extractFlatConfig<
46
+ T extends Partial<
47
+ (Api & DeprecatedInApi & FlatApi) & (DeprecatedInRawConfig & RawConfig & FlatRawConfig)
48
+ >
49
+ >({
50
+ plugins,
51
+ extends: _extends,
52
+
53
+ rules,
54
+ oas2Rules,
55
+ oas3_0Rules,
56
+ oas3_1Rules,
57
+
58
+ preprocessors,
59
+ oas2Preprocessors,
60
+ oas3_0Preprocessors,
61
+ oas3_1Preprocessors,
62
+
63
+ decorators,
64
+ oas2Decorators,
65
+ oas3_0Decorators,
66
+ oas3_1Decorators,
67
+
68
+ ...rawConfigRest
69
+ }: T): {
70
+ styleguideConfig?: StyleguideRawConfig;
71
+ rawConfigRest: Omit<T, 'plugins' | 'extends' | RulesFields>;
72
+ } {
73
+ const styleguideConfig = {
74
+ plugins,
75
+ extends: _extends,
76
+
77
+ rules,
78
+ oas2Rules,
79
+ oas3_0Rules,
80
+ oas3_1Rules,
81
+
82
+ preprocessors,
83
+ oas2Preprocessors,
84
+ oas3_0Preprocessors,
85
+ oas3_1Preprocessors,
86
+
87
+ decorators,
88
+ oas2Decorators,
89
+ oas3_0Decorators,
90
+ oas3_1Decorators,
91
+
92
+ doNotResolveExamples: (rawConfigRest as FlatRawConfig).resolve?.doNotResolveExamples,
93
+ };
94
+
95
+ if (
96
+ (rawConfigRest.lint && rawConfigRest.styleguide) ||
97
+ (Object.values(styleguideConfig).some(isDefined) &&
98
+ (rawConfigRest.lint || rawConfigRest.styleguide))
99
+ ) {
100
+ throw new Error(
101
+ `Do not use 'lint', 'styleguide' and flat syntax together. \nSee more about the configuration in the docs: https://redocly.com/docs/cli/configuration/ \n`
102
+ );
103
+ }
104
+
105
+ return {
106
+ styleguideConfig: Object.values(styleguideConfig).some(isDefined)
107
+ ? styleguideConfig
108
+ : undefined,
109
+
110
+ rawConfigRest,
111
+ };
112
+ }
113
+
42
114
  function transformApis(
43
- legacyApis?: Record<string, Api & DeprecatedInApi>
115
+ legacyApis?: Record<string, Api & DeprecatedInApi & FlatApi>
44
116
  ): Record<string, Api> | undefined {
45
117
  if (!legacyApis) return undefined;
46
118
  const apis: Record<string, Api> = {};
47
119
  for (const [apiName, { lint, ...apiContent }] of Object.entries(legacyApis)) {
48
- apis[apiName] = { styleguide: lint, ...apiContent };
120
+ const { styleguideConfig, rawConfigRest } = extractFlatConfig(apiContent);
121
+ apis[apiName] = {
122
+ styleguide: styleguideConfig || lint,
123
+ ...rawConfigRest,
124
+ };
49
125
  }
50
126
  return apis;
51
127
  }
@@ -165,17 +241,22 @@ export function getMergedConfig(config: Config, apiName?: string): Config {
165
241
  }
166
242
 
167
243
  function checkForDeprecatedFields(
168
- deprecatedField: keyof DeprecatedInRawConfig,
169
- updatedField: keyof RawConfig,
170
- rawConfig: DeprecatedInRawConfig & RawConfig
244
+ deprecatedField: keyof (DeprecatedInRawConfig & RawConfig),
245
+ updatedField: keyof FlatRawConfig | undefined,
246
+ rawConfig: DeprecatedInRawConfig & RawConfig & FlatRawConfig
171
247
  ): void {
172
248
  const isDeprecatedFieldInApis =
173
249
  rawConfig.apis &&
174
250
  Object.values(rawConfig.apis).some(
175
- (api: Api & DeprecatedInApi & DeprecatedInRawConfig) => api[deprecatedField]
251
+ (api: Api & FlatApi & DeprecatedInApi & DeprecatedInRawConfig & RawConfig & FlatRawConfig) =>
252
+ api[deprecatedField]
176
253
  );
177
254
 
178
- if (rawConfig[deprecatedField] && rawConfig[updatedField]) {
255
+ if (rawConfig[deprecatedField] && updatedField === null) {
256
+ showWarningForDeprecatedField(deprecatedField);
257
+ }
258
+
259
+ if (rawConfig[deprecatedField] && updatedField && rawConfig[updatedField]) {
179
260
  showErrorForDeprecatedField(deprecatedField, updatedField);
180
261
  }
181
262
 
@@ -184,11 +265,17 @@ function checkForDeprecatedFields(
184
265
  }
185
266
  }
186
267
 
187
- export function transformConfig(rawConfig: DeprecatedInRawConfig & RawConfig): RawConfig {
188
- const migratedFields: [keyof DeprecatedInRawConfig, keyof RawConfig][] = [
268
+ export function transformConfig(
269
+ rawConfig: DeprecatedInRawConfig & RawConfig & FlatRawConfig
270
+ ): RawConfig {
271
+ const migratedFields: [
272
+ keyof (DeprecatedInRawConfig & RawConfig),
273
+ keyof FlatRawConfig | undefined
274
+ ][] = [
189
275
  ['apiDefinitions', 'apis'],
190
276
  ['referenceDocs', 'features.openapi'],
191
- ['lint', 'styleguide'], // TODO: update docs
277
+ ['lint', undefined],
278
+ ['styleguide', undefined],
192
279
  ];
193
280
 
194
281
  for (const [deprecatedField, updatedField] of migratedFields) {
@@ -197,11 +284,13 @@ export function transformConfig(rawConfig: DeprecatedInRawConfig & RawConfig): R
197
284
 
198
285
  const { apis, apiDefinitions, referenceDocs, lint, ...rest } = rawConfig;
199
286
 
287
+ const { styleguideConfig, rawConfigRest } = extractFlatConfig(rest);
288
+
200
289
  return {
201
290
  'features.openapi': referenceDocs,
202
291
  apis: transformApis(apis) || transformApiDefinitionsToApis(apiDefinitions),
203
- styleguide: lint,
204
- ...rest,
292
+ styleguide: styleguideConfig || lint,
293
+ ...rawConfigRest,
205
294
  };
206
295
  }
207
296
 
@@ -7,7 +7,7 @@ export const RegistryDependencies: Oas3Decorator | Oas2Decorator = () => {
7
7
  const registryDependencies = new Set<string>();
8
8
 
9
9
  return {
10
- DefinitionRoot: {
10
+ Root: {
11
11
  leave(_: any, ctx: UserContext) {
12
12
  const data = ctx.getVisitorData();
13
13
  data.links = Array.from(registryDependencies);
@@ -44,7 +44,13 @@ function severityToNumber(severity: ProblemSeverity) {
44
44
  return severity === 'error' ? 1 : 2;
45
45
  }
46
46
 
47
- export type OutputFormat = 'codeframe' | 'stylish' | 'json' | 'checkstyle' | 'codeclimate';
47
+ export type OutputFormat =
48
+ | 'codeframe'
49
+ | 'stylish'
50
+ | 'json'
51
+ | 'checkstyle'
52
+ | 'codeclimate'
53
+ | 'summary';
48
54
 
49
55
  export function getTotals(problems: (NormalizedProblem & { ignored?: boolean })[]): Totals {
50
56
  let errors = 0;
@@ -143,6 +149,9 @@ export function formatProblems(
143
149
  case 'codeclimate':
144
150
  outputForCodeClimate();
145
151
  break;
152
+ case 'summary':
153
+ formatSummary(problems);
154
+ break;
146
155
  }
147
156
 
148
157
  if (totalProblems - ignoredProblems > maxProblems) {
@@ -257,13 +266,34 @@ export function formatProblems(
257
266
  }
258
267
  }
259
268
 
269
+ function formatSummary(problems: NormalizedProblem[]): void {
270
+ const counts: Record<string, { count: number; severity: ProblemSeverity }> = {};
271
+ for (const problem of problems) {
272
+ counts[problem.ruleId] = counts[problem.ruleId] || { count: 0, severity: problem.severity };
273
+ counts[problem.ruleId].count++;
274
+ }
275
+ const sorted = Object.entries(counts).sort(([, a], [, b]) => {
276
+ const severityDiff = severityToNumber(a.severity) - severityToNumber(b.severity);
277
+ return severityDiff || b.count - a.count;
278
+ });
279
+
280
+ for (const [ruleId, info] of sorted) {
281
+ const color = COLORS[info.severity];
282
+ const severityName = color(SEVERITY_NAMES[info.severity].toLowerCase().padEnd(7));
283
+ logger.info(`${severityName} ${ruleId}: ${info.count}\n`);
284
+ }
285
+
286
+ logger.info('\n');
287
+ }
288
+
260
289
  function formatFrom(cwd: string, location?: LocationObject) {
261
290
  if (!location) return '';
262
291
  const relativePath = path.relative(cwd, location.source.absoluteRef);
263
292
  const loc = getLineColLocation(location);
264
293
  const fileWithLoc = `${relativePath}:${loc.start.line}:${loc.start.col}`;
294
+ const atPointer = location.pointer ? colorize.gray(`at ${location.pointer}`) : '';
265
295
 
266
- return `referenced from ${colorize.blue(fileWithLoc)}\n\n`;
296
+ return `referenced from ${colorize.blue(fileWithLoc)} ${atPointer} \n\n`;
267
297
  }
268
298
 
269
299
  function formatDidYouMean(problem: NormalizedProblem) {
package/src/lint.ts CHANGED
@@ -80,13 +80,13 @@ export async function lintDocument(opts: {
80
80
  const normalizedVisitors = normalizeVisitors([...preprocessors, ...regularRules] as any, types);
81
81
  const resolvedRefMap = await resolveDocument({
82
82
  rootDocument: document,
83
- rootType: types.DefinitionRoot,
83
+ rootType: types.Root,
84
84
  externalRefResolver,
85
85
  });
86
86
 
87
87
  walkDocument({
88
88
  document,
89
- rootType: types.DefinitionRoot,
89
+ rootType: types.Root,
90
90
  normalizedVisitors,
91
91
  resolvedRefMap,
92
92
  ctx,
@@ -10,9 +10,6 @@ import { DEFAULT_REGION, DOMAINS } from '../config/config';
10
10
  import { isNotEmptyObject } from '../utils';
11
11
  const version = require('../../package.json').version;
12
12
 
13
- export const currentCommand =
14
- typeof process !== 'undefined' ? process.env?.REDOCLY_CLI_COMMAND || '' : '';
15
-
16
13
  export class RegistryApi {
17
14
  constructor(private accessTokens: AccessTokens, private region: Region) {}
18
15
 
@@ -30,9 +27,13 @@ export class RegistryApi {
30
27
  }
31
28
 
32
29
  private async request(path = '', options: RequestInit = {}, region?: Region) {
30
+ const currentCommand =
31
+ typeof process !== 'undefined' ? process.env?.REDOCLY_CLI_COMMAND || '' : '';
32
+ const redoclyEnv = typeof process !== 'undefined' ? process.env?.REDOCLY_ENVIRONMENT || '' : '';
33
+
33
34
  const headers = Object.assign({}, options.headers || {}, {
34
35
  'x-redocly-cli-version': version,
35
- 'user-agent': `redocly-cli / ${version} ${currentCommand}`,
36
+ 'user-agent': `redocly-cli / ${version} ${currentCommand} ${redoclyEnv}`,
36
37
  });
37
38
 
38
39
  if (!headers.hasOwnProperty('authorization')) {
package/src/resolve.ts CHANGED
@@ -115,7 +115,9 @@ export class BaseResolver {
115
115
  const { body, mimeType } = await readFileFromUrl(absoluteRef, this.config.http);
116
116
  return new Source(absoluteRef, body, mimeType);
117
117
  } else {
118
- return new Source(absoluteRef, await fs.promises.readFile(absoluteRef, 'utf-8'));
118
+ const content = await fs.promises.readFile(absoluteRef, 'utf-8');
119
+ // In some cases file have \r\n line delimeters like on windows, we should skip it.
120
+ return new Source(absoluteRef, content.replace(/\r\n/g, '\n'));
119
121
  }
120
122
  } catch (error) {
121
123
  throw new ResolveError(error);
@@ -153,7 +153,7 @@ describe('get-additional-properties-option', () => {
153
153
  } catch (error) {
154
154
  expect(error).toBeInstanceOf(Error);
155
155
  expect(error.message).toEqual(
156
- "Do not use 'disallowAdditionalProperties' field. Use 'allowAdditionalProperties' instead.\n"
156
+ "Do not use 'disallowAdditionalProperties' field. Use 'allowAdditionalProperties' instead. \n"
157
157
  );
158
158
  }
159
159
  });
@@ -191,6 +191,7 @@ describe('Oas3 typed enum', () => {
191
191
  expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
192
192
  Array [
193
193
  Object {
194
+ "from": undefined,
194
195
  "location": Array [
195
196
  Object {
196
197
  "pointer": "#/paths/~1some/get/responses/200/content/application~1json/schema",
@@ -34,7 +34,7 @@ describe('Oas3 operation-2xx-response', () => {
34
34
  "source": "foobar.yaml",
35
35
  },
36
36
  ],
37
- "message": "Operation must have at least one \`2xx\` response.",
37
+ "message": "Operation must have at least one \`2XX\` response.",
38
38
  "ruleId": "operation-2xx-response",
39
39
  "severity": "error",
40
40
  "suggest": Array [],
@@ -34,7 +34,7 @@ describe('Oas3 operation-4xx-response', () => {
34
34
  "source": "foobar.yaml",
35
35
  },
36
36
  ],
37
- "message": "Operation must have at least one \`4xx\` response.",
37
+ "message": "Operation must have at least one \`4XX\` response.",
38
38
  "ruleId": "operation-4xx-response",
39
39
  "severity": "error",
40
40
  "suggest": Array [],
@@ -43,7 +43,7 @@ describe('Oas3 operation-4xx-response', () => {
43
43
  `);
44
44
  });
45
45
 
46
- it('should not report for present 4xx response', async () => {
46
+ it('should not report for present 400 response', async () => {
47
47
  const document = parseYamlToDocument(
48
48
  outdent`
49
49
  openapi: 3.0.0
@@ -66,6 +66,29 @@ describe('Oas3 operation-4xx-response', () => {
66
66
  expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
67
67
  });
68
68
 
69
+ it('should not report for present 4XX response', async () => {
70
+ const document = parseYamlToDocument(
71
+ outdent`
72
+ openapi: 3.0.0
73
+ paths:
74
+ '/test/':
75
+ put:
76
+ responses:
77
+ 4XX:
78
+ description: error response
79
+ `,
80
+ 'foobar.yaml'
81
+ );
82
+
83
+ const results = await lintDocument({
84
+ externalRefResolver: new BaseResolver(),
85
+ document,
86
+ config: await makeConfig({ 'operation-4xx-response': 'error' }),
87
+ });
88
+
89
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
90
+ });
91
+
69
92
  it('should report if default is present but missing 4xx response', async () => {
70
93
  const document = parseYamlToDocument(
71
94
  outdent`
@@ -96,7 +119,7 @@ describe('Oas3 operation-4xx-response', () => {
96
119
  "source": "foobar.yaml",
97
120
  },
98
121
  ],
99
- "message": "Operation must have at least one \`4xx\` response.",
122
+ "message": "Operation must have at least one \`4XX\` response.",
100
123
  "ruleId": "operation-4xx-response",
101
124
  "severity": "error",
102
125
  "suggest": Array [],
@@ -0,0 +1,175 @@
1
+ import { outdent } from 'outdent';
2
+ import { lintDocument } from '../../../lint';
3
+ import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils';
4
+ import { BaseResolver } from '../../../resolve';
5
+
6
+ describe('Oas3 security-defined', () => {
7
+ it('should report on securityRequirements object if security scheme is not defined in components', async () => {
8
+ const document = parseYamlToDocument(
9
+ outdent`
10
+ openapi: 3.0.0
11
+ paths:
12
+ /pets:
13
+ get:
14
+ security:
15
+ - some: []`,
16
+ 'foobar.yaml'
17
+ );
18
+
19
+ const results = await lintDocument({
20
+ externalRefResolver: new BaseResolver(),
21
+ document,
22
+ config: await makeConfig({ 'security-defined': 'error' }),
23
+ });
24
+
25
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
26
+ Array [
27
+ Object {
28
+ "location": Array [
29
+ Object {
30
+ "pointer": "#/paths/~1pets/get/security/0/some",
31
+ "reportOnKey": true,
32
+ "source": "foobar.yaml",
33
+ },
34
+ ],
35
+ "message": "There is no \`some\` security scheme defined.",
36
+ "ruleId": "security-defined",
37
+ "severity": "error",
38
+ "suggest": Array [],
39
+ },
40
+ ]
41
+ `);
42
+ });
43
+
44
+ it('should not report if security defined with an empty array', async () => {
45
+ const document = parseYamlToDocument(
46
+ outdent`
47
+ openapi: 3.0.0
48
+ security: []
49
+ paths:`,
50
+ 'foobar.yaml'
51
+ );
52
+
53
+ const results = await lintDocument({
54
+ externalRefResolver: new BaseResolver(),
55
+ document,
56
+ config: await makeConfig({ 'security-defined': 'error' }),
57
+ });
58
+
59
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
60
+ });
61
+
62
+ it('should report if security not defined at all', async () => {
63
+ const document = parseYamlToDocument(
64
+ outdent`
65
+ openapi: 3.0.0
66
+ paths:
67
+ /pets:
68
+ get:
69
+ requestBody:`,
70
+ 'foobar.yaml'
71
+ );
72
+
73
+ const results = await lintDocument({
74
+ externalRefResolver: new BaseResolver(),
75
+ document,
76
+ config: await makeConfig({ 'security-defined': 'error' }),
77
+ });
78
+
79
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
80
+ Array [
81
+ Object {
82
+ "location": Array [
83
+ Object {
84
+ "pointer": "#/",
85
+ "reportOnKey": false,
86
+ "source": "foobar.yaml",
87
+ },
88
+ ],
89
+ "message": "Every API should have security defined on the root level or for each operation.",
90
+ "ruleId": "security-defined",
91
+ "severity": "error",
92
+ "suggest": Array [],
93
+ },
94
+ ]
95
+ `);
96
+ });
97
+
98
+ it('should report if security not defined for each operation', async () => {
99
+ const document = parseYamlToDocument(
100
+ outdent`
101
+ openapi: 3.0.0
102
+ paths:
103
+ /pets:
104
+ get:
105
+ security:
106
+ - some: []
107
+ /cats:
108
+ get:`,
109
+ 'foobar.yaml'
110
+ );
111
+
112
+ const results = await lintDocument({
113
+ externalRefResolver: new BaseResolver(),
114
+ document,
115
+ config: await makeConfig({ 'security-defined': 'error' }),
116
+ });
117
+
118
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`
119
+ Array [
120
+ Object {
121
+ "location": Array [
122
+ Object {
123
+ "pointer": "#/paths/~1pets/get/security/0/some",
124
+ "reportOnKey": true,
125
+ "source": "foobar.yaml",
126
+ },
127
+ ],
128
+ "message": "There is no \`some\` security scheme defined.",
129
+ "ruleId": "security-defined",
130
+ "severity": "error",
131
+ "suggest": Array [],
132
+ },
133
+ Object {
134
+ "location": Array [
135
+ Object {
136
+ "pointer": "#/",
137
+ "reportOnKey": false,
138
+ "source": "foobar.yaml",
139
+ },
140
+ ],
141
+ "message": "Every API should have security defined on the root level or for each operation.",
142
+ "ruleId": "security-defined",
143
+ "severity": "error",
144
+ "suggest": Array [],
145
+ },
146
+ ]
147
+ `);
148
+ });
149
+
150
+ it('should not report on securityRequirements object if security scheme is defined in components', async () => {
151
+ const document = parseYamlToDocument(
152
+ outdent`
153
+ openapi: 3.0.0
154
+ paths:
155
+ /pets:
156
+ get:
157
+ security:
158
+ some: []
159
+ components:
160
+ securitySchemes:
161
+ some:
162
+ type: http
163
+ scheme: basic`,
164
+ 'foobar.yaml'
165
+ );
166
+
167
+ const results = await lintDocument({
168
+ externalRefResolver: new BaseResolver(),
169
+ document,
170
+ config: await makeConfig({ 'security-defined': 'error' }),
171
+ });
172
+
173
+ expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`Array []`);
174
+ });
175
+ });