Skip to content

Commit

Permalink
Merge pull request #252 from vhiairrassary/vhiairrassary/add-scala-3-…
Browse files Browse the repository at this point in the history
…support

Add Scala 3 support
  • Loading branch information
fthomas authored Feb 7, 2023
2 parents 1c953c5 + c9a017a commit 734bb47
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 5 deletions.
14 changes: 13 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
scala: [2.12.17, 2.13.10]
scala: [2.12.17, 2.13.10, 3.2.2]
java: [temurin@11, temurin@17]
project: [rootJVM]
exclude:
- scala: 2.12.17
java: temurin@17
- scala: 3.2.2
java: temurin@17
runs-on: ${{ matrix.os }}
steps:
- name: Checkout current branch (full)
Expand Down Expand Up @@ -202,6 +204,16 @@ jobs:
tar xf targets.tar
rm targets.tar
- name: Download target directories (3.2.2, rootJVM)
uses: actions/download-artifact@v3
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-3.2.2-rootJVM

- name: Inflate target directories (3.2.2, rootJVM)
run: |
tar xf targets.tar
rm targets.tar
- name: Import signing key
if: env.PGP_SECRET != '' && env.PGP_PASSPHRASE == ''
run: echo $PGP_SECRET | base64 -di | gpg --import
Expand Down
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
version = 3.7.1
runner.dialect = scala213
runner.dialect = scala3
continuationIndent.defnSite = 2
docstrings.style = Asterisk
includeCurlyBraceInSelectChains = false
Expand Down
8 changes: 5 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
ThisBuild / tlBaseVersion := "0.8"
ThisBuild / tlBaseVersion := "0.10"
ThisBuild / startYear := Some(2017)
ThisBuild / description := "Yet another Typesafe Config decoder"
ThisBuild / developers := List(
Developer("jonas", "Jonas Fonseca", "[email protected]", url("https://github.com/jonas"))
)
val scala212 = "2.12.17"
val scala213 = "2.13.10"
val scala3 = "3.2.2"
ThisBuild / scalaVersion := scala213
ThisBuild / crossScalaVersions := Seq(scala212, scala213)
ThisBuild / crossScalaVersions := Seq(scala212, scala213, scala3)
ThisBuild / circeRootOfCodeCoverage := Some("root")
ThisBuild / tlCiReleaseBranches := Seq("master")
ThisBuild / tlFatalWarningsInCi := false
Expand Down Expand Up @@ -56,6 +57,7 @@ lazy val config = project
doctestMarkdownEnabled := true,
tlVersionIntroduced := Map(
"2.12" -> "0.3.0",
"2.13" -> "0.7.0"
"2.13" -> "0.7.0",
"3" -> "0.10.0"
)
)
199 changes: 199 additions & 0 deletions config/src/test/scala-3/io.circe.config/CirceConfigSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
* Copyright 2017 circe
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/*
* Copyright 2017 Jonas Fonseca
*
* SPDX-License-Identifier: Apache-2.0
*
* Based on https://github.com/jonas/circe-config/blob/0.2.1/src/test/scala/io.github.jonas.circe.config/CirceConfigSpec.scala
*/

package io.circe.config

import cats.effect.IO
import com.typesafe.config.{parser => _, _}
import io.circe.config.syntax._
import io.circe.syntax._
import io.circe.{parser => _, _}
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers

import java.time.Period
import scala.concurrent.duration._
import scala.io.Source

