@apidiff/core 1.0.0
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.
- package/dist/ast/hash.d.ts +2 -0
- package/dist/ast/hash.d.ts.map +1 -0
- package/dist/ast/index.d.ts +3 -0
- package/dist/ast/index.d.ts.map +1 -0
- package/dist/ast/traverse.d.ts +13 -0
- package/dist/ast/traverse.d.ts.map +1 -0
- package/dist/config/index.d.ts +9 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/diff/auth-differ.d.ts +4 -0
- package/dist/diff/auth-differ.d.ts.map +1 -0
- package/dist/diff/endpoint-differ.d.ts +3 -0
- package/dist/diff/endpoint-differ.d.ts.map +1 -0
- package/dist/diff/index.d.ts +3 -0
- package/dist/diff/index.d.ts.map +1 -0
- package/dist/diff/schema-differ.d.ts +3 -0
- package/dist/diff/schema-differ.d.ts.map +1 -0
- package/dist/diff/server-differ.d.ts +3 -0
- package/dist/diff/server-differ.d.ts.map +1 -0
- package/dist/index.cjs +2902 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2855 -0
- package/dist/loader/file-loader.d.ts +5 -0
- package/dist/loader/file-loader.d.ts.map +1 -0
- package/dist/loader/git-loader.d.ts +5 -0
- package/dist/loader/git-loader.d.ts.map +1 -0
- package/dist/loader/index.d.ts +7 -0
- package/dist/loader/index.d.ts.map +1 -0
- package/dist/loader/url-loader.d.ts +5 -0
- package/dist/loader/url-loader.d.ts.map +1 -0
- package/dist/output/html.d.ts +3 -0
- package/dist/output/html.d.ts.map +1 -0
- package/dist/output/index.d.ts +3 -0
- package/dist/output/index.d.ts.map +1 -0
- package/dist/output/json.d.ts +3 -0
- package/dist/output/json.d.ts.map +1 -0
- package/dist/output/markdown.d.ts +3 -0
- package/dist/output/markdown.d.ts.map +1 -0
- package/dist/output/terminal.d.ts +3 -0
- package/dist/output/terminal.d.ts.map +1 -0
- package/dist/parsers/base.d.ts +8 -0
- package/dist/parsers/base.d.ts.map +1 -0
- package/dist/parsers/graphql/index.d.ts +13 -0
- package/dist/parsers/graphql/index.d.ts.map +1 -0
- package/dist/parsers/index.d.ts +7 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/parsers/openapi2/index.d.ts +9 -0
- package/dist/parsers/openapi2/index.d.ts.map +1 -0
- package/dist/parsers/openapi3/index.d.ts +9 -0
- package/dist/parsers/openapi3/index.d.ts.map +1 -0
- package/dist/parsers/openapi3/ref-resolver.d.ts +2 -0
- package/dist/parsers/openapi3/ref-resolver.d.ts.map +1 -0
- package/dist/parsers/openapi3/schema-normalizer.d.ts +3 -0
- package/dist/parsers/openapi3/schema-normalizer.d.ts.map +1 -0
- package/dist/parsers/openapi3/security-normalizer.d.ts +3 -0
- package/dist/parsers/openapi3/security-normalizer.d.ts.map +1 -0
- package/dist/parsers/protobuf/index.d.ts +14 -0
- package/dist/parsers/protobuf/index.d.ts.map +1 -0
- package/dist/rules/auth/oauth-scope-removed.d.ts +9 -0
- package/dist/rules/auth/oauth-scope-removed.d.ts.map +1 -0
- package/dist/rules/auth/security-added.d.ts +9 -0
- package/dist/rules/auth/security-added.d.ts.map +1 -0
- package/dist/rules/auth/security-removed.d.ts +9 -0
- package/dist/rules/auth/security-removed.d.ts.map +1 -0
- package/dist/rules/auth/security-scheme-type-changed.d.ts +9 -0
- package/dist/rules/auth/security-scheme-type-changed.d.ts.map +1 -0
- package/dist/rules/base.d.ts +19 -0
- package/dist/rules/base.d.ts.map +1 -0
- package/dist/rules/endpoint/endpoint-added.d.ts +9 -0
- package/dist/rules/endpoint/endpoint-added.d.ts.map +1 -0
- package/dist/rules/endpoint/endpoint-deprecated.d.ts +9 -0
- package/dist/rules/endpoint/endpoint-deprecated.d.ts.map +1 -0
- package/dist/rules/endpoint/endpoint-removed.d.ts +9 -0
- package/dist/rules/endpoint/endpoint-removed.d.ts.map +1 -0
- package/dist/rules/endpoint/http-method-changed.d.ts +9 -0
- package/dist/rules/endpoint/http-method-changed.d.ts.map +1 -0
- package/dist/rules/endpoint/path-changed.d.ts +9 -0
- package/dist/rules/endpoint/path-changed.d.ts.map +1 -0
- package/dist/rules/index.d.ts +4 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/meta/server-removed.d.ts +9 -0
- package/dist/rules/meta/server-removed.d.ts.map +1 -0
- package/dist/rules/param/param-added.d.ts +9 -0
- package/dist/rules/param/param-added.d.ts.map +1 -0
- package/dist/rules/param/param-deprecated.d.ts +9 -0
- package/dist/rules/param/param-deprecated.d.ts.map +1 -0
- package/dist/rules/param/param-enum-value-added.d.ts +9 -0
- package/dist/rules/param/param-enum-value-added.d.ts.map +1 -0
- package/dist/rules/param/param-enum-value-removed.d.ts +9 -0
- package/dist/rules/param/param-enum-value-removed.d.ts.map +1 -0
- package/dist/rules/param/param-location-changed.d.ts +9 -0
- package/dist/rules/param/param-location-changed.d.ts.map +1 -0
- package/dist/rules/param/param-removed.d.ts +9 -0
- package/dist/rules/param/param-removed.d.ts.map +1 -0
- package/dist/rules/param/param-required-added.d.ts +9 -0
- package/dist/rules/param/param-required-added.d.ts.map +1 -0
- package/dist/rules/param/param-required-true-to-false.d.ts +9 -0
- package/dist/rules/param/param-required-true-to-false.d.ts.map +1 -0
- package/dist/rules/param/param-type-changed.d.ts +9 -0
- package/dist/rules/param/param-type-changed.d.ts.map +1 -0
- package/dist/rules/request/request-body-added-required.d.ts +9 -0
- package/dist/rules/request/request-body-added-required.d.ts.map +1 -0
- package/dist/rules/request/request-body-removed.d.ts +9 -0
- package/dist/rules/request/request-body-removed.d.ts.map +1 -0
- package/dist/rules/request/request-content-type-added.d.ts +9 -0
- package/dist/rules/request/request-content-type-added.d.ts.map +1 -0
- package/dist/rules/request/request-content-type-removed.d.ts +9 -0
- package/dist/rules/request/request-content-type-removed.d.ts.map +1 -0
- package/dist/rules/request/request-field-added-required.d.ts +9 -0
- package/dist/rules/request/request-field-added-required.d.ts.map +1 -0
- package/dist/rules/request/request-field-removed.d.ts +9 -0
- package/dist/rules/request/request-field-removed.d.ts.map +1 -0
- package/dist/rules/request/request-field-type-changed.d.ts +9 -0
- package/dist/rules/request/request-field-type-changed.d.ts.map +1 -0
- package/dist/rules/request/request-required-false-to-true.d.ts +9 -0
- package/dist/rules/request/request-required-false-to-true.d.ts.map +1 -0
- package/dist/rules/response/response-field-added.d.ts +9 -0
- package/dist/rules/response/response-field-added.d.ts.map +1 -0
- package/dist/rules/response/response-field-removed.d.ts +9 -0
- package/dist/rules/response/response-field-removed.d.ts.map +1 -0
- package/dist/rules/response/response-field-type-changed.d.ts +9 -0
- package/dist/rules/response/response-field-type-changed.d.ts.map +1 -0
- package/dist/rules/response/response-header-added-required.d.ts +9 -0
- package/dist/rules/response/response-header-added-required.d.ts.map +1 -0
- package/dist/rules/response/response-header-removed.d.ts +9 -0
- package/dist/rules/response/response-header-removed.d.ts.map +1 -0
- package/dist/rules/response/response-media-type-added.d.ts +9 -0
- package/dist/rules/response/response-media-type-added.d.ts.map +1 -0
- package/dist/rules/response/response-media-type-removed.d.ts +9 -0
- package/dist/rules/response/response-media-type-removed.d.ts.map +1 -0
- package/dist/rules/response/response-status-added.d.ts +9 -0
- package/dist/rules/response/response-status-added.d.ts.map +1 -0
- package/dist/rules/response/response-status-removed.d.ts +9 -0
- package/dist/rules/response/response-status-removed.d.ts.map +1 -0
- package/dist/types/ast.d.ts +140 -0
- package/dist/types/ast.d.ts.map +1 -0
- package/dist/types/config.d.ts +17 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/diff.d.ts +43 -0
- package/dist/types/diff.d.ts.map +1 -0
- package/dist/types/errors.d.ts +21 -0
- package/dist/types/errors.d.ts.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/semantic.d.ts +45 -0
- package/dist/types/semantic.d.ts.map +1 -0
- package/package.json +31 -0
- package/src/ast/hash.ts +26 -0
- package/src/ast/index.ts +2 -0
- package/src/ast/traverse.ts +59 -0
- package/src/config/index.ts +44 -0
- package/src/diff/auth-differ.ts +72 -0
- package/src/diff/endpoint-differ.ts +144 -0
- package/src/diff/index.ts +13 -0
- package/src/diff/schema-differ.ts +70 -0
- package/src/diff/server-differ.ts +22 -0
- package/src/index.ts +92 -0
- package/src/loader/file-loader.ts +19 -0
- package/src/loader/git-loader.ts +22 -0
- package/src/loader/index.ts +32 -0
- package/src/loader/url-loader.ts +25 -0
- package/src/output/html.ts +177 -0
- package/src/output/index.ts +16 -0
- package/src/output/json.ts +5 -0
- package/src/output/markdown.ts +29 -0
- package/src/output/terminal.ts +37 -0
- package/src/parsers/base.ts +8 -0
- package/src/parsers/graphql/index.ts +181 -0
- package/src/parsers/index.ts +61 -0
- package/src/parsers/openapi2/index.ts +218 -0
- package/src/parsers/openapi3/index.ts +223 -0
- package/src/parsers/openapi3/ref-resolver.ts +101 -0
- package/src/parsers/openapi3/schema-normalizer.ts +52 -0
- package/src/parsers/openapi3/security-normalizer.ts +18 -0
- package/src/parsers/protobuf/index.ts +208 -0
- package/src/rules/auth/oauth-scope-removed.ts +34 -0
- package/src/rules/auth/security-added.ts +32 -0
- package/src/rules/auth/security-removed.ts +47 -0
- package/src/rules/auth/security-scheme-type-changed.ts +30 -0
- package/src/rules/base.ts +29 -0
- package/src/rules/endpoint/endpoint-added.ts +26 -0
- package/src/rules/endpoint/endpoint-deprecated.ts +29 -0
- package/src/rules/endpoint/endpoint-removed.ts +26 -0
- package/src/rules/endpoint/http-method-changed.ts +29 -0
- package/src/rules/endpoint/path-changed.ts +29 -0
- package/src/rules/index.ts +83 -0
- package/src/rules/meta/server-removed.ts +26 -0
- package/src/rules/param/param-added.ts +52 -0
- package/src/rules/param/param-deprecated.ts +34 -0
- package/src/rules/param/param-enum-value-added.ts +32 -0
- package/src/rules/param/param-enum-value-removed.ts +32 -0
- package/src/rules/param/param-location-changed.ts +43 -0
- package/src/rules/param/param-removed.ts +52 -0
- package/src/rules/param/param-required-added.ts +34 -0
- package/src/rules/param/param-required-true-to-false.ts +34 -0
- package/src/rules/param/param-type-changed.ts +32 -0
- package/src/rules/request/request-body-added-required.ts +33 -0
- package/src/rules/request/request-body-removed.ts +30 -0
- package/src/rules/request/request-content-type-added.ts +31 -0
- package/src/rules/request/request-content-type-removed.ts +31 -0
- package/src/rules/request/request-field-added-required.ts +41 -0
- package/src/rules/request/request-field-removed.ts +35 -0
- package/src/rules/request/request-field-type-changed.ts +37 -0
- package/src/rules/request/request-required-false-to-true.ts +56 -0
- package/src/rules/response/response-field-added.ts +34 -0
- package/src/rules/response/response-field-removed.ts +34 -0
- package/src/rules/response/response-field-type-changed.ts +37 -0
- package/src/rules/response/response-header-added-required.ts +47 -0
- package/src/rules/response/response-header-removed.ts +32 -0
- package/src/rules/response/response-media-type-added.ts +32 -0
- package/src/rules/response/response-media-type-removed.ts +32 -0
- package/src/rules/response/response-status-added.ts +31 -0
- package/src/rules/response/response-status-removed.ts +31 -0
- package/src/types/ast.ts +164 -0
- package/src/types/config.ts +31 -0
- package/src/types/diff.ts +49 -0
- package/src/types/errors.ts +26 -0
- package/src/types/index.ts +5 -0
- package/src/types/semantic.ts +60 -0
- package/test/integration/stripe-v1.yaml +36 -0
- package/test/integration/stripe-v2.yaml +35 -0
- package/tests/ast.test.ts +60 -0
- package/tests/config.test.ts +43 -0
- package/tests/diff/auth-differ.test.ts +55 -0
- package/tests/diff/endpoint-differ.test.ts +42 -0
- package/tests/diff/schema-differ.test.ts +46 -0
- package/tests/diff/server-differ.test.ts +23 -0
- package/tests/diff.test.ts +116 -0
- package/tests/fixtures/openapi3/auth-added/v1.yaml +11 -0
- package/tests/fixtures/openapi3/auth-added/v2.yaml +18 -0
- package/tests/integration.test.ts +21 -0
- package/tests/loader-more.test.ts +75 -0
- package/tests/loader.test.ts +61 -0
- package/tests/output.test.ts +58 -0
- package/tests/parsers/openapi3-coverage.test.ts +77 -0
- package/tests/parsers/openapi3.test.ts +41 -0
- package/tests/parsers/parsers-other.test.ts +135 -0
- package/tests/parsers/ref-resolver.test.ts +78 -0
- package/tests/parsers/schema-normalizer.test.ts +30 -0
- package/tests/rules/auth-meta.test.ts +87 -0
- package/tests/rules/base.test.ts +44 -0
- package/tests/rules/endpoint.test.ts +122 -0
- package/tests/rules/param.test.ts +242 -0
- package/tests/rules/request.test.ts +147 -0
- package/tests/rules/response.test.ts +161 -0
- package/tests/rules/rules-coverage.test.ts +64 -0
- package/tests/types-errors.test.ts +22 -0
- package/tsconfig.json +8 -0
- package/tsconfig.tsbuildinfo +1 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { BaseRule } from '../base.js';
|
|
2
|
+
import type { DiffSet, RuleContext, SemanticChange } from '../../types/index.js';
|
|
3
|
+
|
|
4
|
+
export class ResponseFieldAddedRule extends BaseRule {
|
|
5
|
+
id = 'RESPONSE_FIELD_ADDED';
|
|
6
|
+
description = 'A field was added to the response body';
|
|
7
|
+
severity = 'info' as const;
|
|
8
|
+
|
|
9
|
+
apply(diff: DiffSet, context: RuleContext): SemanticChange[] {
|
|
10
|
+
const changes: SemanticChange[] = [];
|
|
11
|
+
for (const ed of diff.endpointDiffs) {
|
|
12
|
+
if (ed.type !== 'changed') continue;
|
|
13
|
+
if (this.isIgnored(ed.path, context)) continue;
|
|
14
|
+
|
|
15
|
+
for (const fc of ed.fieldChanges) {
|
|
16
|
+
if (fc.fieldPath[0] === 'responses' && fc.changeType === 'added') {
|
|
17
|
+
const propsIdx = fc.fieldPath.lastIndexOf('properties');
|
|
18
|
+
if (propsIdx !== -1 && fc.fieldPath.length === propsIdx + 2) {
|
|
19
|
+
const fieldName = fc.fieldPath[propsIdx + 1];
|
|
20
|
+
changes.push(this.makeChange({
|
|
21
|
+
severity: 'info',
|
|
22
|
+
category: 'response',
|
|
23
|
+
message: `Response field '${fieldName}' was added to status code ${fc.fieldPath[1]}.`,
|
|
24
|
+
consequence: 'Clients parsing responses strictly might fail if they do not ignore unknown fields.',
|
|
25
|
+
migration: 'Clients should ensure their JSON parsers ignore unknown fields.',
|
|
26
|
+
location: { path: ed.path, method: ed.method, field: fc.fieldPath.join('.') }
|
|
27
|
+
}, context));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return changes;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { BaseRule } from '../base.js';
|
|
2
|
+
import type { DiffSet, RuleContext, SemanticChange } from '../../types/index.js';
|
|
3
|
+
|
|
4
|
+
export class ResponseFieldRemovedRule extends BaseRule {
|
|
5
|
+
id = 'RESPONSE_FIELD_REMOVED';
|
|
6
|
+
description = 'A field was removed from a response payload';
|
|
7
|
+
severity = 'breaking' as const;
|
|
8
|
+
|
|
9
|
+
apply(diff: DiffSet, context: RuleContext): SemanticChange[] {
|
|
10
|
+
const changes: SemanticChange[] = [];
|
|
11
|
+
for (const ed of diff.endpointDiffs) {
|
|
12
|
+
if (ed.type !== 'changed') continue;
|
|
13
|
+
if (this.isIgnored(ed.path, context)) continue;
|
|
14
|
+
|
|
15
|
+
for (const fc of ed.fieldChanges) {
|
|
16
|
+
if (fc.fieldPath[0] === 'responses' && fc.changeType === 'removed') {
|
|
17
|
+
if (fc.fieldPath.includes('properties')) {
|
|
18
|
+
const fieldName = fc.fieldPath[fc.fieldPath.length - 1];
|
|
19
|
+
const statusCode = fc.fieldPath[1] || 'unknown';
|
|
20
|
+
changes.push(this.makeChange({
|
|
21
|
+
severity: 'breaking',
|
|
22
|
+
category: 'response',
|
|
23
|
+
message: `Field '${fieldName}' was removed from the response.`,
|
|
24
|
+
consequence: `Clients depending on '${fieldName}' will encounter missing data errors.`,
|
|
25
|
+
migration: `Ensure clients no longer require '${fieldName}' before removing it.`,
|
|
26
|
+
location: { path: ed.path, method: ed.method, field: fieldName, statusCode }
|
|
27
|
+
}, context));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return changes;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { BaseRule } from '../base.js';
|
|
2
|
+
import type { DiffSet, RuleContext, SemanticChange } from '../../types/index.js';
|
|
3
|
+
|
|
4
|
+
export class ResponseFieldTypeChangedRule extends BaseRule {
|
|
5
|
+
id = 'RESPONSE_FIELD_TYPE_CHANGED';
|
|
6
|
+
description = 'The data type of a response body field changed';
|
|
7
|
+
severity = 'breaking' as const;
|
|
8
|
+
|
|
9
|
+
apply(diff: DiffSet, context: RuleContext): SemanticChange[] {
|
|
10
|
+
const changes: SemanticChange[] = [];
|
|
11
|
+
for (const ed of diff.endpointDiffs) {
|
|
12
|
+
if (ed.type !== 'changed') continue;
|
|
13
|
+
if (this.isIgnored(ed.path, context)) continue;
|
|
14
|
+
|
|
15
|
+
for (const fc of ed.fieldChanges) {
|
|
16
|
+
if (fc.fieldPath[0] === 'responses' && fc.changeType === 'changed') {
|
|
17
|
+
const typeIdx = fc.fieldPath.lastIndexOf('type');
|
|
18
|
+
if (typeIdx !== -1 && typeIdx === fc.fieldPath.length - 1) {
|
|
19
|
+
const propsIdx = fc.fieldPath.lastIndexOf('properties');
|
|
20
|
+
if (propsIdx !== -1 && typeIdx > propsIdx + 1) {
|
|
21
|
+
const fieldName = fc.fieldPath[propsIdx + 1];
|
|
22
|
+
changes.push(this.makeChange({
|
|
23
|
+
severity: 'breaking',
|
|
24
|
+
category: 'response',
|
|
25
|
+
message: `Response body field '${fieldName}' changed type from ${fc.oldValue} to ${fc.newValue} for status code ${fc.fieldPath[1]}.`,
|
|
26
|
+
consequence: 'Clients expecting the old type will fail to parse the response.',
|
|
27
|
+
migration: `Update clients to expect the new type (${fc.newValue}) for '${fieldName}'.`,
|
|
28
|
+
location: { path: ed.path, method: ed.method, field: fc.fieldPath.slice(0, -1).join('.') }
|
|
29
|
+
}, context));
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return changes;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { BaseRule } from '../base.js';
|
|
2
|
+
import type { DiffSet, RuleContext, SemanticChange } from '../../types/index.js';
|
|
3
|
+
|
|
4
|
+
export class ResponseHeaderAddedRequiredRule extends BaseRule {
|
|
5
|
+
id = 'RESPONSE_HEADER_ADDED_REQUIRED';
|
|
6
|
+
description = 'A required response header was added';
|
|
7
|
+
severity = 'breaking' as const;
|
|
8
|
+
|
|
9
|
+
apply(diff: DiffSet, context: RuleContext): SemanticChange[] {
|
|
10
|
+
const changes: SemanticChange[] = [];
|
|
11
|
+
for (const ed of diff.endpointDiffs) {
|
|
12
|
+
if (ed.type !== 'changed') continue;
|
|
13
|
+
if (this.isIgnored(ed.path, context)) continue;
|
|
14
|
+
|
|
15
|
+
for (const fc of ed.fieldChanges) {
|
|
16
|
+
if (fc.fieldPath[0] === 'responses' && fc.fieldPath[2] === 'headers' && fc.changeType === 'added' && fc.fieldPath.length === 4) {
|
|
17
|
+
const statusCode = fc.fieldPath[1];
|
|
18
|
+
const headerName = fc.fieldPath[3];
|
|
19
|
+
const headerDef = fc.newValue as { required?: boolean };
|
|
20
|
+
|
|
21
|
+
if (headerDef.required) {
|
|
22
|
+
changes.push(this.makeChange({
|
|
23
|
+
severity: 'breaking',
|
|
24
|
+
category: 'response',
|
|
25
|
+
message: `Required response header '${headerName}' was added to status code ${statusCode}.`,
|
|
26
|
+
consequence: `Clients not designed to handle the new required header may fail or reject the response.`,
|
|
27
|
+
migration: `Update clients to accept and process the new '${headerName}' header.`,
|
|
28
|
+
location: { path: ed.path, method: ed.method, field: fc.fieldPath.join('.') }
|
|
29
|
+
}, context));
|
|
30
|
+
}
|
|
31
|
+
} else if (fc.fieldPath[0] === 'responses' && fc.fieldPath[2] === 'headers' && fc.fieldPath[4] === 'required' && fc.changeType === 'changed' && fc.oldValue === false && fc.newValue === true) {
|
|
32
|
+
const statusCode = fc.fieldPath[1];
|
|
33
|
+
const headerName = fc.fieldPath[3];
|
|
34
|
+
changes.push(this.makeChange({
|
|
35
|
+
severity: 'breaking',
|
|
36
|
+
category: 'response',
|
|
37
|
+
message: `Optional response header '${headerName}' was made required for status code ${statusCode}.`,
|
|
38
|
+
consequence: `Clients not designed to handle the required header may fail or reject the response.`,
|
|
39
|
+
migration: `Update clients to accept and process the '${headerName}' header.`,
|
|
40
|
+
location: { path: ed.path, method: ed.method, field: fc.fieldPath.join('.') }
|
|
41
|
+
}, context));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return changes;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { BaseRule } from '../base.js';
|
|
2
|
+
import type { DiffSet, RuleContext, SemanticChange } from '../../types/index.js';
|
|
3
|
+
|
|
4
|
+
export class ResponseHeaderRemovedRule extends BaseRule {
|
|
5
|
+
id = 'RESPONSE_HEADER_REMOVED';
|
|
6
|
+
description = 'A response header was removed';
|
|
7
|
+
severity = 'breaking' as const;
|
|
8
|
+
|
|
9
|
+
apply(diff: DiffSet, context: RuleContext): SemanticChange[] {
|
|
10
|
+
const changes: SemanticChange[] = [];
|
|
11
|
+
for (const ed of diff.endpointDiffs) {
|
|
12
|
+
if (ed.type !== 'changed') continue;
|
|
13
|
+
if (this.isIgnored(ed.path, context)) continue;
|
|
14
|
+
|
|
15
|
+
for (const fc of ed.fieldChanges) {
|
|
16
|
+
if (fc.fieldPath[0] === 'responses' && fc.fieldPath[2] === 'headers' && fc.fieldPath.length === 4 && fc.changeType === 'removed') {
|
|
17
|
+
const statusCode = fc.fieldPath[1];
|
|
18
|
+
const headerName = fc.fieldPath[3];
|
|
19
|
+
changes.push(this.makeChange({
|
|
20
|
+
severity: 'breaking',
|
|
21
|
+
category: 'response',
|
|
22
|
+
message: `Response header '${headerName}' was removed from status code ${statusCode}.`,
|
|
23
|
+
consequence: `Clients depending on the '${headerName}' header will no longer receive it.`,
|
|
24
|
+
migration: `Update clients to not expect the '${headerName}' header.`,
|
|
25
|
+
location: { path: ed.path, method: ed.method, field: fc.fieldPath.join('.') }
|
|
26
|
+
}, context));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return changes;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { BaseRule } from '../base.js';
|
|
2
|
+
import type { DiffSet, RuleContext, SemanticChange } from '../../types/index.js';
|
|
3
|
+
|
|
4
|
+
export class ResponseMediaTypeAddedRule extends BaseRule {
|
|
5
|
+
id = 'RESPONSE_MEDIA_TYPE_ADDED';
|
|
6
|
+
description = 'A response media type was added';
|
|
7
|
+
severity = 'info' as const;
|
|
8
|
+
|
|
9
|
+
apply(diff: DiffSet, context: RuleContext): SemanticChange[] {
|
|
10
|
+
const changes: SemanticChange[] = [];
|
|
11
|
+
for (const ed of diff.endpointDiffs) {
|
|
12
|
+
if (ed.type !== 'changed') continue;
|
|
13
|
+
if (this.isIgnored(ed.path, context)) continue;
|
|
14
|
+
|
|
15
|
+
for (const fc of ed.fieldChanges) {
|
|
16
|
+
if (fc.fieldPath[0] === 'responses' && fc.fieldPath[2] === 'content' && fc.fieldPath.length === 4 && fc.changeType === 'added') {
|
|
17
|
+
const statusCode = fc.fieldPath[1];
|
|
18
|
+
const mediaType = fc.fieldPath[3];
|
|
19
|
+
changes.push(this.makeChange({
|
|
20
|
+
severity: 'info',
|
|
21
|
+
category: 'response',
|
|
22
|
+
message: `Response media type '${mediaType}' was added to status code ${statusCode}.`,
|
|
23
|
+
consequence: 'Clients can now request the new media type.',
|
|
24
|
+
migration: `Update clients to request '${mediaType}' if desired.`,
|
|
25
|
+
location: { path: ed.path, method: ed.method, field: fc.fieldPath.join('.') }
|
|
26
|
+
}, context));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return changes;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { BaseRule } from '../base.js';
|
|
2
|
+
import type { DiffSet, RuleContext, SemanticChange } from '../../types/index.js';
|
|
3
|
+
|
|
4
|
+
export class ResponseMediaTypeRemovedRule extends BaseRule {
|
|
5
|
+
id = 'RESPONSE_MEDIA_TYPE_REMOVED';
|
|
6
|
+
description = 'A response media type was removed';
|
|
7
|
+
severity = 'breaking' as const;
|
|
8
|
+
|
|
9
|
+
apply(diff: DiffSet, context: RuleContext): SemanticChange[] {
|
|
10
|
+
const changes: SemanticChange[] = [];
|
|
11
|
+
for (const ed of diff.endpointDiffs) {
|
|
12
|
+
if (ed.type !== 'changed') continue;
|
|
13
|
+
if (this.isIgnored(ed.path, context)) continue;
|
|
14
|
+
|
|
15
|
+
for (const fc of ed.fieldChanges) {
|
|
16
|
+
if (fc.fieldPath[0] === 'responses' && fc.fieldPath[2] === 'content' && fc.fieldPath.length === 4 && fc.changeType === 'removed') {
|
|
17
|
+
const statusCode = fc.fieldPath[1];
|
|
18
|
+
const mediaType = fc.fieldPath[3];
|
|
19
|
+
changes.push(this.makeChange({
|
|
20
|
+
severity: 'breaking',
|
|
21
|
+
category: 'response',
|
|
22
|
+
message: `Response media type '${mediaType}' was removed for status code ${statusCode}.`,
|
|
23
|
+
consequence: `Clients requesting '${mediaType}' will no longer receive it and may fail to process the response.`,
|
|
24
|
+
migration: `Update clients to accept one of the remaining supported media types.`,
|
|
25
|
+
location: { path: ed.path, method: ed.method, field: fc.fieldPath.join('.') }
|
|
26
|
+
}, context));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return changes;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { BaseRule } from '../base.js';
|
|
2
|
+
import type { DiffSet, RuleContext, SemanticChange } from '../../types/index.js';
|
|
3
|
+
|
|
4
|
+
export class ResponseStatusAddedRule extends BaseRule {
|
|
5
|
+
id = 'RESPONSE_STATUS_ADDED';
|
|
6
|
+
description = 'A response status code was added';
|
|
7
|
+
severity = 'info' as const;
|
|
8
|
+
|
|
9
|
+
apply(diff: DiffSet, context: RuleContext): SemanticChange[] {
|
|
10
|
+
const changes: SemanticChange[] = [];
|
|
11
|
+
for (const ed of diff.endpointDiffs) {
|
|
12
|
+
if (ed.type !== 'changed') continue;
|
|
13
|
+
if (this.isIgnored(ed.path, context)) continue;
|
|
14
|
+
|
|
15
|
+
for (const fc of ed.fieldChanges) {
|
|
16
|
+
if (fc.fieldPath[0] === 'responses' && fc.fieldPath.length === 2 && fc.changeType === 'added') {
|
|
17
|
+
const statusCode = fc.fieldPath[1];
|
|
18
|
+
changes.push(this.makeChange({
|
|
19
|
+
severity: 'info',
|
|
20
|
+
category: 'response',
|
|
21
|
+
message: `Response status code ${statusCode} was added.`,
|
|
22
|
+
consequence: `Clients may receive a ${statusCode} response they weren't previously expecting.`,
|
|
23
|
+
migration: `Update clients to gracefully handle the ${statusCode} response.`,
|
|
24
|
+
location: { path: ed.path, method: ed.method, field: `responses['${statusCode}']` }
|
|
25
|
+
}, context));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return changes;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { BaseRule } from '../base.js';
|
|
2
|
+
import type { DiffSet, RuleContext, SemanticChange } from '../../types/index.js';
|
|
3
|
+
|
|
4
|
+
export class ResponseStatusRemovedRule extends BaseRule {
|
|
5
|
+
id = 'RESPONSE_STATUS_REMOVED';
|
|
6
|
+
description = 'A response status code was removed';
|
|
7
|
+
severity = 'breaking' as const;
|
|
8
|
+
|
|
9
|
+
apply(diff: DiffSet, context: RuleContext): SemanticChange[] {
|
|
10
|
+
const changes: SemanticChange[] = [];
|
|
11
|
+
for (const ed of diff.endpointDiffs) {
|
|
12
|
+
if (ed.type !== 'changed') continue;
|
|
13
|
+
if (this.isIgnored(ed.path, context)) continue;
|
|
14
|
+
|
|
15
|
+
for (const fc of ed.fieldChanges) {
|
|
16
|
+
if (fc.fieldPath[0] === 'responses' && fc.fieldPath.length === 2 && fc.changeType === 'removed') {
|
|
17
|
+
const statusCode = fc.fieldPath[1];
|
|
18
|
+
changes.push(this.makeChange({
|
|
19
|
+
severity: 'breaking',
|
|
20
|
+
category: 'response',
|
|
21
|
+
message: `Response status code ${statusCode} was removed.`,
|
|
22
|
+
consequence: `Clients explicitly expecting a ${statusCode} response might fail or behave unexpectedly.`,
|
|
23
|
+
migration: `Update clients to no longer rely on the ${statusCode} response.`,
|
|
24
|
+
location: { path: ed.path, method: ed.method, field: `responses['${statusCode}']` }
|
|
25
|
+
}, context));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return changes;
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/types/ast.ts
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
export type SpecFormat = 'openapi3' | 'openapi2' | 'protobuf' | 'graphql';
|
|
2
|
+
|
|
3
|
+
export type HttpMethod =
|
|
4
|
+
| 'GET' | 'POST' | 'PUT' | 'PATCH'
|
|
5
|
+
| 'DELETE' | 'HEAD' | 'OPTIONS' | 'TRACE';
|
|
6
|
+
|
|
7
|
+
export interface NormalizedAST {
|
|
8
|
+
meta: SpecMeta;
|
|
9
|
+
servers: Server[];
|
|
10
|
+
endpoints: Endpoint[];
|
|
11
|
+
components: ComponentMap;
|
|
12
|
+
security: SecurityScheme[];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SpecMeta {
|
|
16
|
+
title: string;
|
|
17
|
+
version: string;
|
|
18
|
+
format: SpecFormat;
|
|
19
|
+
rawVersion: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Server {
|
|
23
|
+
url: string;
|
|
24
|
+
description?: string;
|
|
25
|
+
variables?: Record<string, ServerVariable>;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ServerVariable {
|
|
29
|
+
default: string;
|
|
30
|
+
enum?: string[];
|
|
31
|
+
description?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface Endpoint {
|
|
35
|
+
id: string;
|
|
36
|
+
path: string;
|
|
37
|
+
method: HttpMethod | 'RPC';
|
|
38
|
+
summary?: string;
|
|
39
|
+
description?: string;
|
|
40
|
+
operationId?: string;
|
|
41
|
+
tags: string[];
|
|
42
|
+
deprecated: boolean;
|
|
43
|
+
security: SecurityRequirement[];
|
|
44
|
+
parameters: Parameter[];
|
|
45
|
+
requestBody?: RequestBody;
|
|
46
|
+
responses: ResponseDef[];
|
|
47
|
+
extensions: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface Parameter {
|
|
51
|
+
name: string;
|
|
52
|
+
in: 'path' | 'query' | 'header' | 'cookie';
|
|
53
|
+
required: boolean;
|
|
54
|
+
deprecated: boolean;
|
|
55
|
+
schema: Schema;
|
|
56
|
+
description?: string;
|
|
57
|
+
example?: unknown;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface RequestBody {
|
|
61
|
+
required: boolean;
|
|
62
|
+
description?: string;
|
|
63
|
+
content: Record<string, MediaTypeObject>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface MediaTypeObject {
|
|
67
|
+
schema: Schema;
|
|
68
|
+
example?: unknown;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ResponseDef {
|
|
72
|
+
statusCode: string;
|
|
73
|
+
description?: string;
|
|
74
|
+
content: Record<string, MediaTypeObject>;
|
|
75
|
+
headers: Record<string, HeaderDef>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface HeaderDef {
|
|
79
|
+
required?: boolean;
|
|
80
|
+
deprecated?: boolean;
|
|
81
|
+
schema: Schema;
|
|
82
|
+
description?: string;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface Schema {
|
|
86
|
+
type?: string | string[];
|
|
87
|
+
format?: string;
|
|
88
|
+
nullable?: boolean;
|
|
89
|
+
description?: string;
|
|
90
|
+
default?: unknown;
|
|
91
|
+
example?: unknown;
|
|
92
|
+
deprecated?: boolean;
|
|
93
|
+
readOnly?: boolean;
|
|
94
|
+
writeOnly?: boolean;
|
|
95
|
+
|
|
96
|
+
required?: string[];
|
|
97
|
+
properties?: Record<string, Schema>;
|
|
98
|
+
additionalProperties?: boolean | Schema;
|
|
99
|
+
items?: Schema;
|
|
100
|
+
enum?: unknown[];
|
|
101
|
+
const?: unknown;
|
|
102
|
+
|
|
103
|
+
minimum?: number;
|
|
104
|
+
maximum?: number;
|
|
105
|
+
exclusiveMinimum?: number | boolean;
|
|
106
|
+
exclusiveMaximum?: number | boolean;
|
|
107
|
+
multipleOf?: number;
|
|
108
|
+
|
|
109
|
+
minLength?: number;
|
|
110
|
+
maxLength?: number;
|
|
111
|
+
pattern?: string;
|
|
112
|
+
|
|
113
|
+
minItems?: number;
|
|
114
|
+
maxItems?: number;
|
|
115
|
+
uniqueItems?: boolean;
|
|
116
|
+
|
|
117
|
+
allOf?: Schema[];
|
|
118
|
+
oneOf?: Schema[];
|
|
119
|
+
anyOf?: Schema[];
|
|
120
|
+
not?: Schema;
|
|
121
|
+
|
|
122
|
+
$circular?: true;
|
|
123
|
+
$ref?: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface SecurityRequirement {
|
|
127
|
+
schemeId: string;
|
|
128
|
+
scopes: string[];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface SecurityScheme {
|
|
132
|
+
id: string;
|
|
133
|
+
type: 'apiKey' | 'http' | 'oauth2' | 'openIdConnect' | 'mutualTLS';
|
|
134
|
+
description?: string;
|
|
135
|
+
name?: string;
|
|
136
|
+
in?: 'header' | 'query' | 'cookie' | string;
|
|
137
|
+
scheme?: string;
|
|
138
|
+
bearerFormat?: string;
|
|
139
|
+
flows?: OAuthFlows;
|
|
140
|
+
openIdConnectUrl?: string;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface OAuthFlows {
|
|
144
|
+
implicit?: OAuthFlow;
|
|
145
|
+
password?: OAuthFlow;
|
|
146
|
+
clientCredentials?: OAuthFlow;
|
|
147
|
+
authorizationCode?: OAuthFlow;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface OAuthFlow {
|
|
151
|
+
authorizationUrl?: string;
|
|
152
|
+
tokenUrl?: string;
|
|
153
|
+
refreshUrl?: string;
|
|
154
|
+
scopes: Record<string, string>;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export interface ComponentMap {
|
|
158
|
+
schemas: Record<string, Schema>;
|
|
159
|
+
securitySchemes: Record<string, SecurityScheme>;
|
|
160
|
+
parameters: Record<string, Parameter>;
|
|
161
|
+
responses: Record<string, ResponseDef>;
|
|
162
|
+
headers: Record<string, HeaderDef>;
|
|
163
|
+
requestBodies: Record<string, RequestBody>;
|
|
164
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Severity } from './semantic.js';
|
|
2
|
+
|
|
3
|
+
export interface ApidiffConfig {
|
|
4
|
+
failOn: 'breaking' | 'warning';
|
|
5
|
+
ignorePaths: string[];
|
|
6
|
+
ruleSeverityOverrides: Record<string, Severity>;
|
|
7
|
+
disabledRules: string[];
|
|
8
|
+
customRules: string[];
|
|
9
|
+
output: OutputConfig;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface OutputConfig {
|
|
13
|
+
format: 'terminal' | 'json' | 'markdown' | 'html';
|
|
14
|
+
color: boolean;
|
|
15
|
+
summary: boolean;
|
|
16
|
+
quiet: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const DEFAULT_CONFIG: ApidiffConfig = {
|
|
20
|
+
failOn: 'breaking',
|
|
21
|
+
ignorePaths: [],
|
|
22
|
+
ruleSeverityOverrides: {},
|
|
23
|
+
disabledRules: [],
|
|
24
|
+
customRules: [],
|
|
25
|
+
output: {
|
|
26
|
+
format: 'terminal',
|
|
27
|
+
color: true,
|
|
28
|
+
summary: false,
|
|
29
|
+
quiet: false,
|
|
30
|
+
}
|
|
31
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Endpoint, Schema, SecurityScheme, Server } from './ast.js';
|
|
2
|
+
|
|
3
|
+
export type ChangeType = 'added' | 'removed' | 'changed';
|
|
4
|
+
|
|
5
|
+
export interface DiffSet {
|
|
6
|
+
endpointDiffs: EndpointDiff[];
|
|
7
|
+
schemaDiffs: SchemaDiff[];
|
|
8
|
+
securityDiffs: SecurityDiff[];
|
|
9
|
+
serverDiffs: ServerDiff[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface EndpointDiff {
|
|
13
|
+
type: ChangeType;
|
|
14
|
+
endpointId: string;
|
|
15
|
+
path: string;
|
|
16
|
+
method: string;
|
|
17
|
+
oldEndpoint?: Endpoint;
|
|
18
|
+
newEndpoint?: Endpoint;
|
|
19
|
+
fieldChanges: FieldChange[];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface SchemaDiff {
|
|
23
|
+
schemaName: string;
|
|
24
|
+
changeType: ChangeType;
|
|
25
|
+
oldSchema?: Schema;
|
|
26
|
+
newSchema?: Schema;
|
|
27
|
+
fieldChanges: FieldChange[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SecurityDiff {
|
|
31
|
+
schemeId: string;
|
|
32
|
+
changeType: ChangeType;
|
|
33
|
+
oldScheme?: SecurityScheme;
|
|
34
|
+
newScheme?: SecurityScheme;
|
|
35
|
+
fieldChanges: FieldChange[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ServerDiff {
|
|
39
|
+
changeType: ChangeType;
|
|
40
|
+
oldServer?: Server;
|
|
41
|
+
newServer?: Server;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface FieldChange {
|
|
45
|
+
fieldPath: string[];
|
|
46
|
+
changeType: ChangeType;
|
|
47
|
+
oldValue?: unknown;
|
|
48
|
+
newValue?: unknown;
|
|
49
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export class ApidiffError extends Error {
|
|
2
|
+
constructor(message: string, public cause?: Error) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = this.constructor.name;
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class LoadError extends ApidiffError {}
|
|
9
|
+
export class ParseError extends ApidiffError {
|
|
10
|
+
constructor(
|
|
11
|
+
message: string,
|
|
12
|
+
public line?: number,
|
|
13
|
+
public col?: number,
|
|
14
|
+
public filePath?: string,
|
|
15
|
+
cause?: Error
|
|
16
|
+
) {
|
|
17
|
+
super(message, cause);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export class RefError extends ApidiffError {
|
|
21
|
+
constructor(message: string, public ref: string, cause?: Error) {
|
|
22
|
+
super(message, cause);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export class ConfigError extends ApidiffError {}
|
|
26
|
+
export class FormatError extends ApidiffError {}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { NormalizedAST, SpecMeta } from './ast.js';
|
|
2
|
+
import type { DiffSet } from './diff.js';
|
|
3
|
+
import type { ApidiffConfig } from './config.js';
|
|
4
|
+
|
|
5
|
+
export type Severity = 'breaking' | 'warning' | 'info';
|
|
6
|
+
|
|
7
|
+
export type RuleCategory =
|
|
8
|
+
| 'endpoint'
|
|
9
|
+
| 'parameter'
|
|
10
|
+
| 'request-body'
|
|
11
|
+
| 'response'
|
|
12
|
+
| 'schema'
|
|
13
|
+
| 'authentication'
|
|
14
|
+
| 'deprecation'
|
|
15
|
+
| 'server'
|
|
16
|
+
| 'rate-limit';
|
|
17
|
+
|
|
18
|
+
export interface SemanticChange {
|
|
19
|
+
ruleId: string;
|
|
20
|
+
severity: Severity;
|
|
21
|
+
category: RuleCategory;
|
|
22
|
+
message: string;
|
|
23
|
+
consequence: string;
|
|
24
|
+
migration?: string;
|
|
25
|
+
location: ChangeLocation;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface ChangeLocation {
|
|
29
|
+
path?: string;
|
|
30
|
+
method?: string;
|
|
31
|
+
field?: string;
|
|
32
|
+
statusCode?: string;
|
|
33
|
+
paramName?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface RunResult {
|
|
37
|
+
changes: SemanticChange[];
|
|
38
|
+
stats: {
|
|
39
|
+
breaking: number;
|
|
40
|
+
warning: number;
|
|
41
|
+
info: number;
|
|
42
|
+
total: number;
|
|
43
|
+
};
|
|
44
|
+
oldSpecMeta: SpecMeta;
|
|
45
|
+
newSpecMeta: SpecMeta;
|
|
46
|
+
durationMs: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface IRule {
|
|
50
|
+
id: string;
|
|
51
|
+
description: string;
|
|
52
|
+
severity: Severity;
|
|
53
|
+
apply(diff: DiffSet, context: RuleContext): SemanticChange[];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface RuleContext {
|
|
57
|
+
oldAST: NormalizedAST;
|
|
58
|
+
newAST: NormalizedAST;
|
|
59
|
+
config: ApidiffConfig;
|
|
60
|
+
}
|