Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enforce fmt checks on ci #312

Merged
merged 1 commit into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/scala.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,19 @@ jobs:

- name: Test
run: sbt ++${{matrix.scala}} test

fmt:
name: Fmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17

- name: Fmt
run: sbt "scalafmtCheckAll;scalafmtSbtCheck"

publish:
name: Publish Artifacts
Expand Down
29 changes: 15 additions & 14 deletions modules/circe/src/main/scala/io.circe/JsonNumberHack.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import tethys.writers.tokens.TokenWriter

trait JsonNumberHack {
protected def writeNumber(number: JsonNumber, writer: TokenWriter): Unit = {
if(JsonNumberHack.isHackCompatible) {
if (JsonNumberHack.isHackCompatible) {
number match {
case value: JsonDecimal =>
writer.writeRawJson(value.toString)
Expand All @@ -27,17 +27,18 @@ trait JsonNumberHack {
}

object JsonNumberHack {
private val isHackCompatible: Boolean = try {
val loader = getClass.getClassLoader
loader.loadClass("io.circe.BiggerDecimalJsonNumber")
loader.loadClass("io.circe.JsonDecimal")
loader.loadClass("io.circe.JsonBiggerDecimal")
loader.loadClass("io.circe.JsonBigDecimal")
loader.loadClass("io.circe.JsonLong")
loader.loadClass("io.circe.JsonDouble")
loader.loadClass("io.circe.JsonFloat")
true
} catch {
case _: ClassNotFoundException => false
}
private val isHackCompatible: Boolean =
try {
val loader = getClass.getClassLoader
loader.loadClass("io.circe.BiggerDecimalJsonNumber")
loader.loadClass("io.circe.JsonDecimal")
loader.loadClass("io.circe.JsonBiggerDecimal")
loader.loadClass("io.circe.JsonBigDecimal")
loader.loadClass("io.circe.JsonLong")
loader.loadClass("io.circe.JsonDouble")
loader.loadClass("io.circe.JsonFloat")
true
} catch {
case _: ClassNotFoundException => false
}
}
100 changes: 58 additions & 42 deletions modules/circe/src/main/scala/tethys/circe/ast/CirceSupport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,59 +8,73 @@ import tethys.{JsonObjectWriter, JsonReader, JsonWriter}
import tethys.writers.tokens.TokenWriter

trait CirceSupport {
implicit lazy val circeJsonObjectWriter: JsonWriter[JsonObject] = new JsonObjectWriter[JsonObject] {
def writeValues(value: JsonObject, writer: TokenWriter): Unit = {
val folder = new TethysJsonFolder(writer)
val it = value.toIterable.iterator
while (it.hasNext) {
val (k, v) = it.next()

writer.writeFieldName(k)
v.foldWith(folder)
implicit lazy val circeJsonObjectWriter: JsonWriter[JsonObject] =
new JsonObjectWriter[JsonObject] {
def writeValues(value: JsonObject, writer: TokenWriter): Unit = {
val folder = new TethysJsonFolder(writer)
val it = value.toIterable.iterator
while (it.hasNext) {
val (k, v) = it.next()

writer.writeFieldName(k)
v.foldWith(folder)
}
}
}
}

implicit lazy val circeJsonWriter: JsonWriter[Json] = new JsonWriter[Json] {
def write(value: Json, writer: TokenWriter): Unit = value.foldWith(new TethysJsonFolder(writer))
def write(value: Json, writer: TokenWriter): Unit =
value.foldWith(new TethysJsonFolder(writer))
}

implicit lazy val circeJsonObjectReader: JsonReader[JsonObject] = new JsonReader[JsonObject] {
def read(it: TokenIterator)(implicit fieldName: FieldName): JsonObject =
if (!it.currentToken().isObjectStart) ReaderError.wrongJson(s"Expected object start but found: ${it.currentToken()}")
else {
it.next()

var builder = ArrayBuffer.newBuilder[(String, Json)]
while (!it.currentToken().isObjectEnd) {
val token = it.currentToken()
if (token.isFieldName) {
val name = it.fieldName()
val value = circeJsonReader.read(it.next())(fieldName.appendFieldName(name))

builder += ((name, value))
implicit lazy val circeJsonObjectReader: JsonReader[JsonObject] =
new JsonReader[JsonObject] {
def read(it: TokenIterator)(implicit fieldName: FieldName): JsonObject =
if (!it.currentToken().isObjectStart)
ReaderError.wrongJson(
s"Expected object start but found: ${it.currentToken()}"
)
else {
it.next()

var builder = ArrayBuffer.newBuilder[(String, Json)]
while (!it.currentToken().isObjectEnd) {
val token = it.currentToken()
if (token.isFieldName) {
val name = it.fieldName()
val value =
circeJsonReader.read(it.next())(fieldName.appendFieldName(name))

builder += ((name, value))
} else
ReaderError.wrongJson(
s"Expect end of object or field name but '$token' found"
)(fieldName)
}
else ReaderError.wrongJson(s"Expect end of object or field name but '$token' found")(fieldName)
}
it.next()
it.next()

JsonObject.fromIterable(builder.result)
}
}
JsonObject.fromIterable(builder.result)
}
}

implicit lazy val circeJsonReader: JsonReader[Json] = new JsonReader[Json] {
def read(it: TokenIterator)(implicit fieldName: FieldName): Json = {
val token = it.currentToken()

if (token.isObjectStart) Json.fromJsonObject(circeJsonObjectReader.read(it))
else if (token.isArrayStart) Json.fromValues(JsonReader[Vector[Json]].read(it))
else if (token.isStringValue) Json.fromString(JsonReader.stringReader.read(it))
else if (token.isBooleanValue) Json.fromBoolean(JsonReader.booleanReader.read(it))
else if (token.isNumberValue) JsonReader.numberReader.read(it) match {
case x@(_: java.lang.Byte | _: java.lang.Short | _: java.lang.Long) => Json.fromLong(x.longValue)
case x: java.lang.Integer => Json.fromInt(x)
case x: java.lang.Float => Json.fromFloatOrNull(x)
case x: java.lang.Double => Json.fromDoubleOrNull(x)
if (token.isObjectStart)
Json.fromJsonObject(circeJsonObjectReader.read(it))
else if (token.isArrayStart)
Json.fromValues(JsonReader[Vector[Json]].read(it))
else if (token.isStringValue)
Json.fromString(JsonReader.stringReader.read(it))
else if (token.isBooleanValue)
Json.fromBoolean(JsonReader.booleanReader.read(it))
else if (token.isNumberValue) JsonReader.numberReader.read(it) match {
case x @ (_: java.lang.Byte | _: java.lang.Short | _: java.lang.Long) =>
Json.fromLong(x.longValue)
case x: java.lang.Integer => Json.fromInt(x)
case x: java.lang.Float => Json.fromFloatOrNull(x)
case x: java.lang.Double => Json.fromDoubleOrNull(x)

case x: java.math.BigInteger => Json.fromBigInt(x)
case x: BigInt => Json.fromBigInt(x)
Expand All @@ -69,12 +83,14 @@ trait CirceSupport {
case x: BigDecimal => Json.fromBigDecimal(x)
case x => Json.fromBigDecimal(x.doubleValue)
}
else if (token.isNullValue) { it.next(); Json.Null }
else if (token.isNullValue) { it.next(); Json.Null }
else ReaderError.wrongJson(s"Unexpected token found: $token")(fieldName)
}
}

private[this] class TethysJsonFolder(writer: TokenWriter) extends Json.Folder[Unit] with JsonNumberHack {
private[this] class TethysJsonFolder(writer: TokenWriter)
extends Json.Folder[Unit]
with JsonNumberHack {
def onNull: Unit = writer.writeNull()
def onBoolean(value: Boolean): Unit = writer.writeBoolean(value)
def onNumber(value: JsonNumber): Unit = writeNumber(value, writer)
Expand Down
46 changes: 29 additions & 17 deletions modules/circe/src/test/scala/tethys/circe/CirceSupportTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,17 @@ class CirceSupportTest extends AnyFlatSpec with Matchers {
}

it should "parse Double" in {
token(100.0D).tokensAs[Json] shouldBe Json.fromDoubleOrNull(100.0D)
token(100.0d).tokensAs[Json] shouldBe Json.fromDoubleOrNull(100.0d)
}

it should "parse BigInt" in {
token(BigInt(100L)).tokensAs[Json] shouldBe Json.fromBigInt(BigInt(100L))
}

it should "parse BigDecimal" in {
token(BigDecimal(100.0D)).tokensAs[Json] shouldBe Json.fromBigDecimal(100.0D)
token(BigDecimal(100.0d)).tokensAs[Json] shouldBe Json.fromBigDecimal(
100.0d
)
}

it should "parse String" in {
Expand All @@ -52,7 +54,9 @@ class CirceSupportTest extends AnyFlatSpec with Matchers {

it should "parse Array" in {
arr(1, 2L, 3).tokensAs[Json] shouldBe
Json.fromValues(List(Json.fromLong(1L), Json.fromLong(2L), Json.fromLong(3L)))
Json.fromValues(
List(Json.fromLong(1L), Json.fromLong(2L), Json.fromLong(3L))
)
}

it should "parse JsonObject" in {
Expand All @@ -72,13 +76,15 @@ class CirceSupportTest extends AnyFlatSpec with Matchers {
}

it should "parse Array of JsonObject" in {
arr(obj("a" -> "b"), obj("c" -> "d")).tokensAs[Json] shouldBe Json.fromValues(List(
Json.fromJsonObject(JsonObject("a" -> Json.fromString("b"))),
Json.fromJsonObject(JsonObject("c" -> Json.fromString("d")))
))
arr(obj("a" -> "b"), obj("c" -> "d"))
.tokensAs[Json] shouldBe Json.fromValues(
List(
Json.fromJsonObject(JsonObject("a" -> Json.fromString("b"))),
Json.fromJsonObject(JsonObject("c" -> Json.fromString("d")))
)
)
}


behavior of "Circe ast JsonWriter"

it should "write Int" in {
Expand All @@ -94,19 +100,21 @@ class CirceSupportTest extends AnyFlatSpec with Matchers {
}

it should "write Double" in {
Json.fromDouble(100.0D).asTokenList shouldBe token(100.0D)
Json.fromDouble(100.0d).asTokenList shouldBe token(100.0d)
}

it should "write BigInt" in {
Json.fromBigInt(BigInt("10000000000")).asTokenList match {
case DoubleValueNode(d) :: Nil => d shouldBe 1.0e10 // 2.11 only behavior
case LongValueNode(l) :: Nil => l shouldBe 10000000000L
case _ => fail()
case LongValueNode(l) :: Nil => l shouldBe 10000000000L
case _ => fail()
}
}

it should "write BigDecimal" in {
Json.fromBigDecimal(BigDecimal(100.0D)).asTokenList shouldBe token(BigDecimal(100.0D))
Json.fromBigDecimal(BigDecimal(100.0d)).asTokenList shouldBe token(
BigDecimal(100.0d)
)
}

it should "write String" in {
Expand All @@ -126,11 +134,15 @@ class CirceSupportTest extends AnyFlatSpec with Matchers {
}

it should "write Array" in {
Json.fromValues(List(
Json.fromInt(1),
Json.fromInt(2),
Json.fromInt(3)
)).asTokenList shouldBe arr(1L, 2L, 3L)
Json
.fromValues(
List(
Json.fromInt(1),
Json.fromInt(2),
Json.fromInt(3)
)
)
.asTokenList shouldBe arr(1L, 2L, 3L)
}

it should "write JsonObject" in {
Expand Down
33 changes: 19 additions & 14 deletions modules/core/src/main/scala-3/tethys/FieldStyle.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package tethys

import java.util.regex.Pattern


enum FieldStyle {
case Capitalize, Uncapitalize, LowerCase, UpperCase

Expand All @@ -11,36 +10,42 @@ enum FieldStyle {
case SnakeCase, LowerSnakeCase, UpperSnakeCase, CapitalizedSnakeCase
}

private[tethys]
object FieldStyle:
private[tethys] object FieldStyle:
private val regexp1: Pattern = Pattern.compile("([A-Z]+)([A-Z][a-z])")
private val regexp2: Pattern = Pattern.compile("([a-z\\d])([A-Z])")
private val replacement: String = "$1_$2"
private val snakeCase: String => String = splitName(_).mkString("_")
private val kebabcase: String => String = splitName(_).mkString("-")
private val capitalize: String => String = _.capitalize
private val uncapitalize: String => String = (field: String) => field.updated(0, field.charAt(0).toLower)
private val uncapitalize: String => String = (field: String) =>
field.updated(0, field.charAt(0).toLower)
private val lowercase: String => String = _.toLowerCase()
private val uppercase: String => String = _.toUpperCase()

def applyStyle(string: String, style: FieldStyle): String =
style match
case FieldStyle.Capitalize => capitalize(string)
case FieldStyle.Capitalize => capitalize(string)
case FieldStyle.Uncapitalize => uncapitalize(string)
case FieldStyle.LowerCase => lowercase(string)
case FieldStyle.UpperCase => uppercase(string)
case FieldStyle.LowerCase => lowercase(string)
case FieldStyle.UpperCase => uppercase(string)

case FieldStyle.KebabCase => kebabcase(string)
case FieldStyle.KebabCase => kebabcase(string)
case FieldStyle.LowerKebabCase => (kebabcase andThen lowercase)(string)
case FieldStyle.UpperKebabCase => (kebabcase andThen uppercase)(string)
case FieldStyle.CapitalizedKebabCase => (kebabcase andThen capitalize)(string)
case FieldStyle.CapitalizedKebabCase =>
(kebabcase andThen capitalize)(string)

case FieldStyle.SnakeCase => snakeCase(string)
case FieldStyle.SnakeCase => snakeCase(string)
case FieldStyle.LowerSnakeCase => (snakeCase andThen lowercase)(string)
case FieldStyle.UpperSnakeCase => (snakeCase andThen uppercase)(string)
case FieldStyle.CapitalizedSnakeCase => (snakeCase andThen capitalize)(string)

case FieldStyle.CapitalizedSnakeCase =>
(snakeCase andThen capitalize)(string)

private def splitName(name: String): List[String] =
val first = FieldStyle.regexp1.matcher(name).replaceAll(FieldStyle.replacement)
FieldStyle.regexp2.matcher(first).replaceAll(FieldStyle.replacement).split("_").toList
val first =
FieldStyle.regexp1.matcher(name).replaceAll(FieldStyle.replacement)
FieldStyle.regexp2
.matcher(first)
.replaceAll(FieldStyle.replacement)
.split("_")
.toList
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ object OrdinalEnumJsonReader:
case ex: NoSuchElementException =>
ReaderError.wrongJson(s"Unknown enum ordinal: $res")
else
ReaderError.wrongJson(s"Expected int value but found: ${it.currentToken()}")
ReaderError.wrongJson(
s"Expected int value but found: ${it.currentToken()}"
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@ package tethys

import tethys.writers.tokens.TokenWriter


trait OrdinalEnumJsonWriter[A] extends JsonWriter[A]

object OrdinalEnumJsonWriter:
inline def derived[A <: scala.reflect.Enum]: OrdinalEnumJsonWriter[A] =
(value: A, tokenWriter: TokenWriter) => tokenWriter.writeNumber(value.ordinal)
(value: A, tokenWriter: TokenWriter) =>
tokenWriter.writeNumber(value.ordinal)

inline def withLabel[A <: scala.reflect.Enum](label: String): JsonObjectWriter[A] =
inline def withLabel[A <: scala.reflect.Enum](
label: String
): JsonObjectWriter[A] =
(value: A, tokenWriter: writers.tokens.TokenWriter) =>
tokenWriter.writeFieldName(label)
tokenWriter.writeNumber(value.ordinal)
Loading