class CirceConfigSpec extends AnyFlatSpec with Matchers {
import CirceConfigSpec._

trait ParserTests {
def parse: Either[ParsingFailure, Json]
def decode: Either[Error, TestConfig]

assert(parse.isRight)

val Right(config) = decode

assert(config == DecodedTestConfig)
assert(config.k.getDouble("ka") == 1.1)
assert(config.k.getString("kb") == "abc")
assert(config.l.unwrapped == "localhost")
}

"parser" should "parse and decode config from string" in new ParserTests {
def parse = parser.parse(AppConfigString)
def decode = parser.decode[TestConfig](AppConfigString)
}

it should "parse and decode config from object" in new ParserTests {
def parse = parser.parse(AppConfig)
def decode = parser.decode[TestConfig](AppConfig)
}

it should "parse and decode config from file" in new ParserTests {
def file = resolveFile("CirceConfigSpec.conf")
def parse = parser.parseFile(file)
def decode = parser.decodeFile[TestConfig](file)
}

it should "parse and decode config from default typesafe config resolution" in {
parser.decode[AppSettings]().fold(fail(_), _ should equal(DecodedAppSettings))
}

it should "parse and decode config from default typesafe config resolution via ApplicativeError" in {
parser.decodeF[IO, AppSettings]().unsafeRunSync() should equal(DecodedAppSettings)
}

it should "parse and decode config from default typesafe config resolution with path via ApplicativeError" in {
parser.decodePathF[IO, HttpSettings]("http").unsafeRunSync() should equal(DecodedAppSettings.http)
}

"printer" should "print it into a config string" in {
val Right(json) = parser.parse(AppConfig)
val expected = readFile("CirceConfigSpec.printed.conf")
assert(printer.print(json) == expected)
}

"syntax" should "provide Config decoder" in {
assert(AppConfig.as[TestConfig] == Right(DecodedTestConfig))
}

it should "provide syntax to decode at a given path" in {
assert(AppConfig.as[Nested]("e") == Right(Nested(true)))
}

it should "provide Config decoder via ApplicativeError" in {
assert(AppConfig.asF[IO, TestConfig].unsafeRunSync() == DecodedTestConfig)
}

it should "provide syntax to decode at a given path via ApplicativeError" in {
assert(AppConfig.asF[IO, Nested]("e").unsafeRunSync() == Nested(true))
}

"round-trip" should "parse and print" in {
for (file <- testResourcesDir.listFiles) {
val Right(json) = parser.parseFile(file)
assert(parser.parse(printer.print(json)) == Right(json), s"round-trip failed for ${file.getName}")
}
}
}

object CirceConfigSpec {
val testResourcesDir = new java.io.File("config/src/test/resources")
def resolveFile(name: String) = new java.io.File(testResourcesDir, name)
def readFile(path: String) = Source.fromFile(resolveFile(path)).getLines().mkString("\n")

val AppConfig: Config = ConfigFactory.parseResources("CirceConfigSpec.conf")
val AppConfigString: String = readFile("CirceConfigSpec.conf")

sealed abstract class Adder[T] {
def add(a: T, b: T): T
}
implicit def numericAdder[T: scala.math.Numeric]: Adder[T] = new Adder[T] {
override def add(a: T, b: T): T = implicitly[scala.math.Numeric[T]].plus(a, b)
}

case class TypeWithAdder[T: Adder](typeWithAdder: T)
case class Nested(obj: Boolean) derives Decoder
case class TestConfig(
a: Int,
b: Boolean,
c: String,
d: Option[String],
e: Nested,
f: List[Double],
g: List[List[String]],
h: List[Nested],
i: FiniteDuration,
j: ConfigMemorySize,
k: Config,
l: ConfigValue,
m: TypeWithAdder[Int],
n: Double,
o: Double,
p: Period
) derives Decoder

case class ServerSettings(
host: String,
port: Int,
timeout: FiniteDuration,
maxUpload: ConfigMemorySize
) derives Decoder
case class HttpSettings(
version: Double,
server: ServerSettings
) derives Decoder
case class AppSettings(http: HttpSettings) derives Decoder

val DecodedAppSettings = AppSettings(
HttpSettings(
1.1,
ServerSettings(
"localhost",
8080,
5.seconds,
ConfigMemorySize.ofBytes(5242880)
)
)
)

val DecodedTestConfig = TestConfig(
a = 42,
b = false,
c = "http://example.org",
d = None,
e = Nested(obj = true),
f = List(0, .2, 123.4),
g = List(List("nested", "list")),
h = List(Nested(obj = true), Nested(obj = false)),
i = 7357.seconds,
j = ConfigMemorySize.ofBytes(134217728),
k = ConfigFactory.parseString("ka = 1.1, kb = abc"),
l = ConfigValueFactory.fromAnyRef("localhost"),
m = TypeWithAdder(12),
n = 0.0,
o = 0,
p = Period.ofWeeks(4)
)

implicit def typeWithAdderDecoder[T: Adder](implicit adderDecoder: Decoder[T]): Decoder[TypeWithAdder[T]] = {
hCursor =>
for {
typeWithAdder <- hCursor.downField("typeWithAdder").as[T]
} yield TypeWithAdder(typeWithAdder)
}
}

0 comments on commit 734bb47

Please sign in to comment.