diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1f90b49..b65dbc3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - scala-version: [2.13.x] + scala-version: [2.13.x, 3.3.x] java-version: [8, 11, 17, 21] steps: - uses: actions/checkout@v4 @@ -18,6 +18,6 @@ jobs: distribution: temurin java-version: ${{ matrix.java-version }} - name: Run tests - run: sbt ++${{ matrix.scala-version }} clean versionPolicyCheck coverage test coverageReport + run: sbt ++${{ matrix.scala-version }} clean coverage test coverageReport - name: Upload coverage report run: bash <(curl -s https://codecov.io/bash) diff --git a/.scalafmt.conf b/.scalafmt.conf index 7a60977..b79a3b6 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -10,4 +10,4 @@ rewriteTokens = { "→": "->" "←": "<-" } -runner.dialect = scala213 +runner.dialect = scala213source3 diff --git a/build.sbt b/build.sbt index f650513..c3e0e33 100644 --- a/build.sbt +++ b/build.sbt @@ -13,8 +13,8 @@ developers := List( ) ) -crossScalaVersions := List("2.13.15") -scalaVersion := crossScalaVersions.value.last +scalaVersion := "2.13.15" +crossScalaVersions += "3.3.4" ThisBuild / versionScheme := Some("semver-spec") ThisBuild / versionPolicyIntention := Compatibility.BinaryCompatible @@ -23,10 +23,28 @@ Compile / packageBin / packageOptions += Package.ManifestAttributes( "Automatic-Module-Name" -> "nl.gn0s1s.pureconfig.module.javanet" ) -scalacOptions += "-deprecation" +Test / unmanagedSourceDirectories ++= { + (Test / unmanagedSourceDirectories).value.map { dir => + CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, 13)) => file(dir.getPath ++ "-2.13") + case _ => file(dir.getPath ++ "-3+") + } + } +} + +scalacOptions ++= (CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, 13)) => Seq("-Xsource:3", "-deprecation") + case _ => Seq("-deprecation") +}) + +val pureConfigVersion = "0.17.7" + +libraryDependencies += (CrossVersion.partialVersion(scalaVersion.value) match { + case Some((2, 13)) => "com.github.pureconfig" %% "pureconfig" % pureConfigVersion % Provided + case _ => "com.github.pureconfig" %% "pureconfig-core" % pureConfigVersion % Provided +}) libraryDependencies ++= Seq( - "com.github.pureconfig" %% "pureconfig" % "0.17.7" % Provided, - "commons-validator" % "commons-validator" % "1.9.0", - "org.scalameta" %% "munit" % "1.0.2" % Test + "commons-validator" % "commons-validator" % "1.9.0", + "org.scalameta" %% "munit" % "1.0.2" % Test ) diff --git a/src/test/scala/nl/gn0s1s/pureconfig/module/javanet/JavanetSuite.scala b/src/test/scala-2.13/nl/gn0s1s/pureconfig/module/javanet/JavanetSuite.scala similarity index 98% rename from src/test/scala/nl/gn0s1s/pureconfig/module/javanet/JavanetSuite.scala rename to src/test/scala-2.13/nl/gn0s1s/pureconfig/module/javanet/JavanetSuite.scala index 915e4e4..766b4fc 100644 --- a/src/test/scala/nl/gn0s1s/pureconfig/module/javanet/JavanetSuite.scala +++ b/src/test/scala-2.13/nl/gn0s1s/pureconfig/module/javanet/JavanetSuite.scala @@ -4,10 +4,10 @@ import java.net.InetSocketAddress import com.typesafe.config.ConfigFactory.parseString import com.typesafe.config.ConfigOriginFactory -import pureconfig._ +import pureconfig.* import pureconfig.error.{CannotConvert, ConfigReaderFailures, ConvertFailure} -import pureconfig.generic.auto._ -import pureconfig.syntax._ +import pureconfig.generic.auto.* +import pureconfig.syntax.* class JavanetSuite extends munit.FunSuite { private val expectedConfigOrigin = diff --git a/src/test/scala-3+/nl/gn0s1s/pureconfig/module/javanet/JavanetSuite.scala b/src/test/scala-3+/nl/gn0s1s/pureconfig/module/javanet/JavanetSuite.scala new file mode 100644 index 0000000..a35e8df --- /dev/null +++ b/src/test/scala-3+/nl/gn0s1s/pureconfig/module/javanet/JavanetSuite.scala @@ -0,0 +1,251 @@ +package nl.gn0s1s.pureconfig.module.javanet + +import java.net.InetSocketAddress + +import com.typesafe.config.ConfigFactory.parseString +import com.typesafe.config.ConfigOriginFactory +import pureconfig.* +import pureconfig.error.{CannotConvert, ConfigReaderFailures, ConvertFailure} +import pureconfig.generic.derivation.default.* +import pureconfig.syntax.* + +class JavanetSuite extends munit.FunSuite { + private val expectedConfigOrigin = + Some(ConfigOriginFactory.newSimple("String").withLineNumber(1)) + + test("can read a valid ipv4 address") { + case class Config(host: InetSocketAddress) derives ConfigReader + + val conf = parseString(""""host": "127.0.0.1:65535"""") + + assert(conf.to[Config].contains(Config(InetSocketAddress.createUnresolved("127.0.0.1", 65535)))) + } + + test("can read a valid single named address") { + case class Config(host: InetSocketAddress) derives ConfigReader + + val conf = parseString(""""host": "abc.def.ghi:65535"""") + + assert(conf.to[Config].contains(Config(InetSocketAddress.createUnresolved("abc.def.ghi", 65535)))) + } + + test("can read a valid ipv6 address") { + case class Config(host: InetSocketAddress) derives ConfigReader + + val conf = parseString(""""host": "2001:db8::1:80"""") + + assert(conf.to[Config].contains(Config(InetSocketAddress.createUnresolved("2001:db8::1", 80)))) + } + + test("can read a valid ipv6 address in brackets") { + case class Config(host: InetSocketAddress) derives ConfigReader + + val conf = parseString(""""host": "[2001:db8::1]:80"""") + + assert(conf.to[Config].contains(Config(InetSocketAddress.createUnresolved("2001:db8::1", 80)))) + } + + test("validates if a host is defined") { + case class Config(host: InetSocketAddress) derives ConfigReader + + val conf = parseString(""""host": ":80"""") + + val expectedErrors = Left( + ConfigReaderFailures( + ConvertFailure(CannotConvert(":80", "InetSocketAddress", "no host defined"), expectedConfigOrigin, "host") + ) + ) + + assert(conf.to[Config] == expectedErrors) + } + + test("validates if a port is defined") { + case class Config(host: InetSocketAddress) derives ConfigReader + + val conf = parseString(""""host": "localhost123"""") + + val expectedErrors = Left( + ConfigReaderFailures( + ConvertFailure( + CannotConvert("localhost123", "InetSocketAddress", "no port defined"), + expectedConfigOrigin, + "host" + ) + ) + ) + + assert(conf.to[Config] == expectedErrors) + } + + test("validates if a port is defined when a colon is present") { + case class Config(host: InetSocketAddress) derives ConfigReader + + val conf = parseString(""""host": "abc:"""") + + val expectedErrors = Left( + ConfigReaderFailures( + ConvertFailure(CannotConvert("abc:", "InetSocketAddress", "no port defined"), expectedConfigOrigin, "host") + ) + ) + + assert(conf.to[Config] == expectedErrors) + } + + test("validates if a port is a number") { + case class Config(host: InetSocketAddress) derives ConfigReader + + val conf = parseString(""""host": "abc:def"""") + + val expectedErrors = Left( + ConfigReaderFailures( + ConvertFailure( + CannotConvert("abc:def", "InetSocketAddress", "port is not a number:def"), + expectedConfigOrigin, + "host" + ) + ) + ) + + assert(conf.to[Config] == expectedErrors) + } + + test("validates if a port is within range") { + case class Config(host: InetSocketAddress) derives ConfigReader + + val conf = parseString(""""host": "abc:65536"""") + + val expectedErrors = Left( + ConfigReaderFailures( + ConvertFailure( + CannotConvert("abc:65536", "InetSocketAddress", "port out of range:65536"), + expectedConfigOrigin, + "host" + ) + ) + ) + + assert(conf.to[Config] == expectedErrors) + } + + test("can read back a written address") { + val address = InetSocketAddress.createUnresolved("localhost", 65535) + + assert(ConfigReader[InetSocketAddress].from(ConfigWriter[InetSocketAddress].to(address)).contains(address)) + } + + test("can read multiple addresses") { + case class Config(hosts: Seq[InetSocketAddress]) derives ConfigReader + + val conf = parseString("""hosts: "localhost:65535,127.0.0.1:80,localhost:443"""") + + assert( + conf + .to[Config] + .contains( + Config( + Seq( + InetSocketAddress.createUnresolved("localhost", 65535), + InetSocketAddress.createUnresolved("127.0.0.1", 80), + InetSocketAddress.createUnresolved("localhost", 443) + ) + ) + ) + ) + } + + test("can read multiple addresses as a list") { + case class Config(hosts: List[InetSocketAddress]) derives ConfigReader + + val conf = parseString("""hosts: "localhost:65535,127.0.0.1:80,localhost:443"""") + + assert( + conf + .to[Config] + .contains( + Config( + List( + InetSocketAddress.createUnresolved("localhost", 65535), + InetSocketAddress.createUnresolved("127.0.0.1", 80), + InetSocketAddress.createUnresolved("localhost", 443) + ) + ) + ) + ) + } + + test("can read a single address as multiple addresses") { + case class Config(hosts: Seq[InetSocketAddress]) derives ConfigReader + + val conf = parseString("""hosts: "localhost:65535"""") + + assert(conf.to[Config].contains(Config(Seq(InetSocketAddress.createUnresolved("localhost", 65535))))) + } + + test("is lenient about whitespace") { + case class Config(hosts: Seq[InetSocketAddress]) derives ConfigReader + + val conf = parseString("""hosts: "localhost: 65535,127.0.0.1: 80,localhost: 443"""") + + assert( + conf + .to[Config] + .contains( + Config( + Seq( + InetSocketAddress.createUnresolved("localhost", 65535), + InetSocketAddress.createUnresolved("127.0.0.1", 80), + InetSocketAddress.createUnresolved("localhost", 443) + ) + ) + ) + ) + } + + test("can read back a written Seq[InetSocketAddress]") { + val addresses = Seq( + InetSocketAddress.createUnresolved("localhost", 65535), + InetSocketAddress.createUnresolved("127.0.0.1", 80), + InetSocketAddress.createUnresolved("localhost", 443) + ) + + assert( + ConfigReader[Seq[InetSocketAddress]].from(ConfigWriter[Seq[InetSocketAddress]].to(addresses)).contains(addresses) + ) + } + + test("can read back a written List[InetSocketAddress]") { + val addresses = List( + InetSocketAddress.createUnresolved("localhost", 65535), + InetSocketAddress.createUnresolved("127.0.0.1", 80), + InetSocketAddress.createUnresolved("localhost", 443) + ) + + assert( + ConfigReader[List[InetSocketAddress]].from(ConfigWriter[List[InetSocketAddress]].to(addresses)).contains( + addresses + ) + ) + } + + test("validates the supplied setting") { + case class Config(hosts: Seq[InetSocketAddress]) derives ConfigReader + + val conf = parseString("""hosts: "localhost:65535 + localhost:80"""") + + val expectedErrors = Left( + ConfigReaderFailures( + ConvertFailure( + CannotConvert( + "localhost:65535 + localhost:80", + "Seq[InetSocketAddress]", + "Cannot parse string into hosts and ports" + ), + expectedConfigOrigin, + "hosts" + ) + ) + ) + + assert(conf.to[Config] == expectedErrors) + } +}