Skip to content

Commit

Permalink
fix: address recursive loop in example gen with a max depth and lookb…
Browse files Browse the repository at this point in the history
…ack (#3086)

fix recursive loop in example gen
  • Loading branch information
armandobelardo authored Feb 29, 2024
1 parent f2121e4 commit 82de747
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 66 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,67 @@ exports[`open api parser axle simple 1`] = `
},
},
"types": {
"Account": {
"docs": "An Account represents an account with an insurance carrier and includes high-level account information (e.g. name) and any Policy objects associated with the Account.",
"properties": {
"carrier": "string",
"createdAt": "string",
"email": "optional<string>",
"firstName": "string",
"id": "string",
"lastName": "string",
"modifiedAt": "string",
"phone": "optional<string>",
"policies": "list<string>",
"refreshedAt": "string",
},
},
"GetAccountResponse": {
"docs": undefined,
"properties": {
"data": "optional<root.Account>",
"data": "optional<Account>",
"success": "optional<root.Success>",
},
},
"Policy": {
"docs": "A Policy represents a specific policy associated with an Account and includes high-level policy information (e.g. policy number) and any children objects (e.g., coverages) associated with the policy.",
"properties": {
"account": "string",
"address": "root.Address",
"carrier": "string",
"coverages": {
"docs": "",
"type": "list<root.Coverage>",
},
"createdAt": "string",
"effectiveDate": "string",
"expirationDate": "string",
"id": "string",
"insureds": {
"docs": "",
"type": "list<root.Insured>",
},
"isActive": "boolean",
"modifiedAt": "string",
"policyNumber": "string",
"properties": {
"docs": "",
"type": "list<root.Property>",
},
"refreshedAt": "string",
"thirdParties": {
"docs": "",
"type": "list<root.ThirdParty>",
},
"type": "PolicyType",
},
},
"PolicyType": {
"enum": [
"auto",
"motorcycle",
],
},
},
},
"carriers.yml": {
Expand Down Expand Up @@ -270,6 +324,7 @@ exports[`open api parser axle simple 1`] = `
},
"policies.yml": {
"imports": {
"accounts": "accounts.yml",
"root": "__package__.yml",
},
"service": {
Expand Down Expand Up @@ -386,7 +441,7 @@ exports[`open api parser axle simple 1`] = `
"GetPolicyResponse": {
"docs": undefined,
"properties": {
"data": "optional<root.Policy>",
"data": "optional<accounts.Policy>",
"success": "optional<root.Success>",
},
},
Expand Down Expand Up @@ -481,21 +536,6 @@ Auth codes are ephemeral and expire after 10 minutes, while accessTokens do not
},
"packageMarkerFile": {
"types": {
"Account": {
"docs": "An Account represents an account with an insurance carrier and includes high-level account information (e.g. name) and any Policy objects associated with the Account.",
"properties": {
"carrier": "string",
"createdAt": "string",
"email": "optional<string>",
"firstName": "string",
"id": "string",
"lastName": "string",
"modifiedAt": "string",
"phone": "optional<string>",
"policies": "list<string>",
"refreshedAt": "string",
},
},
"Address": {
"docs": undefined,
"properties": {
Expand Down Expand Up @@ -566,45 +606,6 @@ Auth codes are ephemeral and expire after 10 minutes, while accessTokens do not
"type": "optional<string>",
},
},
"Policy": {
"docs": "A Policy represents a specific policy associated with an Account and includes high-level policy information (e.g. policy number) and any children objects (e.g., coverages) associated with the policy.",
"properties": {
"account": "string",
"address": "Address",
"carrier": "string",
"coverages": {
"docs": "",
"type": "list<Coverage>",
},
"createdAt": "string",
"effectiveDate": "string",
"expirationDate": "string",
"id": "string",
"insureds": {
"docs": "",
"type": "list<Insured>",
},
"isActive": "boolean",
"modifiedAt": "string",
"policyNumber": "string",
"properties": {
"docs": "",
"type": "list<Property>",
},
"refreshedAt": "string",
"thirdParties": {
"docs": "",
"type": "list<ThirdParty>",
},
"type": "PolicyType",
},
},
"PolicyType": {
"enum": [
"auto",
"motorcycle",
],
},
"Property": {
"docs": undefined,
"properties": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export declare namespace ExampleTypeFactory {
ignoreOptionals: boolean;
/* True if example is for query or path parameter */
isParameter: boolean;

maxCheckerDepth?: number;
}
}

Expand All @@ -44,7 +46,14 @@ export class ExampleTypeFactory {
example: unknown | undefined;
options: ExampleTypeFactory.Options;
}): FullExample | undefined {
return this.buildExampleHelper({ schema, visitedSchemaIds: new Set(), example, options, depth: 0 });
return this.buildExampleHelper({
schema,
visitedSchemaIds: new Set(),
example,
// Default maxCheckerDepth to 5
options: { ...options, maxCheckerDepth: options.maxCheckerDepth ?? 5 },
depth: 0
});
}

private buildExampleHelper({
Expand All @@ -71,7 +80,7 @@ export class ExampleTypeFactory {
case "nullable": {
if (
example == null &&
!this.hasExample(schema.value) &&
!this.hasExample(schema.value, 0, visitedSchemaIds, options) &&
(options.ignoreOptionals || this.exceedsMaxDepth(depth, options))
) {
return undefined;
Expand All @@ -97,7 +106,7 @@ export class ExampleTypeFactory {
case "optional": {
if (
example == null &&
!this.hasExample(schema.value) &&
!this.hasExample(schema.value, 0, visitedSchemaIds, options) &&
(options.ignoreOptionals || this.exceedsMaxDepth(depth, options))
) {
return undefined;
Expand Down Expand Up @@ -372,23 +381,34 @@ export class ExampleTypeFactory {
}
}

private hasExample(schema: SchemaWithExample): boolean {
private hasExample(
schema: SchemaWithExample,
depth: number,
visitedSchemaIds: Set<SchemaId> = new Set(),
options: ExampleTypeFactory.Options
): boolean {
if (this.exceedsMaxCheckerDepth(depth, options)) {
return false;
}
switch (schema.type) {
case "array":
return this.hasExample(schema.value);
return this.hasExample(schema.value, depth + 1, visitedSchemaIds, options);
case "enum":
return schema.example != null;
case "literal":
return false;
case "map":
return schema.key.schema.example != null && this.hasExample(schema.value);
return (
schema.key.schema.example != null &&
this.hasExample(schema.value, depth + 1, visitedSchemaIds, options)
);
case "object": {
const objectExample = schema.fullExamples != null && schema.fullExamples.length > 0;
if (objectExample) {
return true;
}
for (const property of schema.properties) {
if (this.hasExample(property.schema)) {
if (this.hasExample(property.schema, depth + 1, visitedSchemaIds, options)) {
return true;
}
}
Expand All @@ -398,20 +418,31 @@ export class ExampleTypeFactory {
return schema.schema.example != null;
case "reference": {
const resolvedSchema = this.schemas[schema.schema];
if (resolvedSchema != null) {
return this.hasExample(resolvedSchema);

if (resolvedSchema != null && !visitedSchemaIds.has(schema.schema)) {
visitedSchemaIds.add(schema.schema);
const hasExample = this.hasExample(resolvedSchema, depth, visitedSchemaIds, options);
visitedSchemaIds.delete(schema.schema);
return hasExample;
}

return false;
}
case "unknown":
return schema.example != null;
case "oneOf":
return Object.values(schema.value.schemas).some((schema) => this.hasExample(schema));
return Object.values(schema.value.schemas).some((schema) =>
this.hasExample(schema, depth, visitedSchemaIds, options)
);
default:
return false;
}
}

private exceedsMaxCheckerDepth(depth: number, options: ExampleTypeFactory.Options): boolean {
return depth > (options.maxCheckerDepth ?? 0);
}

private exceedsMaxDepth(depth: number, options: ExampleTypeFactory.Options): boolean {
return depth > (options.maxDepth ?? 0);
}
Expand Down

0 comments on commit 82de747

Please sign in to comment.