Skip to content

Commit 54588ba

Browse files
feat: support default jackson subtype for unions (#2323)
1 parent c7a6530 commit 54588ba

File tree

3 files changed

+487
-9
lines changed

3 files changed

+487
-9
lines changed

src/generators/java/presets/JacksonPreset.ts

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {
22
ConstrainedDictionaryModel,
33
ConstrainedObjectModel,
4-
ConstrainedReferenceModel
4+
ConstrainedReferenceModel,
5+
ConstrainedUnionModel
56
} from '../../../models';
67
import { JavaPreset } from '../JavaPreset';
78

@@ -111,14 +112,34 @@ ${content}`;
111112

112113
if (model.options.discriminator) {
113114
const { discriminator } = model.options;
114-
blocks.push(
115-
renderer.renderAnnotation('JsonTypeInfo', {
116-
use: 'JsonTypeInfo.Id.NAME',
117-
include: 'JsonTypeInfo.As.EXISTING_PROPERTY',
118-
property: `"${discriminator.discriminator}"`,
119-
visible: 'true'
120-
})
121-
);
115+
const defaultDiscriminator =
116+
model.originalInput?.properties?.[discriminator.discriminator]
117+
?.default;
118+
119+
if (defaultDiscriminator) {
120+
blocks.push(
121+
renderer.renderAnnotation('JsonTypeInfo', {
122+
use: 'JsonTypeInfo.Id.NAME',
123+
include: 'JsonTypeInfo.As.EXISTING_PROPERTY',
124+
defaultImpl: findClassNameOfSubtype(
125+
model,
126+
discriminator.discriminator,
127+
defaultDiscriminator
128+
),
129+
property: `"${discriminator.discriminator}"`,
130+
visible: 'true'
131+
})
132+
);
133+
} else {
134+
blocks.push(
135+
renderer.renderAnnotation('JsonTypeInfo', {
136+
use: 'JsonTypeInfo.Id.NAME',
137+
include: 'JsonTypeInfo.As.EXISTING_PROPERTY',
138+
property: `"${discriminator.discriminator}"`,
139+
visible: 'true'
140+
})
141+
);
142+
}
122143

123144
const types = model.union
124145
.map((union) => {
@@ -169,3 +190,27 @@ ${content}`;
169190
}
170191
}
171192
};
193+
194+
function findClassNameOfSubtype(
195+
model: ConstrainedUnionModel,
196+
discriminatorPropertyName: string,
197+
discriminatorValue: string
198+
): string {
199+
for (const union of model.union) {
200+
if (
201+
union instanceof ConstrainedReferenceModel &&
202+
union.ref instanceof ConstrainedObjectModel
203+
) {
204+
const discriminatorProp = Object.values(union.ref.properties).find(
205+
(model) => model.unconstrainedPropertyName === discriminatorPropertyName
206+
);
207+
if (
208+
discriminatorProp?.property.options.const?.originalInput ===
209+
discriminatorValue
210+
) {
211+
return `${union.name}.class`;
212+
}
213+
}
214+
}
215+
return `${discriminatorValue}.class`;
216+
}

test/generators/java/presets/JacksonPreset.spec.ts

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,241 @@ describe('JAVA_JACKSON_PRESET', () => {
118118
expect(models.map((model) => model.result)).toMatchSnapshot();
119119
});
120120

