Skip to content

Commit

Permalink
fix: fixes union type with discriminator in go (asyncapi#1967)
Browse files Browse the repository at this point in the history
  • Loading branch information
kennethaasan authored Apr 25, 2024
1 parent 4d0bb16 commit ecd9618
Show file tree
Hide file tree
Showing 5 changed files with 307 additions and 141 deletions.
12 changes: 4 additions & 8 deletions docs/migrations/version-3-to-4.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ While the above changes work for primitives, it's problematic for objects with a

```go
type Vehicle interface {
IsVehicleType() bool
IsVehicleVehicleType()
}

type Car struct {
Expand All @@ -270,19 +270,15 @@ type Car struct {
AdditionalProperties map[string]interface{}
}

func (serdp Car) IsVehicleType() bool {
return true
}
func (r Car) IsVehicleVehicleType() {}

type Truck struct {
VehicleType *VehicleType
RegistrationPlate string
AdditionalProperties map[string]interface{}
}

func (serdp Truck) IsVehicleType() bool {
return true
}
func (r Truck) IsVehicleVehicleType() {}

type VehicleType uint

Expand Down Expand Up @@ -313,7 +309,7 @@ Modelina now has support for nullable and required properties in go structs. Thi
```go
type info struct {
name string // required
description *string // nullable
description *string // nullable
version *float64
isDevelopment *bool
}
Expand Down
39 changes: 27 additions & 12 deletions src/generators/go/renderers/StructRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { StructPresetType } from '../GoPreset';
import {
ConstrainedObjectModel,
ConstrainedObjectPropertyModel,
ConstrainedReferenceModel
ConstrainedReferenceModel,
ConstrainedUnionModel
} from '../../../models';
import { GoOptions } from '../GoGenerator';
import { FormatHelpers } from '../../../helpers/FormatHelpers';
Expand All @@ -26,10 +27,7 @@ export class StructRenderer extends GoRenderer<ConstrainedObjectModel> {

let discriminator = '';

if (
this.model.options.parents?.length &&
this.model.options.discriminator?.discriminator
) {
if (this.model.options.parents?.length) {
discriminator = await this.runDiscriminatorFuncPreset();
}

Expand Down Expand Up @@ -71,20 +69,37 @@ export const GO_DEFAULT_STRUCT_PRESET: StructPresetType<GoOptions> = {
},
field({ field }) {
let fieldType = field.property.type;
if (field.property instanceof ConstrainedReferenceModel) {
if (
field.property instanceof ConstrainedReferenceModel &&
!(
field.property.ref instanceof ConstrainedUnionModel &&
field.property.ref.options.discriminator
)
) {
fieldType = `*${fieldType}`;
}
return `${field.propertyName} ${fieldType}`;
},
discriminator({ model }) {
if (!model.options.discriminator?.discriminator) {
const { parents } = model.options;

if (!parents?.length) {
return '';
}

return `func (serdp ${model.name}) Is${FormatHelpers.toPascalCase(
model.options.discriminator.discriminator
)}() bool {
return true
}`;
return parents
.map((parent) => {
if (!parent.options.discriminator) {
return undefined;
}

return `func (r ${model.name}) Is${FormatHelpers.toPascalCase(
parent.name
)}${FormatHelpers.toPascalCase(
parent.options.discriminator.discriminator
)}() {}`;
})
.filter((parent) => !!parent)
.join('\n\n');
}
};
24 changes: 13 additions & 11 deletions src/generators/go/renderers/UnionRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@ import {
import { GoOptions } from '../GoGenerator';
import { FormatHelpers } from '../../../helpers/FormatHelpers';

const unionIncludesPrimitives = (model: ConstrainedUnionModel): boolean => {
return !model.union.every(
(union) =>
union instanceof ConstrainedObjectModel ||
(union instanceof ConstrainedReferenceModel &&
union.ref instanceof ConstrainedObjectModel)
const unionIncludesDiscriminator = (model: ConstrainedUnionModel): boolean => {
return (
!!model.options.discriminator &&
model.union.every(
(union) =>
union instanceof ConstrainedObjectModel ||
(union instanceof ConstrainedReferenceModel &&
union.ref instanceof ConstrainedObjectModel)
)
);
};

Expand All @@ -29,10 +32,7 @@ export class UnionRenderer extends GoRenderer<ConstrainedUnionModel> {
`${this.model.name} represents a ${this.model.name} model.`
);

if (
!unionIncludesPrimitives(this.model) &&
this.model.options.discriminator
) {
if (unionIncludesDiscriminator(this.model)) {
const content: string[] = [await this.runDiscriminatorAccessorPreset()];

return `${doc}
Expand Down Expand Up @@ -98,7 +98,9 @@ export const GO_DEFAULT_UNION_PRESET: UnionPresetType<GoOptions> = {
}

return `Is${FormatHelpers.toPascalCase(
model.name
)}${FormatHelpers.toPascalCase(
model.options.discriminator.discriminator
)}() bool`;
)}()`;
}
};
196 changes: 144 additions & 52 deletions test/generators/go/GoGenerator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,75 +95,167 @@ describe('GoGenerator', () => {
expect(result).toMatchSnapshot();
});

test('should render interfaces for objects with discriminator', async () => {
const asyncapiDoc = {
asyncapi: '2.6.0',
info: {
title: 'Vehicle example',
version: '1.0.0'
},
channels: {},
components: {
messages: {
Cargo: {
payload: {
title: 'Cargo',
describe('oneOf/discriminator', () => {
test('should render interfaces for objects with discriminator', async () => {
const asyncapiDoc = {
asyncapi: '2.6.0',
info: {
title: 'Vehicle example',
version: '1.0.0'
},
channels: {},
components: {
messages: {
Cargo: {
payload: {
title: 'Cargo',
type: 'object',
properties: {
vehicle: {
$ref: '#/components/schemas/Vehicle'
}
}
}
}
},
schemas: {
Vehicle: {
title: 'Vehicle',
type: 'object',
discriminator: 'vehicleType',
properties: {
vehicleType: {
title: 'VehicleType',
type: 'string'
},
registrationPlate: {
title: 'RegistrationPlate',
type: 'string'
}
},
required: ['vehicleType', 'registrationPlate'],
oneOf: [
{
$ref: '#/components/schemas/Car'
},
{
$ref: '#/components/schemas/Truck'
}
]
},
Car: {
type: 'object',
properties: {
vehicleType: {
const: 'Car'
}
}
},
Truck: {
type: 'object',
properties: {
vehicle: {
$ref: '#/components/schemas/Vehicle'
vehicleType: {
const: 'Truck'
}
}
}
}
}
};

const models = await generator.generate(asyncapiDoc);
expect(models.map((model) => model.result)).toMatchSnapshot();
});

test('handle setting title with const', async () => {
const asyncapiDoc = {
asyncapi: '2.5.0',
info: {
title: 'CloudEvent example',
version: '1.0.0'
},
schemas: {
Vehicle: {
title: 'Vehicle',
type: 'object',
discriminator: 'vehicleType',
properties: {
vehicleType: {
title: 'VehicleType',
type: 'string'
},
registrationPlate: {
title: 'RegistrationPlate',
type: 'string'
channels: {
pet: {
publish: {
message: {
oneOf: [
{
$ref: '#/components/messages/Dog'
},
{
$ref: '#/components/messages/Cat'
}
]
}
},
required: ['vehicleType', 'registrationPlate'],
oneOf: [
{
$ref: '#/components/schemas/Car'
},
{
$ref: '#/components/schemas/Truck'
}
}
},
components: {
messages: {
Dog: {
payload: {
title: 'Dog',
allOf: [
{
$ref: '#/components/schemas/CloudEvent'
},
{
$ref: '#/components/schemas/Dog'
}
]
}
]
},
Car: {
type: 'object',
properties: {
vehicleType: {
const: 'Car'
},
Cat: {
payload: {
title: 'Cat',
allOf: [
{
$ref: '#/components/schemas/CloudEvent'
},
{
$ref: '#/components/schemas/Cat'
}
]
}
}
},
Truck: {
type: 'object',
properties: {
vehicleType: {
const: 'Truck'
schemas: {
CloudEvent: {
title: 'CloudEvent',
type: 'object',
discriminator: 'type',
properties: {
type: {
type: 'string'
}
},
required: ['type']
},
Dog: {
type: 'object',
properties: {
type: {
title: 'DogType',
const: 'Dog'
}
}
},
Cat: {
type: 'object',
properties: {
type: {
title: 'CatType',
const: 'Cat'
}
}
}
}
}
}
};
};

const models = await generator.generate(asyncapiDoc);
expect(models.map((model) => model.result)).toMatchSnapshot();
const models = await generator.generate(asyncapiDoc);
expect(models.map((model) => model.result)).toMatchSnapshot();
});
});

test('should work custom preset for `struct` type', async () => {
Expand Down
Loading

0 comments on commit ecd9618

Please sign in to comment.