Skip to content

Commit bf97024

Browse files
authored
support new requiredMode attribute (#225)
1 parent 3f218db commit bf97024

File tree

4 files changed

+132
-29
lines changed

4 files changed

+132
-29
lines changed

src/main/scala/com/github/swagger/scala/converter/SwaggerScalaModelConverter.scala

Lines changed: 50 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import io.swagger.v3.core.converter._
99
import io.swagger.v3.core.jackson.ModelResolver
1010
import io.swagger.v3.core.util.{Json, PrimitiveType}
1111
import io.swagger.v3.oas.annotations.Parameter
12+
import io.swagger.v3.oas.annotations.media.Schema.RequiredMode
1213
import io.swagger.v3.oas.annotations.media.{ArraySchema, Schema => SchemaAnnotation}
1314
import io.swagger.v3.oas.models.media.{MapSchema, ObjectSchema, Schema}
1415
import org.slf4j.LoggerFactory
@@ -130,10 +131,7 @@ class SwaggerScalaModelConverter extends ModelResolver(SwaggerScalaModelConverte
130131
val annotatedOverrides = getRequiredSettings(`type`)
131132
if (_isOptional(`type`, cls)) {
132133
val baseType =
133-
if (
134-
SwaggerScalaModelConverter.isRequiredBasedOnAnnotation
135-
&& annotatedOverrides.headOption.getOrElse(false)
136-
) new AnnotatedType()
134+
if (annotatedOverrides.headOption.getOrElse(false)) new AnnotatedType()
137135
else new AnnotatedTypeForOption()
138136
resolve(nextType(baseType, `type`, javaType), context, chain)
139137
} else if (!annotatedOverrides.headOption.getOrElse(true)) {
@@ -236,12 +234,8 @@ class SwaggerScalaModelConverter extends ModelResolver(SwaggerScalaModelConverte
236234
}
237235
}
238236
case annotations => {
239-
val annotationRequired = getRequiredSettings(annotations).headOption.getOrElse(false)
240-
if (SwaggerScalaModelConverter.isRequiredBasedOnAnnotation) {
241-
setRequiredBasedOnAnnotation(schema, propertyName, annotationRequired)
242-
} else {
243-
setRequiredBasedOnType(schema, propertyName, isOptional, hasDefaultValue, annotationRequired)
244-
}
237+
val annotationRequired = getRequiredSettings(annotations).headOption
238+
setRequiredBasedOnType(schema, propertyName, isOptional, hasDefaultValue, annotationRequired)
245239
}
246240
}
247241

@@ -272,26 +266,25 @@ class SwaggerScalaModelConverter extends ModelResolver(SwaggerScalaModelConverte
272266
}
273267
}
274268

275-
private def setRequiredBasedOnAnnotation(
276-
schema: Schema[_],
277-
propertyName: String,
278-
annotationSetting: Boolean
279-
): Unit = {
280-
if (annotationSetting) addRequiredItem(schema, propertyName)
281-
}
282-
283269
private def setRequiredBasedOnType(
284270
schema: Schema[_],
285271
propertyName: String,
286272
isOptional: Boolean,
287273
hasDefaultValue: Boolean,
288-
annotationSetting: Boolean
274+
annotationSetting: Option[Boolean]
289275
): Unit = {
290-
val required = if (isOptional) {
291-
annotationSetting
292-
} else if (SwaggerScalaModelConverter.isRequiredBasedOnDefaultValue) {
293-
!hasDefaultValue
294-
} else true
276+
val required = annotationSetting match {
277+
case Some(req) => req
278+
case _ => {
279+
if (isOptional) {
280+
false
281+
} else if (SwaggerScalaModelConverter.isRequiredBasedOnDefaultValue) {
282+
!hasDefaultValue
283+
} else {
284+
true
285+
}
286+
}
287+
}
295288
if (required) addRequiredItem(schema, propertyName)
296289
}
297290

@@ -326,10 +319,39 @@ class SwaggerScalaModelConverter extends ModelResolver(SwaggerScalaModelConverte
326319
}
327320

328321
private def getRequiredSettings(annotations: Seq[Annotation]): Seq[Boolean] = {
329-
annotations.collect {
330-
case p: Parameter => p.required()
331-
case s: SchemaAnnotation => s.required()
332-
case a: ArraySchema => a.arraySchema().required()
322+
val flags = annotations.collect {
323+
case p: Parameter => if (p.required()) RequiredMode.REQUIRED else RequiredMode.NOT_REQUIRED
324+
case s: SchemaAnnotation => {
325+
if (s.requiredMode() == RequiredMode.AUTO) {
326+
if (s.required()) {
327+
RequiredMode.REQUIRED
328+
} else if (SwaggerScalaModelConverter.isRequiredBasedOnAnnotation) {
329+
RequiredMode.NOT_REQUIRED
330+
} else {
331+
RequiredMode.AUTO
332+
}
333+
} else {
334+
s.requiredMode()
335+
}
336+
}
337+
case a: ArraySchema => {
338+
if (a.arraySchema().requiredMode() == RequiredMode.AUTO) {
339+
if (a.arraySchema().required()) {
340+
RequiredMode.REQUIRED
341+
} else if (SwaggerScalaModelConverter.isRequiredBasedOnAnnotation) {
342+
RequiredMode.NOT_REQUIRED
343+
} else {
344+
RequiredMode.AUTO
345+
}
346+
} else {
347+
a.arraySchema().requiredMode()
348+
}
349+
}
350+
}
351+
flags.flatMap {
352+
case RequiredMode.REQUIRED => Some(true)
353+
case RequiredMode.NOT_REQUIRED => Some(false)
354+
case _ => None
333355
}
334356
}
335357

src/test/scala/com/github/swagger/scala/converter/ModelPropertyParserTest.scala

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,59 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
390390
requiredItems shouldBe List("forcedRequired", "required")
391391
}
392392

