Skip to content

Commit

Permalink
Schema screaming snake case (#3607)
Browse files Browse the repository at this point in the history
Co-authored-by: Kamil Kloch <kamil.kloch@superfund-technologies.com>
  • Loading branch information
kamilkloch and Kamil Kloch committed Mar 14, 2024
1 parent a2282d3 commit 62ff1a6
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 0 deletions.
10 changes: 10 additions & 0 deletions core/src/main/scala/sttp/tapir/generic/Configuration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ import java.util.regex.Pattern
*/
final case class Configuration(toEncodedName: String => String, discriminator: Option[String], toDiscriminatorValue: SName => String) {
def withSnakeCaseMemberNames: Configuration = copy(toEncodedName = Configuration.snakeCaseTransformation)
def withScreamingSnakeCaseMemberNames: Configuration = copy(toEncodedName = Configuration.screamingSnakeCaseTransformation)
def withKebabCaseMemberNames: Configuration = copy(toEncodedName = Configuration.kebabCaseTransformation)
def withDiscriminator(d: String): Configuration = copy(discriminator = Some(d))
def withSnakeCaseDiscriminatorValues: Configuration = copy(toDiscriminatorValue = Configuration.shortSnakeCaseSubtypeTransformation)
def withScreamingSnakeCaseDiscriminatorValues: Configuration = copy(toDiscriminatorValue = Configuration.shortScreamingSnakeCaseSubtypeTransformation)
def withKebabCaseDiscriminatorValues: Configuration = copy(toDiscriminatorValue = Configuration.shortKebabCaseSubtypeTransformation)
def withFullDiscriminatorValues: Configuration = copy(toDiscriminatorValue = Configuration.fullIdentitySubtypeTransformation)
def withFullSnakeCaseDiscriminatorValues: Configuration = copy(toDiscriminatorValue = Configuration.fullSnakeCaseSubtypeTransformation)
Expand All @@ -34,6 +36,11 @@ object Configuration {
swapPattern.matcher(partial).replaceAll("$1_$2").toLowerCase
}

private val screamingSnakeCaseTransformation: String => String = s => {
val partial = basePattern.matcher(s).replaceAll("$1_$2")
swapPattern.matcher(partial).replaceAll("$1_$2").toUpperCase
}

private val kebabCaseTransformation: String => String = s => {
val partial = basePattern.matcher(s).replaceAll("$1-$2")
swapPattern.matcher(partial).replaceAll("$1-$2").toLowerCase
Expand All @@ -54,6 +61,9 @@ object Configuration {
private val shortSnakeCaseSubtypeTransformation: SName => String =
shortIdentitySubtypeTransformation.andThen(snakeCaseTransformation)

private val shortScreamingSnakeCaseSubtypeTransformation: SName => String =
shortIdentitySubtypeTransformation.andThen(screamingSnakeCaseTransformation)

private val shortKebabCaseSubtypeTransformation: SName => String =
shortIdentitySubtypeTransformation.andThen(kebabCaseTransformation)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ class FormCodecDerivationTest extends AnyFlatSpec with FormCodecDerivationTestEx
codec.decode("complicated_name=10") shouldBe DecodeResult.Value(CaseClassWithComplicatedName(10))
}

it should "generate a codec for a one-arg case class using screaming-snake-case naming transformation" in {
// given
implicit val configuration: Configuration = Configuration.default.withScreamingSnakeCaseMemberNames
val codec = implicitly[Codec[String, CaseClassWithComplicatedName, CodecFormat.XWwwFormUrlencoded]]

// when
codec.encode(CaseClassWithComplicatedName(10)) shouldBe "COMPLICATED_NAME=10"
codec.decode("COMPLICATED_NAME=10") shouldBe DecodeResult.Value(CaseClassWithComplicatedName(10))
}

it should "generate a codec for a one-arg case class using kebab-case naming transformation" in {
// given
implicit val configuration: Configuration = Configuration.default.withKebabCaseMemberNames
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ class MultipartCodecDerivationTest extends AnyFlatSpec with MultipartCodecDeriva
codec.decode(createStringParts(List(("complicated_name", "10")))) shouldBe DecodeResult.Value(CaseClassWithComplicatedName(10))
}

it should "generate a codec for a one-arg case class using screaming-snake-case naming transformation" in {
// given
implicit val configuration: Configuration = Configuration.default.withScreamingSnakeCaseMemberNames
val codec = implicitly[MultipartCodec[CaseClassWithComplicatedName]].codec

// when
toPartData(codec.encode(CaseClassWithComplicatedName(10))) shouldBe List(("COMPLICATED_NAME", "10"))
codec.decode(createStringParts(List(("COMPLICATED_NAME", "10")))) shouldBe DecodeResult.Value(CaseClassWithComplicatedName(10))
}

it should "generate a codec for a one-arg case class with list" in {
// given
case class Test1(f1: List[Int])
Expand Down
21 changes: 21 additions & 0 deletions core/src/test/scala/sttp/tapir/generic/SchemaGenericAutoTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,13 @@ class SchemaGenericAutoTest extends AsyncFlatSpec with Matchers {
implicitly[Schema[D]].schemaType shouldBe expectedSnakeCaseNaming
}

it should "find schema for a simple case class and use screaming snake case naming transformation" in {
val expectedScreamingSnakeCaseNaming =
expectedDSchema.copy(fields = List(field[D, String](FieldName("someFieldName", "SOME_FIELD_NAME"), stringSchema)))
implicit val customConf: Configuration = Configuration.default.withScreamingSnakeCaseMemberNames
implicitly[Schema[D]].schemaType shouldBe expectedScreamingSnakeCaseNaming
}

it should "find schema for a simple case class and use kebab case naming transformation" in {
val expectedKebabCaseNaming =
expectedDSchema.copy(fields = List(field[D, String](FieldName("someFieldName", "some-field-name"), stringSchema)))
Expand Down Expand Up @@ -302,6 +309,20 @@ class SchemaGenericAutoTest extends AsyncFlatSpec with Matchers {
)
}

it should "generate one-of schema using the given discriminator (screaming snake case subtype names)" in {
implicit val customConf: Configuration = Configuration.default.withDiscriminator("who_am_i").withScreamingSnakeCaseDiscriminatorValues
implicitly[Schema[Entity]].schemaType.asInstanceOf[SCoproduct[Entity]].discriminator shouldBe Some(
SDiscriminator(
FieldName("who_am_i"),
Map(
"ORGANIZATION" -> SRef(SName("sttp.tapir.generic.Organization")),
"PERSON" -> SRef(SName("sttp.tapir.generic.Person")),
"UNKNOWN_ENTITY" -> SRef(SName("sttp.tapir.generic.UnknownEntity"))
)
)
)
}

it should "generate one-of schema using the given discriminator (full subtype names)" in {
implicit val customConf: Configuration = Configuration.default.withDiscriminator("who_am_i").withFullDiscriminatorValues
implicitly[Schema[Entity]].schemaType.asInstanceOf[SCoproduct[Entity]].discriminator shouldBe Some(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ final case class PicklerConfiguration(genericDerivationConfig: Configuration) {
export genericDerivationConfig.{toEncodedName, discriminator, toDiscriminatorValue}

def withSnakeCaseMemberNames: PicklerConfiguration = PicklerConfiguration(genericDerivationConfig.withSnakeCaseMemberNames)
def withScreamingSnakeCaseMemberNames: PicklerConfiguration = PicklerConfiguration(genericDerivationConfig.withScreamingSnakeCaseMemberNames)
def withKebabCaseMemberNames: PicklerConfiguration = PicklerConfiguration(genericDerivationConfig.withKebabCaseMemberNames)
def withDiscriminator(d: String): PicklerConfiguration = PicklerConfiguration(genericDerivationConfig.withDiscriminator(d))
def withToEncodedName(toEncodedName: String => String) = PicklerConfiguration(genericDerivationConfig.copy(toEncodedName = toEncodedName))
def withSnakeCaseDiscriminatorValues: PicklerConfiguration = PicklerConfiguration(
genericDerivationConfig.withSnakeCaseDiscriminatorValues
)
def withScreamingSnakeCaseDiscriminatorValues: PicklerConfiguration = PicklerConfiguration(
genericDerivationConfig.withScreamingSnakeCaseDiscriminatorValues
)
def withKebabCaseDiscriminatorValues: PicklerConfiguration = PicklerConfiguration(
genericDerivationConfig.withKebabCaseDiscriminatorValues
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,13 @@ class SchemaGenericAutoTest extends AsyncFlatSpec with Matchers with Inside {
implicitlySchema[D].schemaType shouldBe expectedSnakeCaseNaming
}

it should "find schema for a simple case class and use screaming snake case naming transformation" in {
val expectedScreamingSnakeCaseNaming =
expectedDSchema.copy(fields = List(field[D, String](FieldName("someFieldName", "SOME_FIELD_NAME"), stringSchema)))
implicit val customConf: PicklerConfiguration = PicklerConfiguration.default.withScreamingSnakeCaseMemberNames
implicitlySchema[D].schemaType shouldBe expectedScreamingSnakeCaseNaming
}

it should "find schema for a simple case class and use kebab case naming transformation" in {
val expectedKebabCaseNaming =
expectedDSchema.copy(fields = List(field[D, String](FieldName("someFieldName", "some-field-name"), stringSchema)))
Expand Down Expand Up @@ -347,6 +354,21 @@ class SchemaGenericAutoTest extends AsyncFlatSpec with Matchers with Inside {
)
}

it should "generate one-of schema using the given discriminator (screaming snake case subtype names)" in {
implicit val customConf: PicklerConfiguration =
PicklerConfiguration.default.withDiscriminator("who_am_i").withScreamingSnakeCaseDiscriminatorValues
implicitlySchema[Entity].schemaType.asInstanceOf[SCoproduct[Entity]].discriminator shouldBe Some(
SDiscriminator(
FieldName("who_am_i"),
Map(
"ORGANIZATION" -> SRef(SName("sttp.tapir.json.pickler.Organization")),
"PERSON" -> SRef(SName("sttp.tapir.json.pickler.Person")),
"UNKNOWN_ENTITY" -> SRef(SName("sttp.tapir.json.pickler.UnknownEntity"))
)
)
)
}

it should "generate one-of schema using the given discriminator (full subtype names)" in {
implicit val customConf: PicklerConfiguration = PicklerConfiguration.default.withDiscriminator("who_am_i").withFullDiscriminatorValues
implicitlySchema[Entity].schemaType.asInstanceOf[SCoproduct[Entity]].discriminator shouldBe Some(
Expand Down

0 comments on commit 62ff1a6

Please sign in to comment.