Skip to content

Commit 9515087

Browse files
authored
Avoid empty list of required properties in rendered schema (#245)
* Remove required field by setting a new list via API to ensure that the correct order is retained and the `required` field is set to null if the list is empty branch: * Fix some typos and wordings in the readme branch:
1 parent 434ecb8 commit 9515087

File tree

3 files changed

+38
-37
lines changed

3 files changed

+38
-37
lines changed

Diff for: README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This is a fork of https://github.com/swagger-api/swagger-scala-module.
99
| ------- | -------- |
1010
| 2.9.x | Refactor to support Swagger Schema annotation requiredMode. Jackson 2.14. |
1111
| 2.8.x | Builds on the 2.7.x changes. Jackson 2.14. |
12-
| 2.7.x | Scala 2 builds reintroduce scala-reflect dependency and can now introspect better on inner types. See section on `Treatment of Option` below. This has turned into a series with many experimental changes. It is probably best to upgrade to 2.8.x releases. |
12+
| 2.7.x | Scala 2 builds reintroduce scala-reflect dependency and can now introspect better on inner types. See section on `Treatment of Option` below. This has turned into a series with many experimental changes. It is probably best to upgrade to 2.8.x or later releases. |
1313
| 2.6.x/2.5.x | First releases to support Scala 3. Jackson 2.13, [jakarta](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Getting-started) namespace jars. [OpenAPI 3.0.1](https://github.com/OAI/OpenAPI-Specification) / [Swagger-Core](https://github.com/swagger-api/swagger-core) 2.0.x. |
1414
| 2.4.x | First releases to support [jakarta](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Getting-started) namespace jars. Jackson 2.12, [OpenAPI 3.0.1](https://github.com/OAI/OpenAPI-Specification) / [Swagger-Core](https://github.com/swagger-api/swagger-core) 2.0.x. |
1515
| 2.3.x | [OpenAPI 3.0.1](https://github.com/OAI/OpenAPI-Specification) / [Swagger-Core](https://github.com/swagger-api/swagger-core) 2.0.x. |
@@ -31,7 +31,7 @@ When users add swagger annotations (Schema, ArraySchema, Parameter), they can ov
3131

3232
The annotations mentioned above have a `required()` setting which is boolean and defaults to false. It is impossible to know if the user explicitly set false or if the are using the annotations to override other settings but don't intend to affect the required setting.
3333

34-
swagger v2.2.5 introduces a `requiredMode()` setting on the Schema and ArraySchema annotations. The modes are Required, Not_Required and Auto - the latter is the default. This is better - but the `required()` setting, while deprecated, is still there and is set independently of the `requiredMode()`.
34+
Swagger v2.2.5 introduces a `requiredMode()` setting on the Schema and ArraySchema annotations. The modes are Required, Not_Required and Auto - the latter is the default. This is better - but the `required()` setting, while deprecated, is still there and is set independently of the `requiredMode()`.
3535

3636
The treatment of `required()=false` prior to v2.9 is described in the below. [SwaggerScalaModelConverter.setRequiredBasedOnAnnotation](https://github.com/swagger-akka-http/swagger-scala-module/blob/bf97024492d07d7a293f72e4f113e9f378465bc2/src/main/scala/com/github/swagger/scala/converter/SwaggerScalaModelConverter.scala#L44) and [SwaggerScalaModelConverter.setRequiredBasedOnDefaultValue](https://github.com/swagger-akka-http/swagger-scala-module/blob/bf97024492d07d7a293f72e4f113e9f378465bc2/src/main/scala/com/github/swagger/scala/converter/SwaggerScalaModelConverter.scala#L58) will continue to affect how `required()=false` is interpreted.
3737

@@ -53,7 +53,7 @@ With Collections (and Options), scala primitives are affected by type erasure. Y
5353
case class AddOptionRequest(number: Int, @Schema(required = false, implementation = classOf[Int]) number2: Option[Int] = None)
5454
```
5555

56-
Alternatively, you can non-primitive types like BigInt to avoid this requirement.
56+
Alternatively, you can use non-primitive types like BigInt to avoid this requirement.
5757

5858
Since the v2.7 releases, Scala 2 builds use scala-reflect jar to try to work out the class information for the inner types. Since v2.7.5, Scala 3 builds use a lib that supports runtime reflection. See https://github.com/swagger-akka-http/swagger-scala-module/issues/117. One issue affacting Scala 3 users is https://github.com/gzoller/scala-reflection/issues/40.
5959

Diff for: src/main/scala/com/github/swagger/scala/converter/SwaggerScalaModelConverter.scala

+4-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ import org.slf4j.LoggerFactory
1717
import java.lang.annotation.Annotation
1818
import java.lang.reflect.ParameterizedType
1919
import java.util
20+
import java.util.List
2021
import scala.collection.JavaConverters._
21-
import scala.collection.Seq
2222
import scala.util.Try
2323
import scala.util.control.NonFatal
2424

@@ -279,7 +279,9 @@ class SwaggerScalaModelConverter extends ModelResolver(SwaggerScalaModelConverte
279279
case Seq() => {
280280
val requiredFlag = !isOptional && (!SwaggerScalaModelConverter.isRequiredBasedOnDefaultValue || !hasDefaultValue)
281281
if (!requiredFlag && Option(schema.getRequired).isDefined && schema.getRequired.contains(propertyName)) {
282-
schema.getRequired.remove(propertyName)
282+
val requiredFields = new util.ArrayList[String](schema.getRequired)
283+
requiredFields.remove(propertyName)
284+
schema.setRequired(requiredFields)
283285
} else if (requiredFlag && schema.getEnum == null) {
284286
addRequiredItem(schema, propertyName)
285287
}

Diff for: src/test/scala/com/github/swagger/scala/converter/ModelPropertyParserTest.scala

+31-32
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import org.scalatest.matchers.should.Matchers
1111

1212
import java.util
1313
import scala.collection.JavaConverters._
14-
import scala.collection.Seq
1514
import scala.reflect.ClassTag
1615

1716
class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with Matchers with OptionValues {
@@ -57,18 +56,18 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
5756
val stringOpt = model.value.getProperties().get("stringOpt")
5857
stringOpt should not be (null)
5958
stringOpt.isInstanceOf[StringSchema] should be(true)
60-
nullSafeSeq(stringOpt.getRequired) shouldBe empty
59+
stringOpt.getRequired shouldBe null
6160
val stringWithDataType = model.value.getProperties().get("stringWithDataTypeOpt")
6261
stringWithDataType should not be (null)
6362
stringWithDataType shouldBe a[StringSchema]
64-
nullSafeSeq(stringWithDataType.getRequired) shouldBe empty
63+
stringWithDataType.getRequired shouldBe null
6564

6665
val ipAddress = model.value.getProperties().get("ipAddress")
6766
ipAddress should not be (null)
6867
ipAddress shouldBe a[StringSchema]
6968
ipAddress.getDescription shouldBe "An IP address"
7069
ipAddress.getFormat shouldBe "IPv4 or IPv6"
71-
nullSafeSeq(ipAddress.getRequired) shouldBe empty
70+
ipAddress.getRequired shouldBe null
7271
}
7372

7473
it should "process Option[Model] as Model" in new PropertiesScope[ModelWOptionModel] {
@@ -105,22 +104,22 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
105104
val optBigDecimal = model.value.getProperties().get("optBigDecimal")
106105
optBigDecimal should not be (null)
107106
optBigDecimal shouldBe a[NumberSchema]
108-
nullSafeSeq(model.value.getRequired) shouldBe empty
107+
model.value.getRequired shouldBe null
109108
}
110109

111110
it should "process Model with Scala Option BigInt" in new PropertiesScope[ModelWOptionBigInt] {
112111
val optBigInt = model.value.getProperties().get("optBigInt")
113112
optBigInt should not be (null)
114113
optBigInt shouldBe a[IntegerSchema]
115-
nullSafeSeq(model.value.getRequired) shouldBe empty
114+
model.value.getRequired shouldBe null
116115
}
117116

118117
it should "process Model with Scala Option Int" in new PropertiesScope[ModelWOptionInt] {
119118
val optInt = model.value.getProperties().get("optInt")
120119
optInt should not be (null)
121120
optInt shouldBe a[IntegerSchema]
122121
optInt.asInstanceOf[IntegerSchema].getFormat shouldEqual "int32"
123-
nullSafeSeq(model.value.getRequired) shouldBe empty
122+
model.value.getRequired shouldBe null
124123
}
125124

126125
it should "process Model with nested Scala Option Int" in new PropertiesScope[NestedModelWOptionInt] {
@@ -132,7 +131,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
132131
optInt shouldBe a[IntegerSchema]
133132
optInt.asInstanceOf[IntegerSchema].getFormat shouldEqual "int32"
134133
}
135-
nullSafeSeq(model.value.getRequired) shouldBe empty
134+
model.value.getRequired shouldBe null
136135
}
137136

138137
it should "process Model without any properties" in new TestScope {
@@ -150,7 +149,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
150149
optInt shouldBe a[IntegerSchema]
151150
optInt.asInstanceOf[IntegerSchema].getFormat shouldEqual "int32"
152151
optInt.getDescription shouldBe "This is an optional int"
153-
nullSafeSeq(model.value.getRequired) shouldBe empty
152+
model.value.getRequired shouldBe null
154153
}
155154

156155
it should "process Model with Scala Option Int with Schema Override" in new PropertiesScope[ModelWOptionIntSchemaOverride] {
@@ -159,7 +158,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
159158
optInt shouldBe a[IntegerSchema]
160159
optInt.asInstanceOf[IntegerSchema].getFormat shouldEqual "int32"
161160
optInt.getDescription shouldBe "This is an optional int"
162-
nullSafeSeq(model.value.getRequired) shouldBe empty
161+
model.value.getRequired shouldBe null
163162
}
164163

165164
it should "prioritize required as specified in annotation by default" in new PropertiesScope[ModelWOptionIntSchemaOverrideForRequired](
@@ -297,45 +296,45 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
297296
optLong shouldBe a[IntegerSchema]
298297
optLong.asInstanceOf[IntegerSchema].getFormat shouldEqual "int64"
299298
optLong.getDefault shouldEqual Long.MaxValue
300-
nullSafeSeq(model.value.getRequired) shouldBe empty
299+
model.value.getRequired shouldBe null
301300
}
302301

303302
it should "process Model with Scala Option Long" in new PropertiesScope[ModelWOptionLong] {
304303
val optLong = model.value.getProperties().get("optLong")
305304
optLong should not be (null)
306305
optLong shouldBe a[IntegerSchema]
307306
optLong.asInstanceOf[IntegerSchema].getFormat shouldEqual "int64"
308-
nullSafeSeq(model.value.getRequired) shouldBe empty
307+
model.value.getRequired shouldBe null
309308
}
310309

311310
it should "process Model with Scala Option Long with Schema Override" in new PropertiesScope[ModelWOptionLongSchemaOverride] {
312311
val optLong = model.value.getProperties().get("optLong")
313312
optLong should not be (null)
314313
optLong shouldBe a[IntegerSchema]
315314
optLong.asInstanceOf[IntegerSchema].getFormat shouldEqual "int64"
316-
nullSafeSeq(model.value.getRequired) shouldBe empty
315+
model.value.getRequired shouldBe null
317316
}
318317

319318
it should "process Model with Scala Option Long with Schema Int Override" in new PropertiesScope[ModelWOptionLongSchemaIntOverride] {
320319
val optLong = model.value.getProperties().get("optLong")
321320
optLong should not be (null)
322321
optLong shouldBe a[IntegerSchema]
323322
optLong.asInstanceOf[IntegerSchema].getFormat shouldEqual "int32"
324-
nullSafeSeq(model.value.getRequired) shouldBe empty
323+
model.value.getRequired shouldBe null
325324
}
326325

327326
it should "process Model with Scala Option Boolean" in new PropertiesScope[ModelWOptionBoolean] {
328327
val optBoolean = model.value.getProperties().get("optBoolean")
329328
optBoolean should not be (null)
330329
optBoolean shouldBe a[Schema[_]]
331-
nullSafeSeq(model.value.getRequired) shouldBe empty
330+
model.value.getRequired shouldBe null
332331
}
333332

334333
it should "process Model with Scala Option Boolean with Schema Override" in new PropertiesScope[ModelWOptionBooleanSchemaOverride] {
335334
val optBoolean = model.value.getProperties().get("optBoolean")
336335
optBoolean should not be (null)
337336
optBoolean shouldBe a[BooleanSchema]
338-
nullSafeSeq(model.value.getRequired) shouldBe empty
337+
model.value.getRequired shouldBe null
339338
}
340339

341340
it should "process all properties as required barring Option[_] or if overridden in annotation" in new TestScope {
@@ -505,7 +504,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
505504
stringSchema.getExample shouldEqual ("42.0")
506505
stringSchema.getDescription shouldBe "required of annotation should be honoured"
507506

508-
nullSafeSeq(model.value.getRequired) shouldBe empty
507+
model.value.getRequired shouldBe null
509508
}
510509

511510
it should "process Model with Scala BigInt with annotation" in new PropertiesScope[ModelWBigIntAnnotated] {
@@ -540,7 +539,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
540539
it should "default to supplied schema if it can't be corrected" in new PropertiesScope[ModelWMapStringCaseClass] {
541540
schemas should have size 2
542541

543-
nullSafeSeq(model.value.getRequired) shouldBe empty
542+
model.value.getRequired shouldBe null
544543
val mapField = model.value.getProperties.get("maybeMapStringCaseClass")
545544
mapField shouldBe a[MapSchema]
546545
mapField.getAdditionalProperties shouldBe a[Schema[_]]
@@ -554,7 +553,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
554553
it should "handle Option[Map[String, Long]]" in new PropertiesScope[ModelWMapStringLong] {
555554
schemas should have size 1
556555

557-
nullSafeSeq(model.value.getRequired) shouldBe empty
556+
model.value.getRequired shouldBe null
558557
val mapField = model.value.getProperties.get("maybeMapStringLong")
559558
mapField shouldBe a[MapSchema]
560559
nullSafeMap(mapField.getProperties) shouldBe empty
@@ -569,7 +568,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
569568
arraySchema.getUniqueItems() shouldBe (null)
570569
arraySchema.getItems shouldBe a[StringSchema]
571570
nullSafeMap(arraySchema.getProperties()) shouldBe empty
572-
nullSafeSeq(arraySchema.getRequired()) shouldBe empty
571+
arraySchema.getRequired() shouldBe null
573572
}
574573

575574
it should "process Model with Scala Seq Int" in new PropertiesScope[ModelWSeqInt] {
@@ -595,7 +594,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
595594
arraySchema.getUniqueItems() shouldBe (null)
596595
arraySchema.getItems shouldBe a[IntegerSchema]
597596
nullSafeMap(arraySchema.getProperties()) shouldBe empty
598-
nullSafeSeq(arraySchema.getRequired()) shouldBe empty
597+
arraySchema.getRequired() shouldBe null
599598
}
600599

601600
it should "process Model with Scala Seq Int (annotated)" in new PropertiesScope[ModelWSeqIntAnnotated] {
@@ -608,7 +607,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
608607
arraySchema.getItems shouldBe a[IntegerSchema]
609608
arraySchema.getItems.getDescription shouldBe "These are ints"
610609
nullSafeMap(arraySchema.getProperties()) shouldBe empty
611-
nullSafeSeq(arraySchema.getRequired()) shouldBe empty
610+
arraySchema.getRequired() shouldBe null
612611
}
613612

614613
it should "process Model with Scala Seq Int (annotated - old style)" in new PropertiesScope[ModelWSeqIntAnnotatedOldStyle] {
@@ -621,7 +620,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
621620
arraySchema.getItems shouldBe a[IntegerSchema]
622621
arraySchema.getItems.getDescription shouldBe "These are ints"
623622
nullSafeMap(arraySchema.getProperties()) shouldBe empty
624-
nullSafeSeq(arraySchema.getRequired()) shouldBe empty
623+
arraySchema.getRequired() shouldBe null
625624
}
626625

627626
it should "process Model with Scala Set" in new PropertiesScope[ModelWSetString] {
@@ -631,7 +630,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
631630
arraySchema.getUniqueItems() shouldBe true
632631
arraySchema.getItems shouldBe a[StringSchema]
633632
nullSafeMap(arraySchema.getProperties()) shouldBe empty
634-
nullSafeSeq(arraySchema.getRequired()) shouldBe empty
633+
arraySchema.getRequired() shouldBe null
635634
}
636635

637636
it should "process Model with Java List" in new PropertiesScope[ModelWJavaListString] {
@@ -641,7 +640,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
641640
arraySchema.getUniqueItems() shouldBe (null)
642641
arraySchema.getItems shouldBe a[StringSchema]
643642
nullSafeMap(arraySchema.getProperties()) shouldBe empty
644-
nullSafeSeq(arraySchema.getRequired()) shouldBe empty
643+
arraySchema.getRequired() shouldBe null
645644
}
646645

647646
it should "process Model with Scala Map" in new PropertiesScope[ModelWMapString] {
@@ -650,7 +649,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
650649
val mapSchema = stringsField.asInstanceOf[MapSchema]
651650
mapSchema.getUniqueItems() shouldBe (null)
652651
nullSafeMap(mapSchema.getProperties()) shouldBe empty
653-
nullSafeSeq(mapSchema.getRequired()) shouldBe empty
652+
mapSchema.getRequired() shouldBe null
654653
nullSafeSet(mapSchema.getTypes) shouldEqual Set("object")
655654
}
656655

@@ -660,7 +659,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
660659
val mapSchema = stringsField.asInstanceOf[MapSchema]
661660
mapSchema.getUniqueItems() shouldBe (null)
662661
nullSafeMap(mapSchema.getProperties()) shouldBe empty
663-
nullSafeSeq(mapSchema.getRequired()) shouldBe empty
662+
mapSchema.getRequired() shouldBe null
664663
nullSafeSet(mapSchema.getTypes) shouldEqual Set("object")
665664
}
666665

@@ -670,7 +669,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
670669
val mapSchema = mapField.asInstanceOf[MapSchema]
671670
mapSchema.getUniqueItems() shouldBe (null)
672671
nullSafeMap(mapSchema.getProperties()) shouldBe empty
673-
nullSafeSeq(mapSchema.getRequired()) shouldBe empty
672+
mapSchema.getRequired() shouldBe null
674673
nullSafeSet(mapSchema.getTypes) shouldEqual Set("object")
675674
}
676675

@@ -680,7 +679,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
680679
val mapSchema = mapField.asInstanceOf[MapSchema]
681680
mapSchema.getUniqueItems() shouldBe (null)
682681
nullSafeMap(mapSchema.getProperties()) shouldBe empty
683-
nullSafeSeq(mapSchema.getRequired()) shouldBe empty
682+
mapSchema.getRequired() shouldBe null
684683
nullSafeSet(mapSchema.getTypes) shouldEqual Set("object")
685684
}
686685

@@ -709,7 +708,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
709708
amountField shouldBe a[IntegerSchema]
710709
amountField.asInstanceOf[IntegerSchema].getFormat shouldEqual "int64"
711710

712-
nullSafeSeq(model.value.getRequired) shouldBe empty
711+
model.value.getRequired shouldBe null
713712
}
714713

715714
it should "process ModelWJacksonAnnotatedGetFunction" in new PropertiesScope[ModelWJacksonAnnotatedGetFunction] {
@@ -723,7 +722,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
723722
}
724723

725724
it should "process Array-Model with Scala nonOption Seq (annotated)" in new PropertiesScope[ModelWStringSeqAnnotated] {
726-
nullSafeSeq(model.value.getRequired) shouldBe empty
725+
model.value.getRequired shouldBe null
727726
}
728727

729728
it should "process Array-Model with forced required Scala Option Seq (annotated)" in new PropertiesScope[ModelWOptionStringSeqAnnotated] {
@@ -739,7 +738,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
739738
}
740739

741740
it should "process Array-Model with forced required Scala Option Seq" in new PropertiesScope[ModelWOptionStringSeq] {
742-
nullSafeSeq(model.value.getRequired) shouldBe empty
741+
model.value.getRequired shouldBe null
743742
}
744743

745744
it should "process case class with Duration field" in new PropertiesScope[ModelWDuration] {

0 commit comments

Comments
 (0)