Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -1369,12 +1369,11 @@ object RestActionBuilderV3 {
}

/**
*
* handled constraints include
* - allOf
* - anyOf
* - oneOf
* - not (OpenAPI not support this yet)
* - not
*/
private fun assembleObjectGeneWithConstraints(
name: String,
Expand All @@ -1394,37 +1393,61 @@ object RestActionBuilderV3 {
https://spec.openapis.org/oas/latest.html#discriminator-object
*/

if (!options.enableConstraintHandling)
if (!options.enableConstraintHandling){
return assembleObjectGene(name, options, schema, fields, additionalFieldTemplate, referenceTypeName, examples, messages)
}

/*
THIS IS VERY TRICKY
in theory, the use of allOf/anyOf/oneOf/not provides a rich language where many different kinds of
constraints can be specified.
this is the case when "const" is used in schemas to specify particular values, e.g., oneOf could be then
used to specify only particular combinations of field values.
the use of "const" is also quite tricky with "not", as can use to say some specific value combinations are not valid.
furthermore, those keywords are NOT mutually exclusive.

support all possible constraint would be a GIGANTIC piece of work... yet, these kinds of constraints do
not seem so common, apart for allOf on merging fields.
TODO we should have full support, but not high priority task
*/

/*
A further complication is how to use, and interpret, "examples" values (if any).
Eg. what to nested referenced $ref objects that have on examples?
should those be merged/hanled like the fields?
TODO possibly something to handle (but again, not high priority)
*/

val allOf = schema.allOf?.map { s->
//createObjectGene(name, s, swagger, history, null, enableConstraintHandling)
getGene(name, s, schemaHolder,currentSchema, history, null, options, messages = messages, examples = examples)
}

val anyOf = schema.anyOf?.map { s->
//createObjectGene(name, s, swagger, history, null, enableConstraintHandling)
getGene(name, s, schemaHolder,currentSchema, history, null, options, messages = messages, examples = examples)
}

if (!allOf.isNullOrEmpty() && !anyOf.isNullOrEmpty()){
messages.add("Cannot handle allOf and oneOf at same time for a schema with name $name")
return assembleObjectGene(name, options, schema, fields, additionalFieldTemplate, referenceTypeName, examples, messages)
messages.add("Currently cannot handle allOf and oneOf at same time for a schema with name $name")
}

val oneOf = schema.oneOf?.map { s->
//createObjectGene(name, s, swagger, history, null, enableConstraintHandling)
getGene(name, s, schemaHolder,currentSchema, history, null, options = options, messages = messages)
}

if (!oneOf.isNullOrEmpty() && (!allOf.isNullOrEmpty() || !anyOf.isNullOrEmpty())){
messages.add("cannot handle oneOf and allOf/oneOf at same time for a schema with name $name")
return assembleObjectGene(name, options, schema, fields, additionalFieldTemplate, referenceTypeName, examples, messages)
messages.add("Currently cannot handle oneOf and allOf/oneOf at same time for a schema with name $name")
}

if (!allOf.isNullOrEmpty()){

if(allOf.size == 1 && fields.isEmpty()){
//just use it as it is
return allOf[0]
}

val allFields = allOf.mapNotNull {
when (it) {
is ChoiceGene<*> -> extractOriginalObject(it)?.fields
is ObjectGene -> it.fields
else -> null
}
Expand Down Expand Up @@ -1495,6 +1518,19 @@ object RestActionBuilderV3 {
//TODO not
}

private fun extractOriginalObject(choice: ChoiceGene<*>): ObjectGene? {
/*
this only makes sense if we are in this case:
Choice( Choice(examples), Object)
in which we would return Object, null otherwise
*/
val children = choice.getViewOfChildren()
if(children.size != 2 || children.none { it is UserExamplesGene && it.isUsedForExamples() }){
return null
}
return children.find { it is ObjectGene } as ObjectGene?
}

/**
* assemble ObjectGene based on [fields] and [additionalFieldTemplate]
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2289,4 +2289,62 @@ class RestActionBuilderV3Test{
assertTrue(withFooAgain.contains("xfoo"), withFooAgain)
assertTrue(withFooAgain.contains("ybar"), withFooAgain)
}


@Test
fun testSingleAllOf() {

val path = "/swagger/artificial/defaultandexamples/constraints/single_allof.yaml"

val a = loadAndAssertActions(path, 1, RestActionBuilderV3.Options(probUseExamples = 1.0))
.values.first()

val rand = Randomness()
a.doInitialize(rand)

val found = mutableSetOf<String>()

for (i in 0..100) {
a.randomize(rand, false)
val s = a.seeTopGenes().first().getValueAsRawString()
found.add(s)
if(found.size == 2){
break
}
}

//both examples must be there
assertEquals(2, found.size)
assertTrue(found.any{it.contains("Foo")})
assertTrue(found.any{it.contains("Bar")})
}


@Test
fun testMultiAllOf() {

val path = "/swagger/artificial/defaultandexamples/constraints/multi_allof.yaml"

val a = loadAndAssertActions(path, 1, RestActionBuilderV3.Options(probUseExamples = 1.0))
.values.first()

val rand = Randomness()
a.doInitialize(rand)

val found = mutableSetOf<String>()

for (i in 0..100) {
a.randomize(rand, false)
val s = a.seeTopGenes().first().getValueAsRawString()
found.add(s)
}

/*
Only 1 example should be there, the one declared at top level.
TODO: maybe in future we merge examples as well... in that case, then
would need to update this test
*/
assertTrue(found.any{it.contains("Foo")})
assertEquals(1, found.size)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
openapi: 3.1.0
info:
title: a schema
version: 1.0.0
servers:
- url: "/v2"
paths:
"/api":
post:
requestBody:
required: true
content:
application/json:
schema:
allOf:
- type: object
additionalProperties: false
properties:
id:
type: integer
name:
type: string
examples:
- id : 42
name: Bar
- $ref: '#/components/schemas/Dto'
examples:
Foo:
value:
id: 123
name: Foo
extra: some extra
responses:
'200':
description: OK
components:
schemas:
Dto:
type: object
additionalProperties: false
properties:
extra:
type: string
example:
extra: Hello
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
openapi: 3.1.0
info:
title: a schema
version: 1.0.0
servers:
- url: "/v2"
paths:
"/api":
post:
requestBody:
required: true
content:
application/json:
schema:
allOf:
- type: object
additionalProperties: false
properties:
id:
type: integer
name:
type: string
examples:
- id : 42
name: Bar
examples:
Foo:
value:
id: 123
name: Foo
responses:
'200':
description: OK
Loading