Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion packages/compiler/src/core/type-relation-checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -886,6 +886,29 @@ export function createTypeRelationChecker(program: Program, checker: Checker): T
return [Related.false, [createUnassignableDiagnostic(source, target, diagnosticTarget)]];
}

function isAssignableToEnumDeep(enum1: Enum, enum2: Enum) {
if (enum1.node === enum2.node) {
// In the case that one enum is cloned from another (i.e. during OpenAPI3 version
// projections) the nodes will still be the same even if the enums objects are not
// the same object reference as versioning does a shallow clone
return true;
}

// If the nodes are still not similar, we can check whether the two enums are the same
// name and in the same namespace
function isSameNamespace(namespace1: Namespace | undefined, namespace2: Namespace | undefined) {
if (namespace1 === undefined && namespace2 === undefined) {
return true;
}

if (namespace1?.name !== namespace2?.name) {
return false;
}
return isSameNamespace(namespace1?.namespace, namespace2?.namespace);
}
return enum1.name === enum2.name && isSameNamespace(enum1.namespace, enum2.namespace);
}

function isAssignableToEnum(
source: Type,
target: Enum,
Expand All @@ -899,7 +922,7 @@ export function createTypeRelationChecker(program: Program, checker: Checker): T
return [Related.false, [createUnassignableDiagnostic(source, target, diagnosticTarget)]];
}
case "EnumMember":
if (source.enum === target) {
if (source.enum === target || isAssignableToEnumDeep(source.enum, target)) {
return [Related.true, []];
} else {
return [Related.false, [createUnassignableDiagnostic(source, target, diagnosticTarget)]];
Expand Down
72 changes: 72 additions & 0 deletions packages/openapi3/test/versioning.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,78 @@ worksFor(supportedVersions, ({ openApiFor, version: specVersion }) => {
.using("Http", "Rest", "Versioning")
.emit("@typespec/openapi3", { "openapi-versions": [specVersion] });

it("works with enums for examples", async () => {
const { v1, v2 } = await openApiForVersions(
`
@versioned(Versions)
@service(#{title: "My Service"})
namespace MyService {
enum Versions {
v1,
v2
}

enum MyEnum {
VALUE_1,
VALUE_2
}

model MyBaseModel {
id: string;
}

model MyModel1 extends MyBaseModel {
type: "Type1";
prop1: MyEnum;
@added(Versions.v2) prop2?: string;
@removed(Versions.v2) prop3?: int32;
}

model MyModel2 extends MyBaseModel {
type: "Type2";
prop2: string;
}

@discriminated(#{ discriminatorPropertyName: "type", envelope: "none" })
union MyUnion {
Type1: MyModel1;
Type2: MyModel2;
}

@route("/read1")
@added(Versions.v1)
@opExample(#{returnType: #{type: "Type1", id: "id", prop1: MyEnum.VALUE_1, prop3: 234}})
op read1(): MyUnion;
}
`,
["v1", "v2"],
);

deepStrictEqual((v1 as any).paths["/read1"].get.responses["200"], {
content: {
"application/json": {
schema: { $ref: "#/components/schemas/MyUnion" },
example: {
type: "Type1",
id: "id",
prop1: "VALUE_1",
prop3: 234
}
}
},
description: "The request has succeeded.",
});
deepStrictEqual((v2 as any).paths["/read1"].get.responses["200"], {
content: {
"application/json": {
schema: { $ref: "#/components/schemas/MyUnion" },
example: {} // example is empty for v2 since the example contains a property deleted in v2
}
},
description: "The request has succeeded.",
});
});

it("works with models", async () => {
const { v1, v2, v3 } = await openApiForVersions(
`
Expand Down