Skip to content

Commit 0a1ff7c

Browse files
authored
feat: required based on default value (#205)
Add setting to control required flag based on default value
1 parent 2bf4c28 commit 0a1ff7c

File tree

3 files changed

+88
-8
lines changed

3 files changed

+88
-8
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ Alternatively, you can non-primitive types like BigInt to avoid this requirement
3636
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.
3737

3838
v2.7 takes default values into account - either those specified in Scala contructors or via swagger annotations. A field might be marked as not required if a default value is specified.
39+
If you don't use swagger annotations, and would like not like to infer the `required` value based on the default value, then you can set `SwaggerScalaModelConverter.setRequiredBasedOnDefaultValue` to `false`
3940

4041
If you use swagger annotations and don't want to explicity set the `required` value and allow this lib to infer the value, then you can set [SwaggerScalaModelConverter.setRequiredBasedOnAnnotation](https://github.com/swagger-akka-http/swagger-scala-module/blob/564c7c7fb879c1b93b7c913af2219dc4b550ad95/src/main/scala/com/github/swagger/scala/converter/SwaggerScalaModelConverter.scala#L39).
4142

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

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ object SwaggerScalaModelConverter {
2727
val objectMapper: ObjectMapper = Json.mapper().registerModule(DefaultScalaModule)
2828

2929
private var requiredBasedOnAnnotation = true
30+
private var requiredBasedOnDefaultValue = true
3031

3132
/** If you use swagger annotations to override what is automatically derived, then be aware that
3233
* [[io.swagger.v3.oas.annotations.media.Schema]] annotation has required = false, by default. You are advised to set the required
3334
* flag on this annotation to the correct value. If you would prefer to have the Schema annotation required flag ignored and to rely on
34-
* the this module inferring the value (as ot would if you don't annotate the classes or fields), then set
35+
* the this module inferring the value (as it would if you don't annotate the classes or fields), then set
3536
* [[SwaggerScalaModelConverter.setRequiredBasedOnAnnotation]] to true and the required property on the annotation will be ignored,
3637
* unless the field is an [[Option]].
3738
*
@@ -42,17 +43,41 @@ object SwaggerScalaModelConverter {
4243
requiredBasedOnAnnotation = value
4344
}
4445

46+
/** If you use swagger annotations to override what is automatically derived, then this flag will not be used.
47+
* If you rely on this module inferring the required flag (as it would if you don't annotate the classes or fields),
48+
* then this flag will control how the required flag is derived when a default value exists.
49+
* If [[SwaggerScalaModelConverter.setRequiredBasedOnDefaultValue]] is true and a property has a default value, then it will not be required.
50+
* However, if this flag is false, then a property will be required only if it's not an [[Option]].
51+
*
52+
* @param value
53+
* true by default
54+
*/
55+
def setRequiredBasedOnDefaultValue(value: Boolean = true): Unit = {
56+
requiredBasedOnDefaultValue = value
57+
}
58+
4559
/** If you use swagger annotations to override what is automatically derived, then be aware that
4660
* [[io.swagger.v3.oas.annotations.media.Schema]] annotation has required = false, by default. You are advised to set the required
4761
* flag on this annotation to the correct value. If you would prefer to have the Schema annotation required flag ignored and to rely on
48-
* the this module inferring the value (as ot would if you don't annotate the classes or fields), then set
62+
* the this module inferring the value (as it would if you don't annotate the classes or fields), then set
4963
* [[SwaggerScalaModelConverter.setRequiredBasedOnAnnotation]] to true and the required property on the annotation will be ignored,
5064
* unless the field is an [[Option]].
5165
*
5266
* @return
5367
* value: true by default
5468
*/
5569
def isRequiredBasedOnAnnotation: Boolean = requiredBasedOnAnnotation
70+
71+
/** If you use swagger annotations to override what is automatically derived, then this flag will not be used.
72+
* If you rely on this module inferring the required flag (as it would if you don't annotate the classes or fields),
73+
* then this flag will control how the required flag is derived when a default value exists.
74+
* If [[SwaggerScalaModelConverter.setRequiredBasedOnDefaultValue]] is true and a property has a default value, then it will not be required.
75+
* However, if this flag is false, then a property will be required only if it's not an [[Option]].
76+
*
77+
* @return
78+
* value: true by default
79+
*/
80+
def isRequiredBasedOnDefaultValue: Boolean = requiredBasedOnDefaultValue
5681
}
5782

5883
class SwaggerScalaModelConverter extends ModelResolver(SwaggerScalaModelConverter.objectMapper) {
@@ -191,7 +216,7 @@ class SwaggerScalaModelConverter extends ModelResolver(SwaggerScalaModelConverte
191216
}
192217
propertyAnnotations match {
193218
case Seq() => {
194-
val requiredFlag = !isOptional && !hasDefaultValue
219+
val requiredFlag = !isOptional && (!SwaggerScalaModelConverter.isRequiredBasedOnDefaultValue || !hasDefaultValue)
195220
if (!requiredFlag && Option(schema.getRequired).isDefined && schema.getRequired.contains(propertyName)) {
196221
schema.getRequired.remove(propertyName)
197222
} else if (requiredFlag) {
@@ -252,9 +277,9 @@ class SwaggerScalaModelConverter extends ModelResolver(SwaggerScalaModelConverte
252277
): Unit = {
253278
val required = if (isOptional) {
254279
annotationSetting
255-
} else {
280+
} else if (SwaggerScalaModelConverter.isRequiredBasedOnDefaultValue) {
256281
!hasDefaultValue
257-
}
282+
} else true
258283
if (required) addRequiredItem(schema, propertyName)
259284
}
260285

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

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
2727
val converter = ModelConverters.getInstance()
2828
}
2929

30-
class PropertiesScope[A](requiredBasedAnnotation: Boolean = true, debug: Boolean = false)(implicit tt: ClassTag[A]) extends TestScope {
30+
class PropertiesScope[A](requiredBasedAnnotation: Boolean = true, requiredBasedDefaultValue: Boolean = true, debug: Boolean = false)(implicit tt: ClassTag[A]) extends TestScope {
3131
SwaggerScalaModelConverter.setRequiredBasedOnAnnotation(requiredBasedAnnotation)
32+
SwaggerScalaModelConverter.setRequiredBasedOnDefaultValue(requiredBasedDefaultValue)
3233
val schemas = converter.readAll(tt.runtimeClass).asScala.toMap
3334
val model = schemas.get(tt.runtimeClass.getSimpleName)
3435
model should be(defined)
@@ -158,7 +159,7 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
158159
}
159160

160161
it should "prioritize required as specified in annotation by default" in new PropertiesScope[ModelWOptionIntSchemaOverrideForRequired](
161-
true
162+
true, true
162163
) {
163164
val requiredIntWithDefault = model.value.getProperties.get("requiredIntWithDefault")
164165
requiredIntWithDefault shouldBe an[IntegerSchema]
@@ -183,9 +184,35 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
183184
nullSafeSeq(model.value.getRequired).toSet shouldEqual Set("annotatedOptionalInt", "requiredInt")
184185
}
185186

187+
it should "prioritize required as specified in annotation and not based on default value" in new PropertiesScope[ModelWOptionIntSchemaOverrideForRequired](
188+
true, false
189+
) {
190+
val requiredIntWithDefault = model.value.getProperties.get("requiredIntWithDefault")
191+
requiredIntWithDefault shouldBe an[IntegerSchema]
192+
requiredIntWithDefault.asInstanceOf[IntegerSchema].getDefault shouldEqual 5
193+
194+
val annotatedIntWithDefault = model.value.getProperties.get("annotatedIntWithDefault")
195+
annotatedIntWithDefault shouldBe an[IntegerSchema]
196+
annotatedIntWithDefault.asInstanceOf[IntegerSchema].getDefault shouldEqual 10
197+
198+
val annotatedOptionalIntWithNoneDefault = model.value.getProperties.get("annotatedOptionalIntWithNoneDefault")
199+
annotatedOptionalIntWithNoneDefault shouldBe an[IntegerSchema]
200+
annotatedOptionalIntWithNoneDefault.asInstanceOf[IntegerSchema].getDefault should be(null)
201+
202+
val annotatedOptionalIntWithSomeDefault = model.value.getProperties.get("annotatedOptionalIntWithSomeDefault")
203+
annotatedOptionalIntWithSomeDefault shouldBe an[IntegerSchema]
204+
annotatedOptionalIntWithSomeDefault.asInstanceOf[IntegerSchema].getDefault should be(5)
205+
206+
val annotatedOptionalStringWithNoneDefault = model.value.getProperties.get("annotatedOptionalStringWithNoneDefault")
207+
annotatedOptionalStringWithNoneDefault shouldBe an[StringSchema]
208+
annotatedOptionalStringWithNoneDefault.asInstanceOf[StringSchema].getDefault should be(null)
209+
210+
nullSafeSeq(model.value.getRequired).toSet shouldEqual Set("annotatedOptionalInt", "requiredInt", "requiredIntWithDefault")
211+
}
212+
186213
it should "prioritize required based on (Option or not) type when `setRequiredBasedOnAnnotation` is set" in new PropertiesScope[
187214
ModelWOptionIntSchemaOverrideForRequired
188-
](requiredBasedAnnotation = false) {
215+
](requiredBasedAnnotation = false, requiredBasedDefaultValue = true) {
189216

190217
val requiredIntWithDefault = model.value.getProperties.get("requiredIntWithDefault")
191218
requiredIntWithDefault shouldBe an[IntegerSchema]
@@ -210,6 +237,33 @@ class ModelPropertyParserTest extends AnyFlatSpec with BeforeAndAfterEach with M
210237
nullSafeSeq(model.value.getRequired).toSet shouldEqual Set("annotatedOptionalInt", "requiredInt", "annotatedRequiredInt")
211238
}
212239

240+
it should "prioritize required based on (Option or not) type when `setRequiredBasedOnAnnotation` and `requiredBasedDefaultValue` is set" in new PropertiesScope[
241+
ModelWOptionIntSchemaOverrideForRequired
242+
](requiredBasedAnnotation = false, requiredBasedDefaultValue = false) {
243+
244+
val requiredIntWithDefault = model.value.getProperties.get("requiredIntWithDefault")
245+
requiredIntWithDefault shouldBe an[IntegerSchema]
246+
requiredIntWithDefault.asInstanceOf[IntegerSchema].getDefault shouldEqual 5
247+
248+
val annotatedIntWithDefault = model.value.getProperties.get("annotatedIntWithDefault")
249+
annotatedIntWithDefault shouldBe an[IntegerSchema]
250+
annotatedIntWithDefault.asInstanceOf[IntegerSchema].getDefault shouldEqual 10
251+
252+
val annotatedOptionalIntWithNoneDefault = model.value.getProperties.get("annotatedOptionalIntWithNoneDefault")
253+
annotatedOptionalIntWithNoneDefault shouldBe an[IntegerSchema]
254+
annotatedOptionalIntWithNoneDefault.asInstanceOf[IntegerSchema].getDefault should be(null)
255+
256+
val annotatedOptionalIntWithSomeDefault = model.value.getProperties.get("annotatedOptionalIntWithSomeDefault")
257+
annotatedOptionalIntWithSomeDefault shouldBe an[IntegerSchema]
258+
annotatedOptionalIntWithSomeDefault.asInstanceOf[IntegerSchema].getDefault should be(5)
259+
260+
val annotatedOptionalStringWithNoneDefault = model.value.getProperties.get("annotatedOptionalStringWithNoneDefault")
261+
annotatedOptionalStringWithNoneDefault shouldBe an[StringSchema]
262+
annotatedOptionalStringWithNoneDefault.asInstanceOf[StringSchema].getDefault should be(null)
263+
264+
nullSafeSeq(model.value.getRequired).toSet shouldEqual Set("requiredInt", "requiredIntWithDefault", "annotatedRequiredInt", "annotatedRequiredIntWithDefault", "annotatedIntWithDefault", "annotatedOptionalInt")
265+
}
266+
213267
it should "consider fields that aren't optional required if `requiredBasedAnnotation == true`" in new PropertiesScope[
214268
ModelWMultipleRequiredFields
215269
](

0 commit comments

Comments
 (0)