@@ -129,14 +129,53 @@ await VerifyOpenApiDocument(builder, document =>
129129 }
130130
131131 [ Fact ]
132- public async Task HandlesPolymorphicTypesWithNonAbstractBaseClass ( )
132+ public async Task HandlesPolymorphicTypesWithNonAbstractBaseClassWithNoDiscriminator ( )
133133 {
134134 // Arrange
135135 var builder = CreateBuilder ( ) ;
136136
137137 // Act
138138 builder . MapPost ( "/api" , ( Color color ) => { } ) ;
139139
140+ // Assert
141+ await VerifyOpenApiDocument ( builder , document =>
142+ {
143+ var operation = document . Paths [ "/api" ] . Operations [ OperationType . Post ] ;
144+ Assert . NotNull ( operation . RequestBody ) ;
145+ var requestBody = operation . RequestBody . Content ;
146+ Assert . True ( requestBody . TryGetValue ( "application/json" , out var mediaType ) ) ;
147+ var schema = mediaType . Schema . GetEffective ( document ) ;
148+ // Assert discriminator mappings are not configured for this type since we
149+ // can't meet OpenAPI's restrictions that derived types _always_ have a discriminator
150+ // property associated with them.
151+ Assert . Null ( schema . Discriminator ) ;
152+ Assert . Collection ( schema . AnyOf ,
153+ schema => Assert . Equal ( "ColorPaintColor" , schema . Reference . Id ) ,
154+ schema => Assert . Equal ( "ColorFabricColor" , schema . Reference . Id ) ,
155+ schema => Assert . Equal ( "ColorBase" , schema . Reference . Id ) ) ;
156+ // Assert schema with discriminator = "paint" has been inserted into the components
157+ Assert . True ( document . Components . Schemas . TryGetValue ( "ColorPaintColor" , out var paintSchema ) ) ;
158+ Assert . Contains ( "$type" , paintSchema . Properties . Keys ) ;
159+ Assert . Equal ( "paint" , ( ( OpenApiString ) paintSchema . Properties [ "$type" ] . Enum . First ( ) ) . Value ) ;
160+ // Assert schema with discriminator = "fabric" has been inserted into the components
161+ Assert . True ( document . Components . Schemas . TryGetValue ( "ColorFabricColor" , out var fabricSchema ) ) ;
162+ Assert . Contains ( "$type" , fabricSchema . Properties . Keys ) ;
163+ Assert . Equal ( "fabric" , ( ( OpenApiString ) fabricSchema . Properties [ "$type" ] . Enum . First ( ) ) . Value ) ;
164+ // Assert that schema for `Color` has been inserted into the components without a discriminator
165+ Assert . True ( document . Components . Schemas . TryGetValue ( "ColorBase" , out var colorSchema ) ) ;
166+ Assert . DoesNotContain ( "$type" , colorSchema . Properties . Keys ) ;
167+ } ) ;
168+ }
169+
170+ [ Fact ]
171+ public async Task HandlesPolymorphicTypesWithNonAbstractBaseClassAndDiscriminator ( )
172+ {
173+ // Arrange
174+ var builder = CreateBuilder ( ) ;
175+
176+ // Act
177+ builder . MapPost ( "/api" , ( Pet pet ) => { } ) ;
178+
140179 // Assert
141180 await VerifyOpenApiDocument ( builder , document =>
142181 {
@@ -148,30 +187,113 @@ await VerifyOpenApiDocument(builder, document =>
148187 // Assert discriminator mappings have been configured correctly
149188 Assert . Equal ( "$type" , schema . Discriminator . PropertyName ) ;
150189 Assert . Collection ( schema . Discriminator . Mapping ,
151- item => Assert . Equal ( "paint" , item . Key ) ,
152- item => Assert . Equal ( "fabric" , item . Key )
190+ item => Assert . Equal ( "cat" , item . Key ) ,
191+ item => Assert . Equal ( "dog" , item . Key ) ,
192+ item => Assert . Equal ( "pet" , item . Key )
153193 ) ;
154194 Assert . Collection ( schema . Discriminator . Mapping ,
155- item => Assert . Equal ( "#/components/schemas/ColorPaintColor" , item . Value ) ,
156- item => Assert . Equal ( "#/components/schemas/ColorFabricColor" , item . Value )
195+ item => Assert . Equal ( "#/components/schemas/PetCat" , item . Value ) ,
196+ item => Assert . Equal ( "#/components/schemas/PetDog" , item . Value ) ,
197+ item => Assert . Equal ( "#/components/schemas/PetPet" , item . Value )
157198 ) ;
158- // Note that our implementation diverges from the OpenAPI specification here. OpenAPI
159- // requires that derived types in a polymorphic schema _always_ have a discriminator
199+ // OpenAPI requires that derived types in a polymorphic schema _always_ have a discriminator
160200 // property associated with them. STJ permits the discriminator to be omitted from the
161201 // if the base type is a non-abstract class and falls back to serializing to this base
162- // type. This is a known limitation of the current implementation.
202+ // type. In this scenario, we check that the base class is not included in the `anyOf`
203+ // schema.
163204 Assert . Collection ( schema . AnyOf ,
164- schema => Assert . Equal ( "ColorPaintColor" , schema . Reference . Id ) ,
165- schema => Assert . Equal ( "ColorFabricColor" , schema . Reference . Id ) ,
166- schema => Assert . Equal ( "ColorColor" , schema . Reference . Id ) ) ;
167- // Assert schema with discriminator = "paint" has been inserted into the components
168- Assert . True ( document . Components . Schemas . TryGetValue ( "ColorPaintColor" , out var paintSchema ) ) ;
169- Assert . Contains ( schema . Discriminator . PropertyName , paintSchema . Properties . Keys ) ;
170- Assert . Equal ( "paint" , ( ( OpenApiString ) paintSchema . Properties [ schema . Discriminator . PropertyName ] . Enum . First ( ) ) . Value ) ;
171- // Assert schema with discriminator = "fabric" has been inserted into the components
172- Assert . True ( document . Components . Schemas . TryGetValue ( "ColorFabricColor" , out var fabricSchema ) ) ;
173- Assert . Contains ( schema . Discriminator . PropertyName , fabricSchema . Properties . Keys ) ;
174- Assert . Equal ( "fabric" , ( ( OpenApiString ) fabricSchema . Properties [ schema . Discriminator . PropertyName ] . Enum . First ( ) ) . Value ) ;
205+ schema => Assert . Equal ( "PetCat" , schema . Reference . Id ) ,
206+ schema => Assert . Equal ( "PetDog" , schema . Reference . Id ) ,
207+ schema => Assert . Equal ( "PetPet" , schema . Reference . Id ) ) ;
208+ // Assert schema with discriminator = "dog" has been inserted into the components
209+ Assert . True ( document . Components . Schemas . TryGetValue ( "PetDog" , out var dogSchema ) ) ;
210+ Assert . Contains ( schema . Discriminator . PropertyName , dogSchema . Properties . Keys ) ;
211+ Assert . Equal ( "dog" , ( ( OpenApiString ) dogSchema . Properties [ schema . Discriminator . PropertyName ] . Enum . First ( ) ) . Value ) ;
212+ // Assert schema with discriminator = "cat" has been inserted into the components
213+ Assert . True ( document . Components . Schemas . TryGetValue ( "PetCat" , out var catSchema ) ) ;
214+ Assert . Contains ( schema . Discriminator . PropertyName , catSchema . Properties . Keys ) ;
215+ Assert . Equal ( "cat" , ( ( OpenApiString ) catSchema . Properties [ schema . Discriminator . PropertyName ] . Enum . First ( ) ) . Value ) ;
216+ // Assert schema with discriminator = "cat" has been inserted into the components
217+ Assert . True ( document . Components . Schemas . TryGetValue ( "PetPet" , out var petSchema ) ) ;
218+ Assert . Contains ( schema . Discriminator . PropertyName , petSchema . Properties . Keys ) ;
219+ Assert . Equal ( "pet" , ( ( OpenApiString ) petSchema . Properties [ schema . Discriminator . PropertyName ] . Enum . First ( ) ) . Value ) ;
220+ } ) ;
221+ }
222+
223+ [ Fact ]
224+ public async Task HandlesPolymorphicTypesWithNoExplicitDiscriminators ( )
225+ {
226+ // Arrange
227+ var builder = CreateBuilder ( ) ;
228+
229+ // Act
230+ builder . MapPost ( "/api" , ( Organism color ) => { } ) ;
231+
232+ // Assert
233+ await VerifyOpenApiDocument ( builder , document =>
234+ {
235+ var operation = document . Paths [ "/api" ] . Operations [ OperationType . Post ] ;
236+ Assert . NotNull ( operation . RequestBody ) ;
237+ var requestBody = operation . RequestBody . Content ;
238+ Assert . True ( requestBody . TryGetValue ( "application/json" , out var mediaType ) ) ;
239+ var schema = mediaType . Schema . GetEffective ( document ) ;
240+ // Assert discriminator mappings are not configured for this type since we
241+ // can't meet OpenAPI's restrictions that derived types _always_ have a discriminator
242+ // property associated with them.
243+ Assert . Null ( schema . Discriminator ) ;
244+ Assert . Collection ( schema . AnyOf ,
245+ schema => Assert . Equal ( "OrganismAnimal" , schema . Reference . Id ) ,
246+ schema => Assert . Equal ( "OrganismPlant" , schema . Reference . Id ) ,
247+ schema => Assert . Equal ( "OrganismBase" , schema . Reference . Id ) ) ;
248+ // Assert that schemas without discriminators have been inserted into the components
249+ Assert . True ( document . Components . Schemas . TryGetValue ( "OrganismAnimal" , out var animalSchema ) ) ;
250+ Assert . DoesNotContain ( "$type" , animalSchema . Properties . Keys ) ;
251+ Assert . True ( document . Components . Schemas . TryGetValue ( "OrganismPlant" , out var plantSchema ) ) ;
252+ Assert . DoesNotContain ( "$type" , plantSchema . Properties . Keys ) ;
253+ Assert . True ( document . Components . Schemas . TryGetValue ( "OrganismBase" , out var baseSchema ) ) ;
254+ Assert . DoesNotContain ( "$type" , baseSchema . Properties . Keys ) ;
255+ } ) ;
256+ }
257+
258+ [ Fact ]
259+ public async Task HandlesPolymorphicTypesWithSelfReference ( )
260+ {
261+ // Arrange
262+ var builder = CreateBuilder ( ) ;
263+
264+ // Act
265+ builder . MapPost ( "/api" , ( Employee color ) => { } ) ;
266+
267+ // Assert
268+ await VerifyOpenApiDocument ( builder , document =>
269+ {
270+ var operation = document . Paths [ "/api" ] . Operations [ OperationType . Post ] ;
271+ Assert . NotNull ( operation . RequestBody ) ;
272+ var requestBody = operation . RequestBody . Content ;
273+ Assert . True ( requestBody . TryGetValue ( "application/json" , out var mediaType ) ) ;
274+ Assert . Equal ( "Employee" , mediaType . Schema . Reference . Id ) ;
275+ var schema = mediaType . Schema . GetEffective ( document ) ;
276+ // Assert that discriminator mappings are configured correctly for type.
277+ Assert . Equal ( "$type" , schema . Discriminator . PropertyName ) ;
278+ Assert . Collection ( schema . Discriminator . Mapping ,
279+ item => Assert . Equal ( "manager" , item . Key ) ,
280+ item => Assert . Equal ( "employee" , item . Key )
281+ ) ;
282+ Assert . Collection ( schema . Discriminator . Mapping ,
283+ item => Assert . Equal ( "#/components/schemas/EmployeeManager" , item . Value ) ,
284+ item => Assert . Equal ( "#/components/schemas/EmployeeEmployee" , item . Value )
285+ ) ;
286+ // Assert that anyOf schemas use the correct reference IDs.
287+ Assert . Collection ( schema . AnyOf ,
288+ schema => Assert . Equal ( "EmployeeManager" , schema . Reference . Id ) ,
289+ schema => Assert . Equal ( "EmployeeEmployee" , schema . Reference . Id ) ) ;
290+ // Assert that schemas without discriminators have been inserted into the components
291+ Assert . True ( document . Components . Schemas . TryGetValue ( "EmployeeManager" , out var managerSchema ) ) ;
292+ Assert . Equal ( "manager" , ( ( OpenApiString ) managerSchema . Properties [ schema . Discriminator . PropertyName ] . Enum . First ( ) ) . Value ) ;
293+ Assert . True ( document . Components . Schemas . TryGetValue ( "EmployeeEmployee" , out var employeeSchema ) ) ;
294+ Assert . Equal ( "employee" , ( ( OpenApiString ) employeeSchema . Properties [ schema . Discriminator . PropertyName ] . Enum . First ( ) ) . Value ) ;
295+ // Assert that the schema has a correct self-reference to the base-type. This points to the schema that contains the discriminator.
296+ Assert . Equal ( "Employee" , employeeSchema . Properties [ "manager" ] . Reference . Id ) ;
175297 } ) ;
176298 }
177299}
0 commit comments