Skip to content
This repository has been archived by the owner on Oct 11, 2023. It is now read-only.

Commit

Permalink
Fix: Circular dependency issue with sibling schemas/definitions. (#375)
Browse files Browse the repository at this point in the history
  • Loading branch information
timotheeguerin authored Dec 15, 2020
1 parent 8a8a88d commit 2736459
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 43 deletions.
1 change: 1 addition & 0 deletions modelerfour/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- **Feature** Added new flag `always-create-accept-parameter` to enable/disable accept param auto generation. ([PR 366](https://github.com/Azure/autorest.modelerfour/pull/366))
- **Fix** Allow request with body being a file and `application/json` content-type. ([PR 363](https://github.com/Azure/autorest.modelerfour/pull/363))
- **Fix** Dictionaries of dictionaries not being modeled as such(`dict[str, object]` instead of `dict[str, dict[str, str]]`). ([PR 372](https://github.com/Azure/autorest.modelerfour/pull/372))
- **Fix** Issue with sibling models(Model just being a ref of another) causing circular dependency exception. ([PR 375](https://github.com/Azure/autorest.modelerfour/pull/375))
- **Fix** Issue with duplicates schemas names due to consequtive name duplicate removal. ([PR 374](https://github.com/Azure/autorest.modelerfour/pull/374))

#### 4.15.x
Expand Down
103 changes: 60 additions & 43 deletions modelerfour/src/quality-precheck/prechecker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,18 @@ export async function processRequest(host: Host) {
}
}

interface DereferencedSchema {
key: string;
value: Dereferenced<Schema>;
}

export class QualityPreChecker {
input: oai3;
options: ModelerFourOptions = {};
protected interpret: Interpretations;

constructor(protected session: Session<oai3>) {
this.input = session.model; // shadow(session.model, filename);

this.interpret = new Interpretations(session);
}

Expand Down Expand Up @@ -244,48 +248,7 @@ export class QualityPreChecker {
// found two schemas that are indeed the same.
// stop, find all the $refs to the second one, and rewrite them to go to the first one.
// then go back and start again.

delete this.input.components.schemas[schemas[1].key];
const text = JSON.stringify(this.input);
this.input = JSON.parse(
text.replace(
new RegExp(`"\\#\\/components\\/schemas\\/${schemas[1].key}"`, "g"),
`"#/components/schemas/${schemas[0].key}"`,
),
);

// update metadata to match
if (this.input?.components?.schemas?.[schemas[0].key]) {
const primarySchema = this.resolve(this.input.components.schemas[schemas[0].key]);
const primaryMetadata = primarySchema.instance["x-ms-metadata"];
const secondaryMetadata = schemas[1].value.instance["x-ms-metadata"];

if (primaryMetadata && secondaryMetadata) {
primaryMetadata.apiVersions = [
...new Set<string>([
...(primaryMetadata.apiVersions || []),
...(secondaryMetadata.apiVersions || []),
]),
];
primaryMetadata.filename = [
...new Set<string>([...(primaryMetadata.filename || []), ...(secondaryMetadata.filename || [])]),
];
primaryMetadata.originalLocations = [
...new Set<string>([
...(primaryMetadata.originalLocations || []),
...(secondaryMetadata.originalLocations || []),
]),
];
primaryMetadata["x-ms-secondary-file"] = !(
!primaryMetadata["x-ms-secondary-file"] || !secondaryMetadata["x-ms-secondary-file"]
);
}
}
this.session.verbose(
`Schema ${name} has multiple identical declarations, reducing to just one - removing ${schemas[1].key} `,
["PreCheck", "ReducingSchema"],
);

this.removeDuplicateSchemas(name, schemas[0], schemas[1]);
// Restart the scan now that the duplicate has been removed
return true;
}
Expand Down Expand Up @@ -336,6 +299,60 @@ export class QualityPreChecker {
return undefined;
}

private findSchemaToRemove(
schema1: DereferencedSchema,
schema2: DereferencedSchema,
): { keep: DereferencedSchema; remove: DereferencedSchema } {
const schema1Ref = this.input.components?.schemas?.[schema1.key].$ref;
// If schema1 is pointing to schema2 then we should delete schema1
if (schema1Ref && schema1Ref === `#/components/schemas/${schema2.key}`) {
return { remove: schema1, keep: schema2 };
}
return { remove: schema2, keep: schema1 };
}

private removeDuplicateSchemas(name: string, schema1: DereferencedSchema, schema2: DereferencedSchema) {
const { keep: schemaToKeep, remove: schemaToRemove} = this.findSchemaToRemove(schema1, schema2);
delete this.input.components!.schemas![schemaToRemove.key];
const text = JSON.stringify(this.input);
this.input = JSON.parse(
text.replace(
new RegExp(`"\\#\\/components\\/schemas\\/${schemaToRemove.key}"`, "g"),
`"#/components/schemas/${schemaToKeep.key}"`,
),
);

// update metadata to match
if (this.input?.components?.schemas?.[schemaToKeep.key]) {
const primarySchema = this.resolve(this.input.components.schemas[schemaToKeep.key]);
const primaryMetadata = primarySchema.instance["x-ms-metadata"];
const secondaryMetadata = schemaToRemove.value.instance["x-ms-metadata"];

if (primaryMetadata && secondaryMetadata) {
primaryMetadata.apiVersions = [
...new Set<string>([...(primaryMetadata.apiVersions || []), ...(secondaryMetadata.apiVersions || [])]),
];
primaryMetadata.filename = [
...new Set<string>([...(primaryMetadata.filename || []), ...(secondaryMetadata.filename || [])]),
];
primaryMetadata.originalLocations = [
...new Set<string>([
...(primaryMetadata.originalLocations || []),
...(secondaryMetadata.originalLocations || []),
]),
];
primaryMetadata["x-ms-secondary-file"] = !(
!primaryMetadata["x-ms-secondary-file"] || !secondaryMetadata["x-ms-secondary-file"]
);
}
}

this.session.verbose(
`Schema ${name} has multiple identical declarations, reducing to just one - removing: ${schemaToRemove.key}, keeping: ${schemaToKeep.key}`,
["PreCheck", "ReducingSchema"],
);
}

fixUpSchemasThatUseAllOfInsteadOfJustRef() {
const schemas = this.input.components?.schemas;
if (schemas) {
Expand Down
28 changes: 28 additions & 0 deletions modelerfour/test/quality-precheck/prechecker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,32 @@ describe("Prechecker", () => {
fail("No 'ChildSchema' found!");
}
});

it("remove the sibling schema with the $ref", async () => {
const spec = createTestSpec();

addSchema(spec, "SiblingSchema", {
$ref: "#/components/schemas/MainSchema"
});

addSchema(spec, "MainSchema", {
type: "object",
"x-ms-client-name": "MainSchema",
properties: {
name: {
type: "string",
},
},
});

const client = await PreCheckerClient.create(spec);
const model = client.result;
const schemas = model.components!.schemas!;
expect(schemas["SiblingSchema"]).toBeUndefined();
expect(schemas["MainSchema"]).not.toBeUndefined();

console.error(schemas["MainSchema"]);
const mainSchema: Schema = schemas["MainSchema"] as any;
expect(mainSchema.properties?.name.type).toEqual("string");
});
});

0 comments on commit 2736459

Please sign in to comment.