393+
it should "process all properties as required barring Option[_] or if overridden in annotation (Schema annotation - old style)" in new TestScope {
394+
val schemas = ModelConverters
395+
.getInstance()
396+
.readAll(classOf[ModelWithOptionAndNonOption3])
397+
.asScala
398+
399+
val model = schemas("ModelWithOptionAndNonOption3")
400+
model should not be (null)
401+
model.getProperties() should not be (null)
402+
403+
val optional = model.getProperties().get("optional")
404+
optional should not be (null)
405+
406+
val required = model.getProperties().get("required")
407+
required should not be (null)
408+
409+
val forcedRequired = model.getProperties().get("forcedRequired")
410+
forcedRequired should not be (null)
411+
412+
val forcedOptional = model.getProperties().get("forcedOptional")
413+
forcedOptional should not be (null)
414+
415+
val requiredItems = nullSafeSeq(model.getRequired)
416+
requiredItems shouldBe List("forcedRequired", "required")
417+
}
418+
419+
it should "ignore required()=false on Schema annotations when setRequiredBasedOnAnnotation(false)" in new TestScope {
420+
SwaggerScalaModelConverter.setRequiredBasedOnAnnotation(false)
421+
val schemas = ModelConverters
422+
.getInstance()
423+
.readAll(classOf[ModelWithOptionAndNonOption3])
424+
.asScala
425+
426+
val model = schemas("ModelWithOptionAndNonOption3")
427+
model should not be (null)
428+
model.getProperties() should not be (null)
429+
430+
val optional = model.getProperties().get("optional")
431+
optional should not be (null)
432+
433+
val required = model.getProperties().get("required")
434+
required should not be (null)
435+
436+
val forcedRequired = model.getProperties().get("forcedRequired")
437+
forcedRequired should not be (null)
438+
439+
val forcedOptional = model.getProperties().get("forcedOptional")
440+
forcedOptional should not be (null)
441+
442+
val requiredItems = nullSafeSeq(model.getRequired)
443+
requiredItems shouldBe List("forcedOptional", "forcedRequired", "required")
444+
}
445+
393446
it should "handle null properties from converters later in the chain" in new TestScope {
394447
object CustomConverter extends ModelConverter {
395448
override def resolve(`type`: AnnotatedType, context: ModelConverterContext, chain: util.Iterator[ModelConverter]): Schema[_] = {
@@ -558,6 +611,19 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
558611
nullSafeSeq(arraySchema.getRequired()) shouldBe empty
559612
}
560613

614+
it should "process Model with Scala Seq Int (annotated - old style)" in new PropertiesScope[ModelWSeqIntAnnotatedOldStyle] {
615+
val stringsField = model.value.getProperties.get("ints")
616+
617+
stringsField shouldBe a[ArraySchema]
618+
val arraySchema = stringsField.asInstanceOf[ArraySchema]
619+
arraySchema.getUniqueItems() shouldBe (null)
620+
621+
arraySchema.getItems shouldBe a[IntegerSchema]
622+
arraySchema.getItems.getDescription shouldBe "These are ints"
623+
nullSafeMap(arraySchema.getProperties()) shouldBe empty
624+
nullSafeSeq(arraySchema.getRequired()) shouldBe empty
625+
}
626+
561627
it should "process Model with Scala Set" in new PropertiesScope[ModelWSetString] {
562628
val stringsField = model.value.getProperties.get("strings")
563629
stringsField shouldBe a[ArraySchema]

src/test/scala/models/ModelWOptionString.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package models
22

33
import io.swagger.v3.oas.annotations.Parameter
44
import io.swagger.v3.oas.annotations.media.Schema
5+
import io.swagger.v3.oas.annotations.media.Schema.RequiredMode
56

67
case class ModelWOptionString(
78
stringOpt: Option[String],
@@ -20,6 +21,13 @@ case class ModelWithOptionAndNonOption(
2021
)
2122

2223
case class ModelWithOptionAndNonOption2(
24+
required: String,
25+
optional: Option[String],
26+
@Schema(requiredMode = RequiredMode.NOT_REQUIRED, implementation = classOf[String]) forcedOptional: String,
27+
@Schema(requiredMode = RequiredMode.REQUIRED, implementation = classOf[String]) forcedRequired: Option[String]
28+
)
29+
30+
case class ModelWithOptionAndNonOption3(
2331
required: String,
2432
optional: Option[String],
2533
@Schema(required = false, implementation = classOf[String]) forcedOptional: String,
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
11
package models
22

3+
import io.swagger.v3.oas.annotations.media.Schema.RequiredMode
34
import io.swagger.v3.oas.annotations.media.{ArraySchema, Schema}
45

56
case class ModelWSeqInt(ints: Seq[Int])
67

78
case class ModelWSeqIntDefaulted(ints: Seq[Int] = Seq.empty)
89

10+
case class ModelWSeqIntAnnotatedOldStyle(
11+
@ArraySchema(arraySchema = new Schema(required = false),
12+
schema = new Schema(description = "These are ints")) ints: Seq[Int]
13+
)
14+
915
case class ModelWSeqIntAnnotated(
10-
@ArraySchema(arraySchema = new Schema(required = false), schema = new Schema(description = "These are ints")) ints: Seq[Int]
16+
@ArraySchema(arraySchema = new Schema(requiredMode = RequiredMode.NOT_REQUIRED),
17+
schema = new Schema(description = "These are ints")) ints: Seq[Int]
1118
)

0 commit comments

Comments
 (0)