121+
test('handle oneOf with default with AsyncAPI with discriminator with Jackson', async () => {
122+
const asyncapiDoc = {
123+
asyncapi: '2.6.0',
124+
info: {
125+
title: 'CloudEvent example',
126+
version: '1.0.0'
127+
},
128+
channels: {
129+
owner: {
130+
publish: {
131+
message: {
132+
$ref: '#/components/messages/Owner'
133+
}
134+
}
135+
}
136+
},
137+
components: {
138+
messages: {
139+
Owner: {
140+
payload: {
141+
$ref: '#/components/schemas/Owner'
142+
}
143+
}
144+
},
145+
schemas: {
146+
Owner: {
147+
type: 'object',
148+
properties: {
149+
name: {
150+
type: 'string'
151+
},
152+
pets: {
153+
type: 'array',
154+
items: {
155+
$ref: '#/components/schemas/Pet'
156+
}
157+
}
158+
}
159+
},
160+
Pet: {
161+
title: 'Pet',
162+
type: 'object',
163+
discriminator: 'petType',
164+
properties: {
165+
petType: {
166+
type: 'string',
167+
default: 'Fish'
168+
}
169+
},
170+
required: ['petType'],
171+
oneOf: [
172+
{
173+
$ref: '#/components/schemas/Fish'
174+
},
175+
{
176+
$ref: '#/components/schemas/Bird'
177+
},
178+
{
179+
$ref: '#/components/schemas/FlyingFish'
180+
}
181+
]
182+
},
183+
Bird: {
184+
title: 'Bird',
185+
properties: {
186+
breed: {
187+
type: String
188+
}
189+
},
190+
allOf: [
191+
{
192+
$ref: '#/components/schemas/Pet'
193+
}
194+
]
195+
},
196+
Fish: {
197+
title: 'Fish',
198+
allOf: [
199+
{
200+
$ref: '#/components/schemas/Pet'
201+
}
202+
]
203+
},
204+
FlyingFish: {
205+
title: 'FlyingFish',
206+
type: 'object',
207+
allOf: [
208+
{
209+
$ref: '#/components/schemas/Fish'
210+
}
211+
],
212+
properties: {
213+
breed: {
214+
const: 'FlyingNemo'
215+
}
216+
}
217+
}
218+
}
219+
}
220+
};
221+
222+
const models = await generator.generate(asyncapiDoc);
223+
expect(models.map((model) => model.result)).toMatchSnapshot();
224+
});
225+
226+
test('handle oneOf with default with AsyncAPI 3.0 with custom discriminator with Jackson', async () => {
227+
const asyncapiDoc = {
228+
asyncapi: '3.0.0',
229+
info: {
230+
title: 'CloudEvent example',
231+
version: '1.0.0'
232+
},
233+
channels: {
234+
owner: {
235+
address: 'owner',
236+
messages: {
237+
Owner: {
238+
$ref: '#/components/messages/Owner'
239+
}
240+
}
241+
}
242+
},
243+
operations: {
244+
ownerAvailable: {
245+
action: 'receive',
246+
channel: {
247+
$ref: '#/channels/owner'
248+
}
249+
}
250+
},
251+
components: {
252+
messages: {
253+
Owner: {
254+
payload: {
255+
schema: {
256+
$ref: '#/components/schemas/Owner'
257+
}
258+
}
259+
}
260+
},
261+
schemas: {
262+
Owner: {
263+
type: 'object',
264+
properties: {
265+
name: {
266+
type: 'string'
267+
},
268+
pets: {
269+
type: 'array',
270+
items: {
271+
$ref: '#/components/schemas/Pet'
272+
}
273+
}
274+
}
275+
},
276+
Pet: {
277+
title: 'Pet',
278+
type: 'object',
279+
discriminator: 'type',
280+
properties: {
281+
type: {
282+
type: 'string',
283+
title: 'PetType',
284+
default: 'Birdie'
285+
}
286+
},
287+
required: ['type'],
288+
oneOf: [
289+
{
290+
$ref: '#/components/schemas/Fish'
291+
},
292+
{
293+
$ref: '#/components/schemas/Bird'
294+
},
295+
{
296+
$ref: '#/components/schemas/Dog'
297+
}
298+
]
299+
},
300+
Bird: {
301+
title: 'Bird',
302+
type: 'object',
303+
allOf: [
304+
{
305+
$ref: '#/components/schemas/Pet'
306+
}
307+
],
308+
properties: {
309+
type: {
310+
const: 'Birdie'
311+
},
312+
breed: {
313+
type: 'string'
314+
}
315+
}
316+
},
317+
Fish: {
318+
title: 'Fish',
319+
type: 'object',
320+
allOf: [
321+
{
322+
$ref: '#/components/schemas/Pet'
323+
}
324+
],
325+
properties: {
326+
type: {
327+
const: 'Fishie'
328+
}
329+
}
330+
},
331+
Dog: {
332+
title: 'Dog',
333+
type: 'object',
334+
allOf: [
335+
{
336+
$ref: '#/components/schemas/Pet'
337+
}
338+
],
339+
properties: {
340+
type: {
341+
const: 'Doggie'
342+
},
343+
breed: {
344+
const: 'Labradoodle'
345+
}
346+
}
347+
}
348+
}
349+
}
350+
};
351+
352+
const models = await generator.generate(asyncapiDoc);
353+
expect(models.map((model) => model.result)).toMatchSnapshot();
354+
});
355+
121356
test('handle oneOf with Swagger v2 discriminator with Jackson', async () => {
122357
const openapiDoc = {
123358
swagger: '2.0',

0 commit comments

Comments
 (0)