Skip to content

Commit

Permalink
fix(cli): recursive type checking exceeding max call stack. (#5184)
Browse files Browse the repository at this point in the history
* fix(cli): recursive type checking exceeding max call stack.

* Enforce depth assignment instead of fallback to default value.

* Minor cleanup.

* Update Postman snapshots.

* Update PHP SDK snapshots.

* Update Ruby snapshots.

* Update ts-express snapshots.

* Update ts-sdk snapshots.

* Update c sharp snapshots.

* Update ts-sdk snapshots.

* fix

---------

Co-authored-by: dsinghvi <deep@usebirch.com>
  • Loading branch information
eyw520 and dsinghvi authored Nov 17, 2024
1 parent 1908ee6 commit b02a296
Show file tree
Hide file tree
Showing 620 changed files with 26,925 additions and 1,548 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
name: endpoint-recursive
auth: bearer
default-environment: Production
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
types:
RecursiveResponse:
type: list<Node>
Node:
discriminated: false
union:
- type: Node
- type: Node
- type: Node
- type: Node
- type: Node
- type: Terminate
Terminate:
properties:
end: string


service:
auth: false
base-path: ""
endpoints:
recursiveGet:
method: GET
path: /recursive
request:
name: RecursiveRequest
body:
properties:
query: string
response:
type: RecursiveResponse
examples:
- name: "Example Recursion"
request:
query: "foo"
response:
body:
- end: "bar"

Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
Original file line number Diff line number Diff line change
Expand Up @@ -510,4 +510,19 @@ describe("valid-example-endpoint-call", () => {

expect(violations).toEqual(expectedViolations);
});

it("endpoint-recursive-types", async () => {
const violations = await getViolationsForRule({
rule: ValidExampleEndpointCallRule,
absolutePathToWorkspace: join(
AbsoluteFilePath.of(__dirname),
RelativeFilePath.of("fixtures"),
RelativeFilePath.of("endpoint-recursive-types")
)
});

const expectedViolations: ValidationViolation[] = [];

expect(violations).toEqual(expectedViolations);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ export function validateExampleEndpointCallParameters<T>({
workspace,
typeResolver,
exampleResolver,
breadcrumbs
breadcrumbs,
depth: 0
}).map((val): RuleViolation => {
return { severity: "error", message: val.message };
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export function validateRequest({
exampleResolver,
workspace,
example,
breadcrumbs: ["request"]
breadcrumbs: ["request"],
depth: 0
}).map((val): RuleViolation => {
return { severity: "error", message: val.message };
})
Expand All @@ -57,7 +58,8 @@ export function validateRequest({
workspace,
typeResolver,
exampleResolver,
breadcrumbs: ["response", "body"]
breadcrumbs: ["response", "body"],
depth: 0
}).map((val): RuleViolation => {
return { severity: "error", message: val.message };
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ function validateBodyResponse({
exampleResolver,
file,
workspace,
breadcrumbs: ["response", "body"]
breadcrumbs: ["response", "body"],
depth: 0
}).map((val): RuleViolation => {
return {
severity: "error",
Expand Down Expand Up @@ -122,7 +123,8 @@ function validateBodyResponse({
exampleResolver,
file: errorDeclaration.file,
workspace,
breadcrumbs: ["response", "body"]
breadcrumbs: ["response", "body"],
depth: 0
}).map((val): RuleViolation => {
return { severity: "error", message: val.message };
})
Expand Down Expand Up @@ -177,7 +179,8 @@ function validateStreamResponse({
exampleResolver,
file,
workspace,
breadcrumbs: ["response", "body"]
breadcrumbs: ["response", "body"],
depth: 0
}).map((val): RuleViolation => {
return { severity: "error", message: val.message };
})
Expand Down Expand Up @@ -228,7 +231,8 @@ function validateSseResponse({
exampleResolver,
file,
workspace,
breadcrumbs: ["response", "body"]
breadcrumbs: ["response", "body"],
depth: 0
}).map((val): RuleViolation => {
return { severity: "error", message: val.message };
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ export const ValidExampleErrorRule: Rule = {
workspace,
typeResolver,
exampleResolver,
breadcrumbs: ["response", "body"]
breadcrumbs: ["response", "body"],
depth: 0
});
return violations.map((violation) => {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ export const ValidExampleTypeRule: Rule = {
typeResolver,
exampleResolver,
workspace,
breadcrumbs: []
breadcrumbs: [],
depth: 0
});
return violations.map((violation) => {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,8 @@ export function convertTypeExample({
exampleResolver,
file: fileContainingType,
workspace,
breadcrumbs: []
breadcrumbs: [],
depth: 0
});
if (violationsForMember.length === 0) {
return ExampleTypeShape.undiscriminatedUnion({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export function validateAliasExample({
typeResolver,
exampleResolver,
workspace,
breadcrumbs
breadcrumbs,
depth
}: {
rawAlias: string | RawSchemas.AliasSchema;
example: RawSchemas.ExampleTypeValueSchema;
Expand All @@ -22,6 +23,7 @@ export function validateAliasExample({
exampleResolver: ExampleResolver;
workspace: FernWorkspace;
breadcrumbs: string[];
depth: number;
}): ExampleViolation[] {
return validateTypeReferenceExample({
rawTypeReference: typeof rawAlias === "string" ? rawAlias : rawAlias.type,
Expand All @@ -30,6 +32,7 @@ export function validateAliasExample({
typeResolver,
exampleResolver,
workspace,
breadcrumbs
breadcrumbs,
depth: depth + 1
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ export function validateObjectExample({
exampleResolver,
workspace,
example,
breadcrumbs
breadcrumbs,
depth
}: {
// undefined for inline requests
typeName: string | undefined;
Expand All @@ -31,6 +32,7 @@ export function validateObjectExample({
exampleResolver: ExampleResolver;
workspace: FernWorkspace;
breadcrumbs: string[];
depth: number;
}): ExampleViolation[] {
if (!isPlainObject(example)) {
return getViolationsForMisshapenExample(example, "an object");
Expand Down Expand Up @@ -100,7 +102,8 @@ export function validateObjectExample({
workspace,
typeResolver,
exampleResolver,
breadcrumbs: [...breadcrumbs, `${exampleKey}`]
breadcrumbs: [...breadcrumbs, `${exampleKey}`],
depth: depth + 1
})
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export function validateTypeExample({
exampleResolver,
example,
workspace,
breadcrumbs
breadcrumbs,
depth
}: {
typeName: string;
typeDeclaration: RawSchemas.TypeDeclarationSchema;
Expand All @@ -28,6 +29,7 @@ export function validateTypeExample({
example: RawSchemas.ExampleTypeValueSchema;
workspace: FernWorkspace;
breadcrumbs: string[];
depth: number;
}): ExampleViolation[] {
return visitRawTypeDeclaration(typeDeclaration, {
alias: (rawAlias) => {
Expand All @@ -38,7 +40,8 @@ export function validateTypeExample({
exampleResolver,
example,
workspace,
breadcrumbs
breadcrumbs,
depth
});
},
enum: (rawEnum) => {
Expand All @@ -58,7 +61,8 @@ export function validateTypeExample({
typeResolver,
exampleResolver,
workspace,
breadcrumbs
breadcrumbs,
depth
});
},
discriminatedUnion: (rawUnion) => {
Expand All @@ -70,7 +74,8 @@ export function validateTypeExample({
typeResolver,
exampleResolver,
workspace,
breadcrumbs
breadcrumbs,
depth
});
},
undiscriminatedUnion: (rawUnion) => {
Expand All @@ -81,7 +86,8 @@ export function validateTypeExample({
typeResolver,
exampleResolver,
workspace,
breadcrumbs
breadcrumbs,
depth
});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,17 @@ const UUID_REGEX = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA

const RFC_3339_DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/;

const MAX_RECURSION_DEPTH = 16;

export function validateTypeReferenceExample({
rawTypeReference,
example,
typeResolver,
exampleResolver,
file,
workspace,
breadcrumbs
breadcrumbs,
depth
}: {
rawTypeReference: string;
example: RawSchemas.ExampleTypeReferenceSchema;
Expand All @@ -43,7 +46,17 @@ export function validateTypeReferenceExample({
file: FernFileContext;
workspace: FernWorkspace;
breadcrumbs: string[];
depth: number;
}): ExampleViolation[] {
if (depth > MAX_RECURSION_DEPTH) {
// This comment never reaches the user and serves as a termination condition for the recursion.
return [
{
message: "Example is too deeply nested. This may indicate a circular reference."
}
];
}

if (typeof example === "string" && example.startsWith(EXAMPLE_REFERENCE_PREFIX)) {
// if it's a reference to another example, we just need to compare the
// expected type with the referenced type
Expand Down Expand Up @@ -107,7 +120,8 @@ export function validateTypeReferenceExample({
typeResolver,
exampleResolver,
workspace,
breadcrumbs
breadcrumbs,
depth: depth + 1
});
},
map: ({ keyType, valueType }) => {
Expand All @@ -122,7 +136,8 @@ export function validateTypeReferenceExample({
exampleResolver,
file,
workspace,
breadcrumbs: [...breadcrumbs, exampleKey]
breadcrumbs: [...breadcrumbs, exampleKey],
depth: depth + 1
}),
...validateTypeReferenceExample({
rawTypeReference: valueType,
Expand All @@ -131,7 +146,8 @@ export function validateTypeReferenceExample({
exampleResolver,
file,
workspace,
breadcrumbs: [...breadcrumbs, exampleKey]
breadcrumbs: [...breadcrumbs, exampleKey],
depth: depth + 1
})
]);
},
Expand All @@ -147,7 +163,8 @@ export function validateTypeReferenceExample({
exampleResolver,
file,
workspace,
breadcrumbs: [...breadcrumbs, `${idx}`]
breadcrumbs: [...breadcrumbs, `${idx}`],
depth: depth + 1
})
);
},
Expand Down Expand Up @@ -176,7 +193,8 @@ export function validateTypeReferenceExample({
exampleResolver,
file,
workspace,
breadcrumbs: [...breadcrumbs, `${idx}`]
breadcrumbs: [...breadcrumbs, `${idx}`],
depth: depth + 1
})
);
},
Expand All @@ -191,7 +209,8 @@ export function validateTypeReferenceExample({
exampleResolver,
file,
workspace,
breadcrumbs
breadcrumbs,
depth: depth + 1
});
},
unknown: () => {
Expand Down
Loading

0 comments on commit b02a296

Please sign in to comment.