diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b92ca1..6220836 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,24 +15,24 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: fetch-depth: 0 - - name: Install JDK 11 - uses: actions/setup-java@v5 + - name: Install JDK 17 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 with: distribution: zulu - java-version: '11' + java-version: '17' java-package: jdk - name: Install SBT - uses: sbt/setup-sbt@v1 + uses: sbt/setup-sbt@66fb4376e81982c7d92a4074170846fff88e2e30 # v1.5.0 - name: Cache SBT - uses: coursier/cache-action@v8 + uses: coursier/cache-action@95e5b1029b6b86e7bac033ee44a0697d8a527d2d # v8.1.1 with: - extraKey: v1 + extraKey: v2 - name: Lint run: sbt --client "++${{ matrix.scala }} scalafmtCheckAll" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c3b7c20..9f522ec 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,22 +9,22 @@ jobs: name: Publish steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - - name: Install JDK 11 - uses: actions/setup-java@v5 + - name: Install JDK 17 + uses: actions/setup-java@1bcf9fb12cf4aa7d266a90ae39939e61372fe520 # v5.4.0 with: distribution: zulu - java-version: '11' + java-version: '17' java-package: jdk - name: Install SBT - uses: sbt/setup-sbt@v1 + uses: sbt/setup-sbt@66fb4376e81982c7d92a4074170846fff88e2e30 # v1.5.0 - name: Cache SBT - uses: coursier/cache-action@v8 + uses: coursier/cache-action@95e5b1029b6b86e7bac033ee44a0697d8a527d2d # v8.1.1 with: - extraKey: v1 + extraKey: v2 - name: Release run: sbt ci-release diff --git a/build.sbt b/build.sbt index 9f0b226..db54af7 100644 --- a/build.sbt +++ b/build.sbt @@ -1,21 +1,22 @@ // format: off +import sbt.util.CacheImplicits.given +import xsbti.HashedVirtualFileRef + val catsVersion = "2.13.0" val fs2Version = "3.13.0" val zioVersion = "2.1.19" val Scala213 = "2.13.18" -val Scala3 = "3.3.7" - -inThisBuild(Seq( - organization := "io.github.guymers", - homepage := Some(url("https://github.com/guymers/ceesvee")), - licenses := List(License.MIT), - developers := List( - Developer("guymers", "Sam Guymer", "@guymers", url("https://github.com/guymers")) - ), - scmInfo := Some(ScmInfo(url("https://github.com/guymers/ceesvee"), "git@github.com:guymers/ceesvee.git")), -)) +val Scala3 = "3.3.8" + +organization := "io.github.guymers" +homepage := Some(url("https://github.com/guymers/ceesvee")) +licenses := List(License.MIT) +developers := List( + Developer("guymers", "Sam Guymer", "@guymers", url("https://github.com/guymers")) +) +ThisBuild / scmInfo := Some(ScmInfo(url("https://github.com/guymers/ceesvee"), "git@github.com:guymers/ceesvee.git")) lazy val commonSettings = Seq( scalaVersion := Scala213, @@ -26,7 +27,7 @@ lazy val commonSettings = Seq( "-deprecation", "-encoding", "UTF-8", "-feature", - "-release", "11", + "-release", "17", "-unchecked", ), scalacOptions ++= (CrossVersion.partialVersion(scalaVersion.value) match { @@ -144,7 +145,7 @@ lazy val fs2 = module("fs2") "dev.zio" %% "zio-interop-cats" % "23.1.0.5" % Test, ), libraryDependencies ++= (CrossVersion.partialVersion(scalaVersion.value) match { - case Some((2, _)) => Seq(compilerPlugin("org.typelevel" % "kind-projector" % "0.13.4" cross CrossVersion.full)) + case Some((2, _)) => Seq(compilerPlugin(("org.typelevel" % "kind-projector" % "0.13.4").cross(CrossVersion.full))) case _ => Seq.empty }), ) @@ -173,60 +174,32 @@ lazy val benchmark = proj("benchmark", None) Global / excludeLintKeys += benchmark / Compile / compile / wartremoverErrors +lazy val testDataResourceDirectory = settingKey[File]("Directory for downloaded real-world test data files") +lazy val testDataResources = taskKey[Seq[HashedVirtualFileRef]]("Downloaded real-world test data files") -val TestCsvFiles = Map( - // https://www.stats.govt.nz/large-datasets/csv-files-for-download/ - "nz-greenhouse-gas-emissions-2019.csv" -> ( - "https://www.stats.govt.nz/assets/Uploads/Greenhouse-gas-emissions-industry-and-household/Greenhouse-gas-emissions-industry-and-household-Year-ended-2019/Download-data/Greenhouse-gas-emissions-industry-and-household-year-ended-2019-csv.csv", - "2561a652157c5eb8f23a7f84f3648440fe67ba8d", - ), - - // https://data.gov.uk/dataset/48c917d5-11a0-429f-a0db-0c5ae6ffa1c8/places-to-visit-in-causeway-coast-and-glens - "uk-causeway-coast-and-glens.csv" -> ( - "https://ccgbcodni-cbcni.opendata.arcgis.com/datasets/42b6ad70a304442dbdb963974d44b433_0.csv", - "5a15f2bf5861b34f985da88b33523f18aba10c08", - ), - - // https://www.gov.uk/government/statistical-data-sets/price-paid-data-downloads - "uk-property-sales-price-paid-2019.csv" -> ( - "http://prod.publicdata.landregistry.gov.uk.s3-website-eu-west-1.amazonaws.com/pp-2019.csv", - "7c9cf6b70599b8ad54365171e5343273a5a91b04", - ), -) - -val TestTsvFiles = Map( - // https://data.gov.au/data/dataset/fedora-pid_csiro-66416 - "cape-ivy-population-genomics.tsv" -> ( - "https://data.csiro.au/dap/ws/v2/collections/66416/data/86166655", - "07ad1b72deb4cf6251713704f294483f4ffc844a", - ) -) - -def downloadTestFile(log: Logger, file: File, _url: String, _hash: String): Unit = { - import scala.sys.process.* - - val currentHash = FileInfo.hash(file) - val expected = FileInfo.hash(file, _hash.sliding(2, 2).map(Integer.parseInt(_, 16).toByte).toArray) - - if (currentHash != expected) { - file.getParentFile.mkdirs() - file.createNewFile() - - log.info(s"Downloading test file: ${file.name}") +ThisBuild / testDataResourceDirectory := rootOutputDirectory.value.resolve("test-data-resources").toFile - url(_url) #> file ! +ThisBuild / testDataResources := Def.cachedTask { + val converter = fileConverter.value + val s = streams.value - log.info(s"Downloaded test file: ${file.name}") - - val hash = FileInfo.hash(file) - if (hash != expected) { - throw new IllegalStateException(s"Test file ${file.name} does not match expected hash") - } + val dir = (ThisBuild / testDataResourceDirectory).value + val files = TestFiles.Csv.toSeq.map { case (f, (url, hash)) => + val file = dir / "csv" / f + TestFiles.download(s.log, file, url, hash) + file + } ++ TestFiles.Tsv.map { case (f, (url, hash)) => + val file = dir / "tsv" / f + TestFiles.download(s.log, file, url, hash) + file } -} -// only want to download the files once no matter how many cross Scala versions there are -def _testFilesDir(base: File) = base / "target" / "scala" / "resource_managed" / "test" + files.map { file => + val vf = converter.toVirtualFile(file.toPath) + Def.declareOutput(vf) + vf: HashedVirtualFileRef + } +}.value lazy val tests = proj("tests", None) .settings(publish / skip := true) @@ -236,20 +209,15 @@ lazy val tests = proj("tests", None) "co.fs2" %% "fs2-io" % fs2Version % Test, ), Test / resourceGenerators += Def.task { - val s = streams.value - - val dir = _testFilesDir((Test / baseDirectory).value) - TestCsvFiles.toSeq.map { case (f, (url, hash)) => - val file = dir / "csv" / f - downloadTestFile(s.log, file, url, hash) - file - } ++ TestTsvFiles.map { case (f, (url, hash)) => - val file = dir / "tsv" / f - downloadTestFile(s.log, file, url, hash) - file - } + val converter = fileConverter.value + (ThisBuild / testDataResources).value.map(vf => converter.toPath(vf).toFile) }.taskValue, - Test / managedResourceDirectories += _testFilesDir((Test / baseDirectory).value), + Test / managedResourceDirectories := Seq((ThisBuild / testDataResourceDirectory).value), ) .dependsOn(core, fs2, zio) .disablePlugins(MimaPlugin) + +Global / excludeLintKeys ++= Set( + com.github.sbt.git.SbtGit.GitKeys.gitDescribedVersion, + com.github.sbt.git.SbtGit.GitKeys.gitUncommittedChanges, +) diff --git a/project/TestFiles.scala b/project/TestFiles.scala new file mode 100644 index 0000000..d9d13dd --- /dev/null +++ b/project/TestFiles.scala @@ -0,0 +1,55 @@ +import sbt.* + +object TestFiles { + + val Csv = Map( + // https://www.stats.govt.nz/large-datasets/csv-files-for-download/ + "nz-greenhouse-gas-emissions-2019.csv" -> ( + "https://www.stats.govt.nz/assets/Uploads/Greenhouse-gas-emissions-industry-and-household/Greenhouse-gas-emissions-industry-and-household-Year-ended-2019/Download-data/Greenhouse-gas-emissions-industry-and-household-year-ended-2019-csv.csv", + "2561a652157c5eb8f23a7f84f3648440fe67ba8d", + ), + + // https://data.gov.uk/dataset/48c917d5-11a0-429f-a0db-0c5ae6ffa1c8/places-to-visit-in-causeway-coast-and-glens + "uk-causeway-coast-and-glens.csv" -> ( + "https://ccgbcodni-cbcni.opendata.arcgis.com/datasets/42b6ad70a304442dbdb963974d44b433_0.csv", + "5a15f2bf5861b34f985da88b33523f18aba10c08", + ), + + // https://www.gov.uk/government/statistical-data-sets/price-paid-data-downloads + "uk-property-sales-price-paid-2019.csv" -> ( + "http://prod.publicdata.landregistry.gov.uk.s3-website-eu-west-1.amazonaws.com/pp-2019.csv", + "406fe6d20ea0ef4e21b693ac961650121e508e5e", + ), + ) + + val Tsv = Map( + // https://github.com/schappim/australian-postcodes + "australian-postcodes.tsv" -> ( + "https://raw.githubusercontent.com/schappim/australian-postcodes/refs/tags/v2026.5.27/australian-postcodes-2021-04-23.tsv", + "5a4f6888e5f5cd9b3e632b6ed63756a081e26ec1", + ), + ) + + def download(log: Logger, file: File, _url: String, _hash: String): Unit = { + val currentHash = FileInfo.hash(file) + val expected = FileInfo.hash(file, _hash.sliding(2, 2).map(Integer.parseInt(_, 16).toByte).toArray) + + if (currentHash != expected) { + file.getParentFile.mkdirs() + file.createNewFile() + + log.info(s"Downloading test file: ${file.name}") + + sbt.io.Using.urlInputStream(url(_url).toURL) { in => + sbt.io.IO.transfer(in, file) + } + + log.info(s"Downloaded test file: ${file.name}") + + val hash = FileInfo.hash(file) + if (hash != expected) { + throw new IllegalStateException(s"Test file ${file.name} does not match expected hash") + } + } + } +} diff --git a/project/build.properties b/project/build.properties index 2106fb4..312809a 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.12.6 +sbt.version=2.0.1 diff --git a/project/plugins.sbt b/project/plugins.sbt index 2ce2e9a..6152dc1 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,9 +1,9 @@ addSbtPlugin("pl.project13.scala" % "sbt-jmh" % "0.4.8") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.5.6") -addSbtPlugin("org.wartremover" % "sbt-wartremover" % "3.5.6") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.6.1") +addSbtPlugin("org.wartremover" % "sbt-wartremover" % "3.6.1") -addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.5") +addSbtPlugin("com.typesafe" % "sbt-mima-plugin" % "1.1.6") addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.11.2") -//addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4") +addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.7.0") diff --git a/project/project/plugins.sbt b/project/project/plugins.sbt index 83e0d9c..6573bbd 100644 --- a/project/project/plugins.sbt +++ b/project/project/plugins.sbt @@ -1,2 +1,2 @@ // reload plugins; dependencyUpdates -//addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.6.4") +//addSbtPlugin("com.timushev.sbt" % "sbt-updates" % "0.7.0") diff --git a/tests/src/test/scala/ceesvee/tests/RealWorldCsvSpec.scala b/tests/src/test/scala/ceesvee/tests/RealWorldCsvSpec.scala index f500427..40ee3d2 100644 --- a/tests/src/test/scala/ceesvee/tests/RealWorldCsvSpec.scala +++ b/tests/src/test/scala/ceesvee/tests/RealWorldCsvSpec.scala @@ -18,7 +18,6 @@ import zio.test.ZIOSpecDefault import zio.test.assertTrue import java.net.URI -import java.nio.file.Paths object RealWorldCsvSpec extends ZIOSpecDefault { import RealWorldFileHelper.* @@ -27,7 +26,7 @@ object RealWorldCsvSpec extends ZIOSpecDefault { override val spec = suite("RealWorldCsv")( suite("UK Causeway Coast")({ - val path = Paths.get(getClass.getResource("/csv/uk-causeway-coast-and-glens.csv").getPath) + val resource = "csv/uk-causeway-coast-and-glens.csv" val expected = UkCausewayCoast( x = BigDecimal("677156.193266336"), @@ -46,7 +45,7 @@ object RealWorldCsvSpec extends ZIOSpecDefault { List( test("scala") { - readFile(path) { input => + readResource(resource) { input => val result = CsvReader.decodeWithHeader(input, UkCausewayCoast.csvHeader, options) result match { case l @ Left(_) => assertTrue(l.isRight) @@ -55,7 +54,7 @@ object RealWorldCsvSpec extends ZIOSpecDefault { } }, test("fs2") { - val io = readFileFs2(path).through { + val io = readResourceFs2(resource).through { Fs2CsvReader.decodeWithHeader(UkCausewayCoast.csvHeader, options) }.compile.toList catsIoToZio(io).map { result => @@ -63,7 +62,7 @@ object RealWorldCsvSpec extends ZIOSpecDefault { } }, test("zio") { - val stream = readFileZio(path) + val stream = readResourceZio(resource) ZioCsvReader.decodeWithHeader(stream, UkCausewayCoast.csvHeader, options) .runCollect .map { result => @@ -77,23 +76,23 @@ object RealWorldCsvSpec extends ZIOSpecDefault { assertHeaderTotal("nz-greenhouse-gas-emissions-2019.csv", NZGreenhouseGasEmissions.csvHeader, total) }*), suite("UK property sales 2019")({ - val total = 1011675L + val total = 1012107L assertTotal("uk-property-sales-price-paid-2019.csv", UkPropertySalesPricePaid.decoder, total) }*), ) @@ TestAspect.timeout(60.seconds) @@ TestAspect.timed private def assertTotal[T](fileName: String, decoder: CsvRecordDecoder[T], total: Long) = { - val path = Paths.get(getClass.getResource(s"/csv/$fileName").getPath) + val resource = s"csv/$fileName" List( test("scala") { - val result = readFile(path) { input => + val result = readResource(resource) { input => CsvReader.decode(input, options)(decoder).count(_.isRight).toLong } assertTrue(result == total) }, test("fs2") { - val io = readFileFs2(path).through { + val io = readResourceFs2(resource).through { Fs2CsvReader.decode[IO, T](options)(implicitly, decoder) }.collect { case Right(v) => v }.compile.count catsIoToZio(io).map { count => @@ -104,7 +103,7 @@ object RealWorldCsvSpec extends ZIOSpecDefault { val pipeline = ZioCsvReader.decode(options)(decoder, implicitly).mapError { case e: CsvParser.Error.LineTooLong => e }.andThen(ZPipeline.mapZIO(ZIO.fromEither(_))) - readFileZio(path).via(pipeline).runCount.map { count => + readResourceZio(resource).via(pipeline).runCount.map { count => assertTrue(count == total) } }, @@ -112,18 +111,18 @@ object RealWorldCsvSpec extends ZIOSpecDefault { } private def assertHeaderTotal[T](fileName: String, header: CsvHeader[T], total: Long) = { - val path = Paths.get(getClass.getResource(s"/csv/$fileName").getPath) + val resource = s"csv/$fileName" List( test("scala") { - val result = readFile(path) { input => + val result = readResource(resource) { input => CsvReader.decodeWithHeader(input, header, options) .map(_.count(_.isRight).toLong) } assertTrue(result == Right(total)) }, test("fs2") { - val io = readFileFs2(path).through { + val io = readResourceFs2(resource).through { Fs2CsvReader.decodeWithHeader(header, options) }.collect { case Right(v) => v }.compile.count catsIoToZio(io).map { count => @@ -131,7 +130,7 @@ object RealWorldCsvSpec extends ZIOSpecDefault { } }, test("zio") { - val stream = readFileZio(path) + val stream = readResourceZio(resource) ZioCsvReader.decodeWithHeader(stream, header, options) .collectRight .runCount diff --git a/tests/src/test/scala/ceesvee/tests/RealWorldFileHelper.scala b/tests/src/test/scala/ceesvee/tests/RealWorldFileHelper.scala index cd7f4e1..0e86e2e 100644 --- a/tests/src/test/scala/ceesvee/tests/RealWorldFileHelper.scala +++ b/tests/src/test/scala/ceesvee/tests/RealWorldFileHelper.scala @@ -1,23 +1,30 @@ package ceesvee.tests import cats.effect.IO +import cats.syntax.show.* +import zio.ZIO import zio.stream.ZPipeline import zio.stream.ZStream +import java.io.FileNotFoundException import java.io.InputStream import java.nio.charset.Charset import java.nio.charset.StandardCharsets -import java.nio.file.Files -import java.nio.file.Path +import scala.util.Using object RealWorldFileHelper { - def readFile[T](path: Path)(f: Iterator[String] => T) = { - val is = Files.newInputStream(path) - try { + private val ChunkSize = 16 * 1024 + + private def resourceStream(resource: String) = { + val classLoader = Thread.currentThread().getContextClassLoader + Option(classLoader.getResourceAsStream(resource)) + .getOrElse(throw new FileNotFoundException(show"Resource not found: $resource")) + } + + def readResource[T](resource: String)(f: Iterator[String] => T) = { + Using.resource(resourceStream(resource)) { is => f(strings(is, StandardCharsets.UTF_8)) - } finally { - is.close() } } @@ -51,11 +58,12 @@ object RealWorldFileHelper { } } - def readFileFs2(path: Path) = { - fs2.io.file.Files[IO].readAll(fs2.io.file.Path.fromNioPath(path)).through(fs2.text.utf8.decode) + def readResourceFs2(resource: String) = { + fs2.io.readClassLoaderResource[IO](resource, chunkSize = ChunkSize).through(fs2.text.utf8.decode) } - def readFileZio(path: Path) = { - ZStream.fromPath(path, chunkSize = 16384) >>> ZPipeline.utfDecode + def readResourceZio(resource: String) = { + ZStream.fromInputStreamZIO(ZIO.attemptBlockingIO(resourceStream(resource)), chunkSize = ChunkSize) >>> + ZPipeline.utfDecode } } diff --git a/tests/src/test/scala/ceesvee/tests/RealWorldTsvSpec.scala b/tests/src/test/scala/ceesvee/tests/RealWorldTsvSpec.scala index 2057e6f..e912689 100644 --- a/tests/src/test/scala/ceesvee/tests/RealWorldTsvSpec.scala +++ b/tests/src/test/scala/ceesvee/tests/RealWorldTsvSpec.scala @@ -4,15 +4,13 @@ import ceesvee.CsvHeader import ceesvee.CsvParser import ceesvee.CsvReader import ceesvee.fs2.Fs2CsvReader -import ceesvee.tests.model.CapeIvyPopulationGenomics +import ceesvee.tests.model.AustralianPostcodes import ceesvee.zio.ZioCsvReader import zio.durationInt import zio.test.TestAspect import zio.test.ZIOSpecDefault import zio.test.assertTrue -import java.nio.file.Paths - object RealWorldTsvSpec extends ZIOSpecDefault { import RealWorldFileHelper.* @@ -21,26 +19,24 @@ object RealWorldTsvSpec extends ZIOSpecDefault { ) override val spec = suite("RealWorldTsv")( - suite("Population genomics of Cape ivy")({ - val path = Paths.get(getClass.getResource("/tsv/cape-ivy-population-genomics.tsv").getPath) + suite("Australian postcodes")({ + val resource = "tsv/australian-postcodes.tsv" - val expected = CapeIvyPopulationGenomics( - id = "1.18", - pop = "Penola", - state = "South_Australia", - country = "Australia", - stipulate = "Unknown", + val expected = AustralianPostcodes( + suburb = "PADDINGTON", + state = "NSW", + postcode = 2021, ) - def assertResult(result: Seq[Either[CsvHeader.Errors, CapeIvyPopulationGenomics]]) = { - assertTrue(result.count(_.isRight) == 1089) && - assertTrue(result.apply(8) == Right(expected)) + def assertResult(result: Seq[Either[CsvHeader.Errors, AustralianPostcodes]]) = { + assertTrue(result.count(_.isRight) == 16753) && + assertTrue(result.apply(11040) == Right(expected)) } List( test("scala") { - readFile(path) { input => - val result = CsvReader.decodeWithHeader(input, CapeIvyPopulationGenomics.csvHeader, options) + readResource(resource) { input => + val result = CsvReader.decodeWithHeader(input, AustralianPostcodes.csvHeader, options) result match { case l @ Left(_) => assertTrue(l.isRight) case Right(result) => assertResult(result.toSeq) @@ -48,16 +44,16 @@ object RealWorldTsvSpec extends ZIOSpecDefault { } }, test("fs2") { - val io = readFileFs2(path).through { - Fs2CsvReader.decodeWithHeader(CapeIvyPopulationGenomics.csvHeader, options) + val io = readResourceFs2(resource).through { + Fs2CsvReader.decodeWithHeader(AustralianPostcodes.csvHeader, options) }.compile.toList catsIoToZio(io).map { result => assertResult(result) } }, test("zio") { - val stream = readFileZio(path) - ZioCsvReader.decodeWithHeader(stream, CapeIvyPopulationGenomics.csvHeader, options) + val stream = readResourceZio(resource) + ZioCsvReader.decodeWithHeader(stream, AustralianPostcodes.csvHeader, options) .runCollect .map { result => assertResult(result) diff --git a/tests/src/test/scala/ceesvee/tests/model/AustralianPostcodes.scala b/tests/src/test/scala/ceesvee/tests/model/AustralianPostcodes.scala new file mode 100644 index 0000000..fe07c11 --- /dev/null +++ b/tests/src/test/scala/ceesvee/tests/model/AustralianPostcodes.scala @@ -0,0 +1,18 @@ +package ceesvee.tests.model + +import ceesvee.CsvHeader +import ceesvee.CsvRecordDecoder +import zio.NonEmptyChunk + +case class AustralianPostcodes( + suburb: String, + state: String, + postcode: Int, +) +object AustralianPostcodes { + + val header = NonEmptyChunk("Suburb", "State", "Zip") + + implicit val decoder: CsvRecordDecoder[AustralianPostcodes] = CsvRecordDecoder.derived + val csvHeader = CsvHeader.create(header.toCons)(decoder) +} diff --git a/tests/src/test/scala/ceesvee/tests/model/CapeIvyPopulationGenomics.scala b/tests/src/test/scala/ceesvee/tests/model/CapeIvyPopulationGenomics.scala deleted file mode 100644 index f24fdb8..0000000 --- a/tests/src/test/scala/ceesvee/tests/model/CapeIvyPopulationGenomics.scala +++ /dev/null @@ -1,20 +0,0 @@ -package ceesvee.tests.model - -import ceesvee.CsvHeader -import ceesvee.CsvRecordDecoder -import zio.NonEmptyChunk - -case class CapeIvyPopulationGenomics( - id: String, - pop: String, - state: String, - country: String, - stipulate: String, -) -object CapeIvyPopulationGenomics { - - val header = NonEmptyChunk("id", "pop", "state", "country", "stipulate") - - implicit val decoder: CsvRecordDecoder[CapeIvyPopulationGenomics] = CsvRecordDecoder.derived - val csvHeader = CsvHeader.create(header.toCons)(decoder) -}