From 72aeebee6d3468ed59980fc8f3ec59b5485c15c0 Mon Sep 17 00:00:00 2001 From: sijarsu Date: Mon, 7 Feb 2022 16:55:01 +0100 Subject: [PATCH 01/14] Regression test for Request Body with annotated defaults --- .../expected_default_request_body.yml | 34 +++++++++++++++++++ .../tapir/docs/openapi/VerifyYamlTest.scala | 15 +++++++- .../openapi/dtos/VerifyYamlTestData.scala | 2 ++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 docs/openapi-docs/src/test/resources/expected_default_request_body.yml diff --git a/docs/openapi-docs/src/test/resources/expected_default_request_body.yml b/docs/openapi-docs/src/test/resources/expected_default_request_body.yml new file mode 100644 index 0000000000..d8ab788c30 --- /dev/null +++ b/docs/openapi-docs/src/test/resources/expected_default_request_body.yml @@ -0,0 +1,34 @@ +openapi: 3.0.3 +info: + title: Entities + version: '1.0' +paths: + /: + post: + operationId: postRoot + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ObjectWithDefaults' + required: true + responses: + '200': + description: '' + '400': + description: 'Invalid value for: body' + content: + text/plain: + schema: + type: string +components: + schemas: + ObjectWithDefaults: + type: object + properties: + name: + type: string + default: foo + count: + type: integer + default: 12 \ No newline at end of file diff --git a/docs/openapi-docs/src/test/scala/sttp/tapir/docs/openapi/VerifyYamlTest.scala b/docs/openapi-docs/src/test/scala/sttp/tapir/docs/openapi/VerifyYamlTest.scala index b80728a73f..a7b090e451 100644 --- a/docs/openapi-docs/src/test/scala/sttp/tapir/docs/openapi/VerifyYamlTest.scala +++ b/docs/openapi-docs/src/test/scala/sttp/tapir/docs/openapi/VerifyYamlTest.scala @@ -468,7 +468,7 @@ class VerifyYamlTest extends AnyFunSuite with Matchers { actualYamlNoIndent shouldBe expectedYaml } - test("use default for a query parameter") { + test("should use default for a query parameter") { val expectedYaml = load("expected_default_query_param.yml") val actualYaml = OpenAPIDocsInterpreter() .toOpenAPI( @@ -481,6 +481,19 @@ class VerifyYamlTest extends AnyFunSuite with Matchers { actualYamlNoIndent shouldBe expectedYaml } + // #1800 + test("should use default for a request body") { + val expectedYaml = load("expected_default_request_body.yml") + val actualYaml = OpenAPIDocsInterpreter() + .toOpenAPI( + endpoint.post.in(jsonBody[ObjectWithDefaults]), + Info("Entities", "1.0") + ).toYaml + + val actualYamlNoIndent = noIndentation(actualYaml) + actualYamlNoIndent shouldBe expectedYaml + } + test("should match the expected yaml using double quoted style") { val ep = endpoint.get.description("first line:\nsecond line") diff --git a/docs/openapi-docs/src/test/scala/sttp/tapir/docs/openapi/dtos/VerifyYamlTestData.scala b/docs/openapi-docs/src/test/scala/sttp/tapir/docs/openapi/dtos/VerifyYamlTestData.scala index 8962f35438..7cb306e3f4 100644 --- a/docs/openapi-docs/src/test/scala/sttp/tapir/docs/openapi/dtos/VerifyYamlTestData.scala +++ b/docs/openapi-docs/src/test/scala/sttp/tapir/docs/openapi/dtos/VerifyYamlTestData.scala @@ -1,5 +1,6 @@ package sttp.tapir.docs.openapi.dtos +import sttp.tapir.Schema.annotations.default import sttp.tapir.tests.data.FruitAmount // TODO: move back to VerifyYamlTest companion after https://github.com/lampepfl/dotty/issues/12849 is fixed @@ -8,4 +9,5 @@ object VerifyYamlTestData { case class ObjectWrapper(value: FruitAmount) case class ObjectWithList(data: List[FruitAmount]) case class ObjectWithOption(data: Option[FruitAmount]) + case class ObjectWithDefaults(@default("foo") name: String, @default(12) count: Int) } From 6c5dacf1ec2feafd5494ed4a045b831999d87c86 Mon Sep 17 00:00:00 2001 From: sijarsu Date: Mon, 7 Feb 2022 18:01:00 +0100 Subject: [PATCH 02/14] Test for query parameter as enum with defaults --- .../enum/expected_enumeratum_enum_default.yml | 50 +++++++++++++++++++ .../openapi/VerifyYamlEnumeratumTest.scala | 20 +++++++- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default.yml diff --git a/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default.yml b/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default.yml new file mode 100644 index 0000000000..5872f2653d --- /dev/null +++ b/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default.yml @@ -0,0 +1,50 @@ +openapi: 3.0.3 +info: + title: Fruits + version: '1.0' +paths: + /: + get: + operationId: getRoot + parameters: + - name: type + in: query + required: false + schema: + $ref: '#/components/schemas/FruitType' + example: APPLE + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/FruitWithEnum' + '400': + description: 'Invalid value for: query parameter type' + content: + text/plain: + schema: + type: string +components: + schemas: + FruitType: + type: string + default: PEAR + enum: + - APPLE + - PEAR + FruitWithEnum: + required: + - fruit + - amount + type: object + properties: + fruit: + type: string + amount: + type: integer + fruitType: + type: array + items: + $ref: '#/components/schemas/FruitType' diff --git a/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala b/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala index c7bbb21155..78026ce1e7 100644 --- a/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala +++ b/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala @@ -31,7 +31,25 @@ class VerifyYamlEnumeratumTest extends AnyFunSuite with Matchers { val expectedYaml = load("validator/expected_valid_enumeratum_with_metadata.yml") val actualYaml = - OpenAPIDocsInterpreter().toOpenAPI(endpoint.in("numbers").in(jsonBody[Enumeratum.NumberWithMsg]), Info("Numbers", "1.0")).toYaml + OpenAPIDocsInterpreter() + .toOpenAPI(endpoint.in("numbers").in(jsonBody[Enumeratum.NumberWithMsg]), Info("Numbers", "1.0")) + .toYaml + + noIndentation(actualYaml) shouldBe expectedYaml + } + + // #1800 + test("add enum default") { + import sttp.tapir.codec.enumeratum._ + import sttp.tapir.docs.openapi.VerifyYamlEnumeratumTest.Enumeratum.FruitType._ + + val expectedYaml = load("enum/expected_enumeratum_enum_default.yml") + val ep = endpoint + .in(query[Enumeratum.FruitType]("type").example(APPLE).default(PEAR)) + .out(jsonBody[Enumeratum.FruitWithEnum]) + + val actualYaml = + OpenAPIDocsInterpreter().toOpenAPI(ep, Info("Fruits", "1.0")).toYaml noIndentation(actualYaml) shouldBe expectedYaml } From 3ffe98457fab5ecca32d11b0ae75f18d2b70b4d2 Mon Sep 17 00:00:00 2001 From: sijarsu Date: Mon, 7 Feb 2022 18:37:24 +0100 Subject: [PATCH 03/14] Test for query parameter as enum with other default ignored --- ...d_enumeratum_enum_ignore_other_default.yml | 71 +++++++++++++++++++ .../openapi/VerifyYamlEnumeratumTest.scala | 19 +++++ 2 files changed, 90 insertions(+) create mode 100644 docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_ignore_other_default.yml diff --git a/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_ignore_other_default.yml b/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_ignore_other_default.yml new file mode 100644 index 0000000000..83134f9dca --- /dev/null +++ b/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_ignore_other_default.yml @@ -0,0 +1,71 @@ +openapi: 3.0.3 +info: + title: Fruits + version: '1.0' +paths: + /fruit-by-type1: + get: + operationId: getFruit-by-type1 + parameters: + - name: type1 + in: query + required: false + schema: + $ref: '#/components/schemas/FruitType' + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/FruitWithEnum' + '400': + description: 'Invalid value for: query parameter type1' + content: + text/plain: + schema: + type: string + /fruit-by-type2: + get: + operationId: getFruit-by-type2 + parameters: + - name: type2 + in: query + required: false + schema: + $ref: '#/components/schemas/FruitType' + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/FruitWithEnum' + '400': + description: 'Invalid value for: query parameter type2' + content: + text/plain: + schema: + type: string +components: + schemas: + FruitType: + type: string + default: PEAR + enum: + - APPLE + - PEAR + FruitWithEnum: + required: + - fruit + - amount + type: object + properties: + fruit: + type: string + amount: + type: integer + fruitType: + type: array + items: + $ref: '#/components/schemas/FruitType' diff --git a/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala b/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala index 78026ce1e7..53b9a18593 100644 --- a/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala +++ b/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala @@ -53,6 +53,25 @@ class VerifyYamlEnumeratumTest extends AnyFunSuite with Matchers { noIndentation(actualYaml) shouldBe expectedYaml } + + // #1800 + test("ignore enum other default") { + import sttp.tapir.codec.enumeratum._ + import sttp.tapir.docs.openapi.VerifyYamlEnumeratumTest.Enumeratum.FruitType._ + + val expectedYaml = load("enum/expected_enumeratum_enum_ignore_other_default.yml") + val ep1 = endpoint + .in("fruit-by-type1").in(query[Enumeratum.FruitType]("type1").default(PEAR)) + .out(jsonBody[Enumeratum.FruitWithEnum]) + val ep2 = endpoint + .in("fruit-by-type2").in(query[Enumeratum.FruitType]("type2").default(APPLE)) + .out(jsonBody[Enumeratum.FruitWithEnum]) + + val actualYaml = + OpenAPIDocsInterpreter().toOpenAPI(List(ep1, ep2), Info("Fruits", "1.0")).toYaml + + noIndentation(actualYaml) shouldBe expectedYaml + } } object VerifyYamlEnumeratumTest { From 198ca6bceb369cb77b6a759b6d4cb5e57cc498d9 Mon Sep 17 00:00:00 2001 From: sijarsu Date: Mon, 7 Feb 2022 19:34:44 +0100 Subject: [PATCH 04/14] Test for Request Body with enum and annotated default --- ...numeratum_enum_default_in_request_body.yml | 54 +++++++++++++++++++ .../openapi/VerifyYamlEnumeratumTest.scala | 20 ++++++- 2 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_in_request_body.yml diff --git a/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_in_request_body.yml b/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_in_request_body.yml new file mode 100644 index 0000000000..d54220aa32 --- /dev/null +++ b/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_in_request_body.yml @@ -0,0 +1,54 @@ +openapi: 3.0.3 +info: + title: Fruits + version: '1.0' +paths: + /: + post: + operationId: postRoot + requestBody: + content: + application/json: + schema: + $ref:'#/components/schemas/FruitQuery' + required: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/FruitWithEnum' + '400': + description: 'Invalid value for: body' + content: + text/plain: + schema: + type: string +components: + schemas: + FruitQuery: + type: object + properties: + fruitType: + $ref: '#/components/schemas/FruitType' + FruitType: + type: string + default: PEAR + enum: + - APPLE + - PEAR + FruitWithEnum: + required: + - fruit + - amount + type: object + properties: + fruit: + type: string + amount: + type: integer + fruitType: + type: array + items: + $ref: '#/components/schemas/FruitType' diff --git a/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala b/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala index 53b9a18593..55a09c0be3 100644 --- a/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala +++ b/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala @@ -3,7 +3,7 @@ package sttp.tapir.docs.openapi import io.circe.generic.auto._ import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers -import sttp.tapir.Schema.annotations.description +import sttp.tapir.Schema.annotations.{default, description} import sttp.tapir._ import sttp.tapir.docs.openapi.VerifyYamlEnumeratumTest.Enumeratum import sttp.tapir.generic.auto._ @@ -72,6 +72,22 @@ class VerifyYamlEnumeratumTest extends AnyFunSuite with Matchers { noIndentation(actualYaml) shouldBe expectedYaml } + + // #1800 + test("add enum default in request body") { + import sttp.tapir.codec.enumeratum._ + import sttp.tapir.docs.openapi.VerifyYamlEnumeratumTest.Enumeratum.FruitType._ + + val expectedYaml = load("enum/expected_enumeratum_enum_default_in_request_body.yml") + val ep = endpoint + .post.in(jsonBody[Enumeratum.FruitQuery]) + .out(jsonBody[Enumeratum.FruitWithEnum]) + + val actualYaml = + OpenAPIDocsInterpreter().toOpenAPI(ep, Info("Fruits", "1.0")).toYaml + + noIndentation(actualYaml) shouldBe expectedYaml + } } object VerifyYamlEnumeratumTest { @@ -89,6 +105,8 @@ object VerifyYamlEnumeratumTest { override def values: scala.collection.immutable.IndexedSeq[FruitType] = findValues } + case class FruitQuery(@default(FruitType.PEAR) fruitType: FruitType) + @description("* 1 - One\n* 2 - Two\n* 3 - Three") sealed abstract class MyNumber(val value: Int) extends IntEnumEntry From 84638e5921ffd8576b122fe411a99af5751895d5 Mon Sep 17 00:00:00 2001 From: sijarsu Date: Tue, 8 Feb 2022 17:26:24 +0100 Subject: [PATCH 05/14] Work-around with explicit encoded in default --- .../internal/SchemaMagnoliaDerivation.scala | 2 +- core/src/main/scala/sttp/tapir/Schema.scala | 2 +- ...ult_in_request_body_with_given_encoded.yml | 54 +++++++++++++++++++ .../openapi/VerifyYamlEnumeratumTest.scala | 17 +++++- 4 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_in_request_body_with_given_encoded.yml diff --git a/core/src/main/scala-2/sttp/tapir/generic/internal/SchemaMagnoliaDerivation.scala b/core/src/main/scala-2/sttp/tapir/generic/internal/SchemaMagnoliaDerivation.scala index 1c46e5c15e..dc88cdf1a1 100644 --- a/core/src/main/scala-2/sttp/tapir/generic/internal/SchemaMagnoliaDerivation.scala +++ b/core/src/main/scala-2/sttp/tapir/generic/internal/SchemaMagnoliaDerivation.scala @@ -57,7 +57,7 @@ trait SchemaMagnoliaDerivation { annotations.foldLeft(schema) { case (schema, ann: Schema.annotations.description) => schema.description(ann.text) case (schema, ann: Schema.annotations.encodedExample) => schema.encodedExample(ann.example) - case (schema, ann: Schema.annotations.default[X]) => schema.default(ann.default) + case (schema, ann: Schema.annotations.default[X]) => schema.default(ann.default, ann.encoded) case (schema, ann: Schema.annotations.validate[X]) => schema.validate(ann.v) case (schema, ann: Schema.annotations.format) => schema.format(ann.format) case (schema, _: Schema.annotations.deprecated) => schema.deprecated(true) diff --git a/core/src/main/scala/sttp/tapir/Schema.scala b/core/src/main/scala/sttp/tapir/Schema.scala index 76d5e73aa5..24c16a62da 100644 --- a/core/src/main/scala/sttp/tapir/Schema.scala +++ b/core/src/main/scala/sttp/tapir/Schema.scala @@ -280,7 +280,7 @@ object Schema extends LowPrioritySchema with SchemaCompanionMacros { object annotations { class description(val text: String) extends StaticAnnotation class encodedExample(val example: Any) extends StaticAnnotation - class default[T](val default: T) extends StaticAnnotation + class default[T](val default: T, val encoded: Option[Any] = None) extends StaticAnnotation class format(val format: String) extends StaticAnnotation class deprecated extends StaticAnnotation class encodedName(val name: String) extends StaticAnnotation diff --git a/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_in_request_body_with_given_encoded.yml b/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_in_request_body_with_given_encoded.yml new file mode 100644 index 0000000000..bdce20c450 --- /dev/null +++ b/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_in_request_body_with_given_encoded.yml @@ -0,0 +1,54 @@ +openapi: 3.0.3 +info: + title: Fruits + version: '1.0' +paths: + /: + post: + operationId: postRoot + requestBody: + content: + application/json: + schema: + $ref:'#/components/schemas/FruitQueryWithEncoded' + required: true + responses: + '200': + description: '' + content: + application/json: + schema: + $ref: '#/components/schemas/FruitWithEnum' + '400': + description: 'Invalid value for: body' + content: + text/plain: + schema: + type: string +components: + schemas: + FruitQueryWithEncoded: + type: object + properties: + fruitType: + $ref: '#/components/schemas/FruitType' + FruitType: + type: string + default: PEAR + enum: + - APPLE + - PEAR + FruitWithEnum: + required: + - fruit + - amount + type: object + properties: + fruit: + type: string + amount: + type: integer + fruitType: + type: array + items: + $ref: '#/components/schemas/FruitType' diff --git a/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala b/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala index 55a09c0be3..d63d033f37 100644 --- a/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala +++ b/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala @@ -76,7 +76,6 @@ class VerifyYamlEnumeratumTest extends AnyFunSuite with Matchers { // #1800 test("add enum default in request body") { import sttp.tapir.codec.enumeratum._ - import sttp.tapir.docs.openapi.VerifyYamlEnumeratumTest.Enumeratum.FruitType._ val expectedYaml = load("enum/expected_enumeratum_enum_default_in_request_body.yml") val ep = endpoint @@ -88,6 +87,21 @@ class VerifyYamlEnumeratumTest extends AnyFunSuite with Matchers { noIndentation(actualYaml) shouldBe expectedYaml } + + // #1800 + test("add enum default in request body with given encoded") { + import sttp.tapir.codec.enumeratum._ + + val expectedYaml = load("enum/expected_enumeratum_enum_default_in_request_body_with_given_encoded.yml") + val ep = endpoint + .post.in(jsonBody[Enumeratum.FruitQueryWithEncoded]) + .out(jsonBody[Enumeratum.FruitWithEnum]) + + val actualYaml = + OpenAPIDocsInterpreter().toOpenAPI(ep, Info("Fruits", "1.0")).toYaml + + noIndentation(actualYaml) shouldBe expectedYaml + } } object VerifyYamlEnumeratumTest { @@ -106,6 +120,7 @@ object VerifyYamlEnumeratumTest { } case class FruitQuery(@default(FruitType.PEAR) fruitType: FruitType) + case class FruitQueryWithEncoded(@default(FruitType.PEAR, encoded=Some(FruitType.PEAR)) fruitType: FruitType) @description("* 1 - One\n* 2 - Two\n* 3 - Three") sealed abstract class MyNumber(val value: Int) extends IntEnumEntry From 25f08081da4c7d98407c47ca123538087a075478 Mon Sep 17 00:00:00 2001 From: sijarsu Date: Tue, 8 Feb 2022 17:46:23 +0100 Subject: [PATCH 06/14] Cleanup - test name --- ...t.yml => expected_enumeratum_enum_default_one_in_many.yml} | 0 .../sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename docs/openapi-docs/src/test/resources/enum/{expected_enumeratum_enum_ignore_other_default.yml => expected_enumeratum_enum_default_one_in_many.yml} (100%) diff --git a/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_ignore_other_default.yml b/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_one_in_many.yml similarity index 100% rename from docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_ignore_other_default.yml rename to docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_one_in_many.yml diff --git a/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala b/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala index d63d033f37..9fea66cd38 100644 --- a/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala +++ b/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala @@ -55,11 +55,11 @@ class VerifyYamlEnumeratumTest extends AnyFunSuite with Matchers { } // #1800 - test("ignore enum other default") { + test("use enum default one in many") { import sttp.tapir.codec.enumeratum._ import sttp.tapir.docs.openapi.VerifyYamlEnumeratumTest.Enumeratum.FruitType._ - val expectedYaml = load("enum/expected_enumeratum_enum_ignore_other_default.yml") + val expectedYaml = load("enum/expected_enumeratum_enum_default_one_in_many.yml") val ep1 = endpoint .in("fruit-by-type1").in(query[Enumeratum.FruitType]("type1").default(PEAR)) .out(jsonBody[Enumeratum.FruitWithEnum]) From 0097f54412b024438cd0e863ab6b08316f650419 Mon Sep 17 00:00:00 2001 From: sijarsu Date: Thu, 10 Feb 2022 16:20:40 +0100 Subject: [PATCH 07/14] Cleanup - test name --- .../sttp/tapir/internal/SchemaMagnoliaDerivation.scala | 2 +- ... => expected_enumeratum_enum_default_if_more_than_one.yml} | 0 .../sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename docs/openapi-docs/src/test/resources/enum/{expected_enumeratum_enum_default_one_in_many.yml => expected_enumeratum_enum_default_if_more_than_one.yml} (100%) diff --git a/core/src/main/scala-3/sttp/tapir/internal/SchemaMagnoliaDerivation.scala b/core/src/main/scala-3/sttp/tapir/internal/SchemaMagnoliaDerivation.scala index ba11d79087..3bb5fcf452 100644 --- a/core/src/main/scala-3/sttp/tapir/internal/SchemaMagnoliaDerivation.scala +++ b/core/src/main/scala-3/sttp/tapir/internal/SchemaMagnoliaDerivation.scala @@ -60,7 +60,7 @@ trait SchemaMagnoliaDerivation { annotations.foldLeft(schema) { case (schema, ann: Schema.annotations.description) => schema.description(ann.text) case (schema, ann: Schema.annotations.encodedExample) => schema.encodedExample(ann.example) - case (schema, ann: Schema.annotations.default[X @unchecked]) => schema.default(ann.default) + case (schema, ann: Schema.annotations.default[X @unchecked]) => schema.default(ann.default, ann.encoded) case (schema, ann: Schema.annotations.validate[X @unchecked]) => schema.validate(ann.v) case (schema, ann: Schema.annotations.format) => schema.format(ann.format) case (schema, _: Schema.annotations.deprecated) => schema.deprecated(true) diff --git a/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_one_in_many.yml b/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_if_more_than_one.yml similarity index 100% rename from docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_one_in_many.yml rename to docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_if_more_than_one.yml diff --git a/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala b/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala index 9fea66cd38..7208c7f51d 100644 --- a/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala +++ b/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala @@ -55,11 +55,11 @@ class VerifyYamlEnumeratumTest extends AnyFunSuite with Matchers { } // #1800 - test("use enum default one in many") { + test("use enum default if more than one") { import sttp.tapir.codec.enumeratum._ import sttp.tapir.docs.openapi.VerifyYamlEnumeratumTest.Enumeratum.FruitType._ - val expectedYaml = load("enum/expected_enumeratum_enum_default_one_in_many.yml") + val expectedYaml = load("enum/expected_enumeratum_enum_default_if_more_than_one.yml") val ep1 = endpoint .in("fruit-by-type1").in(query[Enumeratum.FruitType]("type1").default(PEAR)) .out(jsonBody[Enumeratum.FruitWithEnum]) From 6eae808417ee92bf7bb7314e55cee03e772ab42a Mon Sep 17 00:00:00 2001 From: sijarsu Date: Thu, 10 Feb 2022 16:25:56 +0100 Subject: [PATCH 08/14] Cleanup - test name for ignored case --- .../enum/expected_enumeratum_enum_default_in_request_body.yml | 1 - .../sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_in_request_body.yml b/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_in_request_body.yml index d54220aa32..44df9d39ed 100644 --- a/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_in_request_body.yml +++ b/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_in_request_body.yml @@ -34,7 +34,6 @@ components: $ref: '#/components/schemas/FruitType' FruitType: type: string - default: PEAR enum: - APPLE - PEAR diff --git a/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala b/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala index 7208c7f51d..2c22aeac18 100644 --- a/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala +++ b/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala @@ -74,7 +74,7 @@ class VerifyYamlEnumeratumTest extends AnyFunSuite with Matchers { } // #1800 - test("add enum default in request body") { + test("ignore enum default in request body") { import sttp.tapir.codec.enumeratum._ val expectedYaml = load("enum/expected_enumeratum_enum_default_in_request_body.yml") From 980a3f2614c5653822c18edd7fc8aa07303747bd Mon Sep 17 00:00:00 2001 From: sijarsu Date: Fri, 11 Feb 2022 17:24:23 +0100 Subject: [PATCH 09/14] Draft macro for explicit encoded in default --- .../generic/internal/EndpointAnnotationsMacro.scala | 4 ++-- .../scala-2/sttp/tapir/internal/CaseClassUtil.scala | 11 +++++++++++ core/src/main/scala/sttp/tapir/EndpointIO.scala | 5 ++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala b/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala index 5f87b597f9..8284fe766a 100644 --- a/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala +++ b/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala @@ -142,8 +142,8 @@ abstract class EndpointAnnotationsMacro(val c: blackbox.Context) { .fold(i)(encodedExample => q"$i.schema(_.encodedExample($encodedExample))"), i => util - .extractTreeFromAnnotation(field, schemaDefaultType) - .fold(i)(default => q"$i.default($default)"), + .extractTreeAndOptFromAnnotation(field, schemaDefaultType) + .fold(i)(default => q"$i.default(${default._1}, encoded=${default._2})"), i => util .extractStringArgFromAnnotation(field, schemaFormatType) diff --git a/core/src/main/scala-2/sttp/tapir/internal/CaseClassUtil.scala b/core/src/main/scala-2/sttp/tapir/internal/CaseClassUtil.scala index 8275b6a532..2ca88425ec 100644 --- a/core/src/main/scala-2/sttp/tapir/internal/CaseClassUtil.scala +++ b/core/src/main/scala-2/sttp/tapir/internal/CaseClassUtil.scala @@ -70,4 +70,15 @@ private[tapir] class CaseClassUtil[C <: blackbox.Context, T: C#WeakTypeTag](val } } } + + def extractTreeAndOptFromAnnotation(field: Symbol, annotationType: c.Type): Option[(Tree, Option[String])] = { + field.annotations.collectFirst { + case a if a.tree.tpe <:< annotationType => + a.tree.children.tail match { + case List(t, Literal(Constant(str: String))) => (t, Some(str)) + case List(t, TypeApply(Select(_, name @ TermName(_)), List(TypeTree()))) if name.decodedName.toString.startsWith("$default") => (t, None) + case _ => throw new IllegalStateException(s"Cannot extract annotation argument from: ${c.universe.showRaw(a.tree)}") + } + } + } } diff --git a/core/src/main/scala/sttp/tapir/EndpointIO.scala b/core/src/main/scala/sttp/tapir/EndpointIO.scala index fbd58d8f68..2c54a753fc 100644 --- a/core/src/main/scala/sttp/tapir/EndpointIO.scala +++ b/core/src/main/scala/sttp/tapir/EndpointIO.scala @@ -79,7 +79,10 @@ object EndpointTransput { schema(_.modifyUnsafe[U](Schema.ModifyCollectionElements)(_.validate(v))) def description(d: String): ThisType[T] = copyWith(codec, info.description(d)) - def default(d: T): ThisType[T] = copyWith(codec.schema(_.default(d, Some(codec.encode(d)))), info) + def default(d: T, encoded: Option[Any] = None): ThisType[T] = { + val e = Some(encoded.getOrElse(codec.encode(d))) + copyWith(codec.schema(_.default(d, e)), info) + } def example(t: T): ThisType[T] = copyWith(codec, info.example(t)) def example(example: Example[T]): ThisType[T] = copyWith(codec, info.example(example)) def examples(examples: List[Example[T]]): ThisType[T] = copyWith(codec, info.examples(examples)) From 9e6ab40e25d6ebb3183f6b70e5e69d37391b039c Mon Sep 17 00:00:00 2001 From: sijarsu Date: Mon, 14 Feb 2022 15:39:15 +0100 Subject: [PATCH 10/14] Cleanup - test names --- ...g_default_when_encoded_value_specified.yml} | 0 ...efault_when_no_encoded_value_specified.yml} | 0 ...um_using_first_specified_default_value.yml} | 0 .../openapi/VerifyYamlEnumeratumTest.scala | 18 +++++++++--------- 4 files changed, 9 insertions(+), 9 deletions(-) rename docs/openapi-docs/src/test/resources/enum/{expected_enumeratum_enum_default_in_request_body_with_given_encoded.yml => expected_enumeratum_enum_adding_default_when_encoded_value_specified.yml} (100%) rename docs/openapi-docs/src/test/resources/enum/{expected_enumeratum_enum_default_in_request_body.yml => expected_enumeratum_enum_not_adding_default_when_no_encoded_value_specified.yml} (100%) rename docs/openapi-docs/src/test/resources/enum/{expected_enumeratum_enum_default_if_more_than_one.yml => expected_enumeratum_enum_using_first_specified_default_value.yml} (100%) diff --git a/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_in_request_body_with_given_encoded.yml b/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_adding_default_when_encoded_value_specified.yml similarity index 100% rename from docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_in_request_body_with_given_encoded.yml rename to docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_adding_default_when_encoded_value_specified.yml diff --git a/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_in_request_body.yml b/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_not_adding_default_when_no_encoded_value_specified.yml similarity index 100% rename from docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_in_request_body.yml rename to docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_not_adding_default_when_no_encoded_value_specified.yml diff --git a/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_if_more_than_one.yml b/docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_using_first_specified_default_value.yml similarity index 100% rename from docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_default_if_more_than_one.yml rename to docs/openapi-docs/src/test/resources/enum/expected_enumeratum_enum_using_first_specified_default_value.yml diff --git a/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala b/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala index 2c22aeac18..424bb2bdc5 100644 --- a/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala +++ b/docs/openapi-docs/src/test/scala-2/sttp/tapir/docs/openapi/VerifyYamlEnumeratumTest.scala @@ -12,7 +12,7 @@ import sttp.tapir.openapi.Info import sttp.tapir.openapi.circe.yaml._ class VerifyYamlEnumeratumTest extends AnyFunSuite with Matchers { - test("use enumeratum validator for array elements") { + test("should use enumeratum validator for array elements") { import sttp.tapir.codec.enumeratum._ val expectedYaml = load("validator/expected_valid_enumeratum.yml") @@ -26,7 +26,7 @@ class VerifyYamlEnumeratumTest extends AnyFunSuite with Matchers { actualYamlNoIndent shouldBe expectedYaml } - test("add metadata from annotations on enumeratum") { + test("should add metadata from annotations on enumeratum") { import sttp.tapir.codec.enumeratum._ val expectedYaml = load("validator/expected_valid_enumeratum_with_metadata.yml") @@ -39,7 +39,7 @@ class VerifyYamlEnumeratumTest extends AnyFunSuite with Matchers { } // #1800 - test("add enum default") { + test("should add enum default") { import sttp.tapir.codec.enumeratum._ import sttp.tapir.docs.openapi.VerifyYamlEnumeratumTest.Enumeratum.FruitType._ @@ -55,11 +55,11 @@ class VerifyYamlEnumeratumTest extends AnyFunSuite with Matchers { } // #1800 - test("use enum default if more than one") { + test("should use first specified default value") { import sttp.tapir.codec.enumeratum._ import sttp.tapir.docs.openapi.VerifyYamlEnumeratumTest.Enumeratum.FruitType._ - val expectedYaml = load("enum/expected_enumeratum_enum_default_if_more_than_one.yml") + val expectedYaml = load("enum/expected_enumeratum_enum_using_first_specified_default_value.yml") val ep1 = endpoint .in("fruit-by-type1").in(query[Enumeratum.FruitType]("type1").default(PEAR)) .out(jsonBody[Enumeratum.FruitWithEnum]) @@ -74,10 +74,10 @@ class VerifyYamlEnumeratumTest extends AnyFunSuite with Matchers { } // #1800 - test("ignore enum default in request body") { + test("should not add default when no encoded value specified") { import sttp.tapir.codec.enumeratum._ - val expectedYaml = load("enum/expected_enumeratum_enum_default_in_request_body.yml") + val expectedYaml = load("enum/expected_enumeratum_enum_not_adding_default_when_no_encoded_value_specified.yml") val ep = endpoint .post.in(jsonBody[Enumeratum.FruitQuery]) .out(jsonBody[Enumeratum.FruitWithEnum]) @@ -89,10 +89,10 @@ class VerifyYamlEnumeratumTest extends AnyFunSuite with Matchers { } // #1800 - test("add enum default in request body with given encoded") { + test("should add default when encoded value specified") { import sttp.tapir.codec.enumeratum._ - val expectedYaml = load("enum/expected_enumeratum_enum_default_in_request_body_with_given_encoded.yml") + val expectedYaml = load("enum/expected_enumeratum_enum_adding_default_when_encoded_value_specified.yml") val ep = endpoint .post.in(jsonBody[Enumeratum.FruitQueryWithEncoded]) .out(jsonBody[Enumeratum.FruitWithEnum]) From 70ec25422922aa6e397fe7956539dc26e92db63e Mon Sep 17 00:00:00 2001 From: sijarsu Date: Tue, 15 Feb 2022 11:38:58 +0100 Subject: [PATCH 11/14] Fix for Scala 3 --- .../scala-3/sttp/tapir/internal/AnnotationsMacros.scala | 4 ++-- core/src/main/scala-3/sttp/tapir/internal/CaseClass.scala | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala b/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala index 5072e68c4c..2452888218 100644 --- a/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala +++ b/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala @@ -376,8 +376,8 @@ class AnnotationsMacros[T <: Product: Type](using q: Quotes) { .getOrElse(t), t => field - .extractTreeFromAnnotation(schemaDefaultAnnotationSymbol) - .map(d => addMetadataToAtom(field, t, '{ i => i.default(${ d.asExprOf[f] }) })) + .extractTreeAndOptFromAnnotation(schemaDefaultAnnotationSymbol) + .map(d => addMetadataToAtom(field, t, '{ i => i.default(${ d._1.asExprOf[f] }, encoded=${ Expr(d._2) }) })) .getOrElse(t), t => field diff --git a/core/src/main/scala-3/sttp/tapir/internal/CaseClass.scala b/core/src/main/scala-3/sttp/tapir/internal/CaseClass.scala index 548d8cdf74..aad5480f5d 100644 --- a/core/src/main/scala-3/sttp/tapir/internal/CaseClass.scala +++ b/core/src/main/scala-3/sttp/tapir/internal/CaseClass.scala @@ -86,6 +86,12 @@ class CaseClassField[Q <: Quotes, T](using val q: Q, t: Type[T])( case _ => report.throwError(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}") } + def extractTreeAndOptFromAnnotation(annSymbol: Symbol): Option[(Tree, Option[String])] = constructorField.getAnnotation(annSymbol).map { + case Apply(_, List(t, TypeApply(Select(_, "$lessinit$greater$default$2"), _))) => (t, None) + case Apply(_, List(t, Literal(c: Constant))) if c.value.isInstanceOf[String] => (t, Some(c.value.asInstanceOf[String])) + case _ => report.throwError(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}") + } + def annotated(annSymbol: Symbol): Boolean = annotation(annSymbol).isDefined def annotation(annSymbol: Symbol): Option[Term] = constructorField.getAnnotation(annSymbol) } From 4851df13bbea6ca4460b6cccb43ea62be4666bd8 Mon Sep 17 00:00:00 2001 From: sijarsu Date: Tue, 15 Feb 2022 20:15:18 +0100 Subject: [PATCH 12/14] Fixing the target of the default macro --- .../tapir/generic/internal/EndpointAnnotationsMacro.scala | 2 +- .../scala-3/sttp/tapir/internal/AnnotationsMacros.scala | 2 +- core/src/main/scala/sttp/tapir/EndpointIO.scala | 7 ++----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala b/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala index 8284fe766a..5da55e0aa9 100644 --- a/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala +++ b/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala @@ -143,7 +143,7 @@ abstract class EndpointAnnotationsMacro(val c: blackbox.Context) { i => util .extractTreeAndOptFromAnnotation(field, schemaDefaultType) - .fold(i)(default => q"$i.default(${default._1}, encoded=${default._2})"), + .fold(i)(default => q"$i.schema(_.default(${default._1}, encoded=${default._2}))"), i => util .extractStringArgFromAnnotation(field, schemaFormatType) diff --git a/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala b/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala index 2452888218..a86a9bdad4 100644 --- a/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala +++ b/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala @@ -377,7 +377,7 @@ class AnnotationsMacros[T <: Product: Type](using q: Quotes) { t => field .extractTreeAndOptFromAnnotation(schemaDefaultAnnotationSymbol) - .map(d => addMetadataToAtom(field, t, '{ i => i.default(${ d._1.asExprOf[f] }, encoded=${ Expr(d._2) }) })) + .map(d => addMetadataToAtom(field, t, '{ i => i.schema(_.default(${ d._1.asExprOf[f] }, encoded=${ Expr(d._2) })) })) .getOrElse(t), t => field diff --git a/core/src/main/scala/sttp/tapir/EndpointIO.scala b/core/src/main/scala/sttp/tapir/EndpointIO.scala index 2c54a753fc..cea5f6ce0d 100644 --- a/core/src/main/scala/sttp/tapir/EndpointIO.scala +++ b/core/src/main/scala/sttp/tapir/EndpointIO.scala @@ -79,10 +79,7 @@ object EndpointTransput { schema(_.modifyUnsafe[U](Schema.ModifyCollectionElements)(_.validate(v))) def description(d: String): ThisType[T] = copyWith(codec, info.description(d)) - def default(d: T, encoded: Option[Any] = None): ThisType[T] = { - val e = Some(encoded.getOrElse(codec.encode(d))) - copyWith(codec.schema(_.default(d, e)), info) - } + def default(d: T): ThisType[T] = copyWith(codec.schema(_.default(d, Some(codec.encode(d)))), info) def example(t: T): ThisType[T] = copyWith(codec, info.example(t)) def example(example: Example[T]): ThisType[T] = copyWith(codec, info.example(example)) def examples(examples: List[Example[T]]): ThisType[T] = copyWith(codec, info.examples(examples)) @@ -414,7 +411,7 @@ object EndpointIO { override private[tapir] type ThisType[X] = OneOfBody[O, X] override def show: String = showOneOf(variants.map { variant => val prefix = - if ((ContentTypeRange.exactNoCharset(variant.body.codec.format.mediaType)) == variant.range) "" else s"${variant.range} -> " + if (ContentTypeRange.exactNoCharset(variant.body.codec.format.mediaType) == variant.range) "" else s"${variant.range} -> " prefix + variant.body.show }) override def map[U](m: Mapping[T, U]): OneOfBody[O, U] = copy[O, U](mapping = mapping.map(m)) From c8a405f2f6e828e33accc97bb00f066aac82a6cd Mon Sep 17 00:00:00 2001 From: sijarsu Date: Wed, 16 Feb 2022 16:25:03 +0100 Subject: [PATCH 13/14] Reverting the default macro after review --- .../sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala | 2 +- .../main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala b/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala index 5da55e0aa9..c0656d0602 100644 --- a/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala +++ b/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala @@ -143,7 +143,7 @@ abstract class EndpointAnnotationsMacro(val c: blackbox.Context) { i => util .extractTreeAndOptFromAnnotation(field, schemaDefaultType) - .fold(i)(default => q"$i.schema(_.default(${default._1}, encoded=${default._2}))"), + .fold(i)(default => q"$i.default(${default._1})"), i => util .extractStringArgFromAnnotation(field, schemaFormatType) diff --git a/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala b/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala index a86a9bdad4..91625b0f41 100644 --- a/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala +++ b/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala @@ -377,7 +377,7 @@ class AnnotationsMacros[T <: Product: Type](using q: Quotes) { t => field .extractTreeAndOptFromAnnotation(schemaDefaultAnnotationSymbol) - .map(d => addMetadataToAtom(field, t, '{ i => i.schema(_.default(${ d._1.asExprOf[f] }, encoded=${ Expr(d._2) })) })) + .map(d => addMetadataToAtom(field, t, '{ i => i.default(${ d._1.asExprOf[f] }) })) .getOrElse(t), t => field From 143b89fbbdbb9b8054d9b506c52c895194943590 Mon Sep 17 00:00:00 2001 From: sijarsu Date: Thu, 17 Feb 2022 19:45:49 +0100 Subject: [PATCH 14/14] Test macro with various combinations of annotation params --- .../internal/EndpointAnnotationsMacro.scala | 2 +- .../sttp/tapir/internal/CaseClassUtil.scala | 7 ++-- .../tapir/internal/AnnotationsMacros.scala | 2 +- .../sttp/tapir/internal/CaseClass.scala | 9 +++-- .../annotations/DeriveEndpointIOTest.scala | 33 +++++++++++++++++++ 5 files changed, 45 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala b/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala index c0656d0602..39ee49e86f 100644 --- a/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala +++ b/core/src/main/scala-2/sttp/tapir/generic/internal/EndpointAnnotationsMacro.scala @@ -142,7 +142,7 @@ abstract class EndpointAnnotationsMacro(val c: blackbox.Context) { .fold(i)(encodedExample => q"$i.schema(_.encodedExample($encodedExample))"), i => util - .extractTreeAndOptFromAnnotation(field, schemaDefaultType) + .extractTreeAndOptTreeFromAnnotation(field, schemaDefaultType) .fold(i)(default => q"$i.default(${default._1})"), i => util diff --git a/core/src/main/scala-2/sttp/tapir/internal/CaseClassUtil.scala b/core/src/main/scala-2/sttp/tapir/internal/CaseClassUtil.scala index 2ca88425ec..6cdd472a32 100644 --- a/core/src/main/scala-2/sttp/tapir/internal/CaseClassUtil.scala +++ b/core/src/main/scala-2/sttp/tapir/internal/CaseClassUtil.scala @@ -71,12 +71,13 @@ private[tapir] class CaseClassUtil[C <: blackbox.Context, T: C#WeakTypeTag](val } } - def extractTreeAndOptFromAnnotation(field: Symbol, annotationType: c.Type): Option[(Tree, Option[String])] = { + def extractTreeAndOptTreeFromAnnotation(field: Symbol, annotationType: c.Type): Option[(Tree, Tree)] = { field.annotations.collectFirst { case a if a.tree.tpe <:< annotationType => a.tree.children.tail match { - case List(t, Literal(Constant(str: String))) => (t, Some(str)) - case List(t, TypeApply(Select(_, name @ TermName(_)), List(TypeTree()))) if name.decodedName.toString.startsWith("$default") => (t, None) + case List(t, u @ Apply(TypeApply(name @ Select(_, _), List(TypeTree())), List(_))) if name.toString.startsWith("scala.Some.apply") => (t, u) + case List(t, u @ Select(_, _)) if u.toString.startsWith("scala.None") => (t, u) + case List(t, TypeApply(Select(_, name @ TermName(_)), List(TypeTree()))) if name.decodedName.toString.startsWith("$default") => (t, q"None") case _ => throw new IllegalStateException(s"Cannot extract annotation argument from: ${c.universe.showRaw(a.tree)}") } } diff --git a/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala b/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala index 91625b0f41..bd5c85ff64 100644 --- a/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala +++ b/core/src/main/scala-3/sttp/tapir/internal/AnnotationsMacros.scala @@ -376,7 +376,7 @@ class AnnotationsMacros[T <: Product: Type](using q: Quotes) { .getOrElse(t), t => field - .extractTreeAndOptFromAnnotation(schemaDefaultAnnotationSymbol) + .extractTreeAndOptTreeFromAnnotation(schemaDefaultAnnotationSymbol) .map(d => addMetadataToAtom(field, t, '{ i => i.default(${ d._1.asExprOf[f] }) })) .getOrElse(t), t => diff --git a/core/src/main/scala-3/sttp/tapir/internal/CaseClass.scala b/core/src/main/scala-3/sttp/tapir/internal/CaseClass.scala index aad5480f5d..a02b67f44e 100644 --- a/core/src/main/scala-3/sttp/tapir/internal/CaseClass.scala +++ b/core/src/main/scala-3/sttp/tapir/internal/CaseClass.scala @@ -86,9 +86,12 @@ class CaseClassField[Q <: Quotes, T](using val q: Q, t: Type[T])( case _ => report.throwError(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}") } - def extractTreeAndOptFromAnnotation(annSymbol: Symbol): Option[(Tree, Option[String])] = constructorField.getAnnotation(annSymbol).map { - case Apply(_, List(t, TypeApply(Select(_, "$lessinit$greater$default$2"), _))) => (t, None) - case Apply(_, List(t, Literal(c: Constant))) if c.value.isInstanceOf[String] => (t, Some(c.value.asInstanceOf[String])) + def extractTreeAndOptTreeFromAnnotation(annSymbol: Symbol): Option[(Tree, Tree)] = constructorField.getAnnotation(annSymbol).map { + case Apply(_, List(t, TypeApply(Select(_, "$lessinit$greater$default$2"), _))) => (t, '{ None }.asTerm) + case Apply(_, List(t, u @ Ident("None"))) => (t, u) + case Apply(_, List(t, NamedArg(_, u @ Ident("None")))) => (t, u) + case Apply(_, List(t, u @ Apply(TypeApply(name @ Select(Ident("Some"), "apply"), List(_)), List(_)))) => (t, u) + case Apply(_, List(t, NamedArg(_, u @ Apply(TypeApply(name @ Select(Ident("Some"), "apply"), List(_)), List(_))))) => (t, u) case _ => report.throwError(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}") } diff --git a/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala b/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala index 79cb7eedac..b7e3970b0f 100644 --- a/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala +++ b/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala @@ -153,6 +153,27 @@ final case class TapirResponseTest2( setCookies: List[CookieWithMeta] ) +final case class TapirRequestTest15( + @query + @Schema.annotations.default(11) + field1: Int, + @query + @Schema.annotations.default(12, None) + field2: Int, + @query + @Schema.annotations.default(13, encoded=None) + field3: Int, + @query + @Schema.annotations.default(14, Some(140)) + field4: Int, + @query + @Schema.annotations.default(15, Some("150")) + field5: Int, + @query + @Schema.annotations.default(16, encoded=Some(160)) + field6: Int +) + class DeriveEndpointIOTest extends AnyFlatSpec with Matchers with TableDrivenPropertyChecks with Tapir { "@endpointInput" should "derive correct input for @query, @cookie, @header" in { @@ -253,6 +274,18 @@ class DeriveEndpointIOTest extends AnyFlatSpec with Matchers with TableDrivenPro derived.codec.schema.applyValidation(TapirRequestTest8(1)) shouldBe empty } + it should "derive default annotation correctly" in { + val expectedInput = query[Int]("field1").default(11) + .and(query[Int]("field2").default(12)) + .and(query[Int]("field3").default(13)) + .and(query[Int]("field4").default(14)) + .and(query[Int]("field5").default(15)) + .and(query[Int]("field6").default(16)) + .mapTo[TapirRequestTest15] + + compareTransputs(EndpointInput.derived[TapirRequestTest15], expectedInput) shouldBe true + } + val bodyInputDerivations = Table( ("body", "expected", "derived"),