Skip to content

Commit 88740ec

Browse files
authored
Surface a schema assertion on bodies for rule runner (#2672)
1 parent 9f47014 commit 88740ec

File tree

20 files changed

+8906
-2361
lines changed

20 files changed

+8906
-2361
lines changed

projects/openapi-utilities/src/openapi3/implementations/openapi3/__tests__/__snapshots__/openapi-traverser.test.ts.snap

+7,552-2,217
Large diffs are not rendered by default.

projects/openapi-utilities/src/openapi3/implementations/openapi3/openapi-traverser.ts

+53-7
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import invariant from 'ts-invariant';
2626
import { jsonPointerHelpers as jsonPointer } from '@useoptic/json-pointer-helpers';
2727
import { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types';
2828
import { OAS3 } from '../../traverser';
29+
import { SchemaLocation } from '../../sdk/types/location';
2930

3031
export function normalizeOpenApiPath(path: string): string {
3132
return path
@@ -53,7 +54,7 @@ const isObject = (value: any) => {
5354
};
5455

5556
// TODO deprecate this usage of facts
56-
// This is currently used by the rule runner, openapi-cli and changelogv1
57+
// This is currently used by the rule runner
5758
export class OpenAPITraverser implements Traverse<OpenAPIV3.Document> {
5859
format = 'openapi3';
5960

@@ -472,6 +473,13 @@ export class OpenAPITraverser implements Traverse<OpenAPIV3.Document> {
472473
)}, found ${schema}`
473474
);
474475
} else if (isNotReferenceObject(schema)) {
476+
yield this.onSchema(schema, jsonPath, conceptualPath, {
477+
...location,
478+
jsonSchemaTrail: [],
479+
context: {
480+
type: 'body',
481+
},
482+
});
475483
yield this.onContentForBody(
476484
schema,
477485
contentType,
@@ -540,6 +548,14 @@ export class OpenAPITraverser implements Traverse<OpenAPIV3.Document> {
540548
location: FieldLocation
541549
): IterableIterator<IFact> {
542550
this.checkJsonTrail(jsonPath, schema);
551+
yield this.onSchema(schema, jsonPath, conceptualPath, {
552+
...location,
553+
context: {
554+
type: 'field',
555+
key,
556+
required,
557+
},
558+
});
543559
yield this.onField(
544560
key,
545561
schema,
@@ -551,7 +567,6 @@ export class OpenAPITraverser implements Traverse<OpenAPIV3.Document> {
551567
yield* this.traverseSchema(schema, jsonPath, conceptualPath, location);
552568
}
553569

554-
// TODO discriminate between ArraySchemaObject | NonArraySchemaObject
555570
*traverseSchema(
556571
schema: OpenAPIV3.SchemaObject | OpenAPIV3_1.SchemaObject,
557572
jsonPath: string,
@@ -592,6 +607,10 @@ export class OpenAPITraverser implements Traverse<OpenAPIV3.Document> {
592607
)}, found ${branchSchema}`
593608
);
594609
} else if (isNotReferenceObject(branchSchema)) {
610+
yield this.onSchema(branchSchema, newJsonPath, newConceptualPath, {
611+
...location,
612+
context: { type: branchType as any },
613+
});
595614
yield* this.traverseSchema(
596615
branchSchema,
597616
newJsonPath,
@@ -647,14 +666,20 @@ export class OpenAPITraverser implements Traverse<OpenAPIV3.Document> {
647666
)}, found ${arrayItems}`
648667
);
649668
} else if (isNotReferenceObject(arrayItems)) {
669+
const nextConceptualPath = [...conceptualPath, 'items'];
670+
const nextLocation = {
671+
...location,
672+
jsonSchemaTrail: [...(location.jsonSchemaTrail || []), 'items'],
673+
};
674+
yield this.onSchema(arrayItems, nextJsonPath, nextConceptualPath, {
675+
...nextLocation,
676+
context: { type: 'array' },
677+
});
650678
yield* this.traverseSchema(
651679
arrayItems,
652680
nextJsonPath,
653-
[...conceptualPath, 'items'],
654-
{
655-
...location,
656-
jsonSchemaTrail: [...(location.jsonSchemaTrail || []), 'items'],
657-
}
681+
nextConceptualPath,
682+
nextLocation
658683
);
659684
} else {
660685
this.warnings.push(
@@ -797,6 +822,27 @@ export class OpenAPITraverser implements Traverse<OpenAPIV3.Document> {
797822
};
798823
}
799824

825+
onSchema(
826+
schema: OpenAPIV3.SchemaObject | OpenAPIV3_1.SchemaObject,
827+
jsonPath: string,
828+
conceptualPath: IPathComponent[],
829+
location: SchemaLocation
830+
): FactVariant<OpenApiKind.Schema> {
831+
const flatSchema = this.getSchemaFact(schema);
832+
833+
return {
834+
value: {
835+
flatSchema,
836+
},
837+
location: {
838+
jsonPath,
839+
conceptualPath,
840+
conceptualLocation: location,
841+
kind: OpenApiKind.Schema,
842+
},
843+
};
844+
}
845+
800846
onBodyExample(
801847
example: OpenAPIV3.ExampleObject | OpenAPIV3_1.ExampleObject,
802848
contentType: string,

projects/openapi-utilities/src/openapi3/sdk/types/index.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ export type OpenApiFact =
4848
| OpenApiBodyExampleFact
4949
| OpenApiFieldFact
5050
| OpenApiSpecificationFact
51-
| OpenApiComponentSchemaExampleFact;
51+
| OpenApiComponentSchemaExampleFact
52+
| OpenApiSchemaFact;
5253

5354
export interface OpenApiSpecificationFact
5455
extends Omit<OpenAPIV3.Document, 'paths' | 'components'> {}
@@ -72,6 +73,10 @@ export interface OpenApiBodyFact {
7273
flatSchema: OpenApi3SchemaFact;
7374
}
7475

76+
export interface OpenApiSchemaFact {
77+
flatSchema: OpenApi3SchemaFact;
78+
}
79+
7580
export interface OpenApiBodyExampleFact extends OpenAPIV3.ExampleObject {
7681
contentType: string;
7782
name?: string;
@@ -120,6 +125,7 @@ export type OpenApiKindToFact = {
120125
[OpenApiKind.BodyExample]: OpenApiBodyExampleFact;
121126
[OpenApiKind.Field]: OpenApiFieldFact;
122127
[OpenApiKind.ComponentSchemaExample]: OpenApiComponentSchemaExampleFact;
128+
[OpenApiKind.Schema]: OpenApiSchemaFact;
123129
};
124130

125131
export interface FactVariant<FactKind extends OpenApiKind> {
@@ -175,7 +181,8 @@ export type IFact =
175181
| FactVariant<OpenApiKind.Body>
176182
| FactVariant<OpenApiKind.BodyExample>
177183
| FactVariant<OpenApiKind.Field>
178-
| FactVariant<OpenApiKind.ComponentSchemaExample>;
184+
| FactVariant<OpenApiKind.ComponentSchemaExample>
185+
| FactVariant<OpenApiKind.Schema>;
179186

180187
export type IChange =
181188
| ChangeVariant<OpenApiKind.Specification>
@@ -190,4 +197,5 @@ export type IChange =
190197
| ChangeVariant<OpenApiKind.Body>
191198
| ChangeVariant<OpenApiKind.BodyExample>
192199
| ChangeVariant<OpenApiKind.Field>
193-
| ChangeVariant<OpenApiKind.ComponentSchemaExample>;
200+
| ChangeVariant<OpenApiKind.ComponentSchemaExample>
201+
| ChangeVariant<OpenApiKind.Schema>;

projects/openapi-utilities/src/openapi3/sdk/types/location.ts

+41
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,46 @@ export type FieldLocation = OperationLocation &
9696
}
9797
);
9898

99+
type SchemaContext =
100+
| {
101+
type: 'polymorphic';
102+
key: 'oneOf' | 'anyOf' | 'allOf';
103+
}
104+
| {
105+
type: 'field';
106+
key: string;
107+
required: boolean;
108+
}
109+
| {
110+
type: 'array';
111+
}
112+
| {
113+
type: 'body';
114+
};
115+
116+
export type SchemaLocation = OperationLocation &
117+
(
118+
| {
119+
inRequest: {
120+
body: {
121+
contentType: string;
122+
};
123+
};
124+
jsonSchemaTrail: string[];
125+
context: SchemaContext;
126+
}
127+
| {
128+
inResponse: {
129+
body: {
130+
contentType: string;
131+
};
132+
statusCode: string;
133+
};
134+
jsonSchemaTrail: string[];
135+
context: SchemaContext;
136+
}
137+
);
138+
99139
export type ComponentSchemaLocation = {
100140
inComponentSchema: {
101141
schemaName: string;
@@ -141,6 +181,7 @@ export type ILocation = {
141181
| { conceptualLocation: BodyLocation; kind: OpenApiKind.Body }
142182
| { conceptualLocation: BodyExampleLocation; kind: OpenApiKind.BodyExample }
143183
| { conceptualLocation: FieldLocation; kind: OpenApiKind.Field }
184+
| { conceptualLocation: SchemaLocation; kind: OpenApiKind.Schema }
144185
| {
145186
conceptualLocation: ComponentSchemaLocation;
146187
kind: OpenApiKind.ComponentSchemaExample;

projects/openapi-utilities/src/openapi3/sdk/types/openApiKinds.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export enum OpenApiKind {
1111
Body = 'body',
1212
BodyExample = 'body-example',
1313
Field = 'field',
14+
Schema = 'schema',
1415
ComponentSchemaExample = 'component-schema-example',
1516
}
1617

projects/openapi-utilities/src/utilities/__tests__/__snapshots__/group-changes.test.ts.snap

+28
Original file line numberDiff line numberDiff line change
@@ -6046,6 +6046,34 @@ exports[`groupChangesAndRules diffs and groups changes between two open api file
60466046
},
60476047
},
60486048
},
6049+
"GET-~_~-/store/inventory" => {
6050+
"change": null,
6051+
"cookieParameters": {
6052+
"changes": Map {},
6053+
"hasRules": false,
6054+
},
6055+
"hasRules": false,
6056+
"headers": {
6057+
"changes": Map {},
6058+
"hasRules": false,
6059+
},
6060+
"method": "get",
6061+
"path": "/store/inventory",
6062+
"pathParameters": {
6063+
"changes": Map {},
6064+
"hasRules": false,
6065+
},
6066+
"queryParameters": {
6067+
"changes": Map {},
6068+
"hasRules": false,
6069+
},
6070+
"request": {
6071+
"bodyChanges": Map {},
6072+
"change": null,
6073+
"hasRules": false,
6074+
},
6075+
"responses": Map {},
6076+
},
60496077
"POST-~_~-/store/order" => {
60506078
"change": null,
60516079
"cookieParameters": {

projects/optic/src/commands/oas/specs/index.ts

-12
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,6 @@ export type { SpecFileOperation } from './files';
1212

1313
// patches and operations
1414
export { SpecFileOperations, SpecFiles, SpecFilesAsync } from './streams/files';
15-
export {
16-
SpecFacts,
17-
SpecFactsIterable,
18-
BodyExampleFacts,
19-
ComponentSchemaExampleFacts,
20-
OperationFacts,
21-
} from './streams/facts';
22-
export type {
23-
BodyExampleFact,
24-
ComponentSchemaExampleFact,
25-
OperationFact,
26-
} from './streams/facts';
2715

2816
// templates
2917
export { SpecTemplate } from './templates';

projects/optic/src/commands/oas/specs/streams/facts.ts

-79
This file was deleted.

0 commit comments

Comments
 (0)