Refactor save return type without Either

This commit is contained in:
Guntis Smaukstelis
2023-06-22 13:59:44 +03:00
parent 1abb12d92a
commit 3e1635d494
8 changed files with 84 additions and 21 deletions
+38
View File
@@ -0,0 +1,38 @@
# Weather Tool
Tool for downloading historical and real-time data from 33 weather stations in Latvia. It aggregates and displays data for different time periods, cities, and weather parameters.
## Run
```
// scala
sbt run
// web
cd web/
npm run dev
```
**Author:** Guntis Smaukstelis
**Mentor:** Jānis Komuls
**Status:** Live
**URL:** [https://weather-tool.fly.dev/](https://weather-tool.fly.dev/)
**Used By:** State television weather news
**Repository:** [https://github.com/guntisdev/WeatherTool](https://github.com/guntisdev/WeatherTool)
## Backend Technologies:
- cats-effects
- http4s
- pureconfig
- fs2
- circe
- scalatest
## Frontend Technologies:
- SolidJS
- Vite
## Deployment:
- Docker
- Shell script
- Fly.io
+4
View File
@@ -9,13 +9,17 @@ import server.Server
object Main extends IOApp {
def run(args: List[String]): IO[ExitCode] = {
for {
// TODO run in separate thread
dbService <- DBService.of
dataService <- DataService.of(dbService)
fetch <- FetchService.of
fileFetchScheduler <- FileFetchScheduler.of(dataService, fetch)
schedulerTask = fileFetchScheduler.run.compile.drain
server <- Server.of(dataService, fetch)
serverTask = server.run
exitCode <- (serverTask, schedulerTask).parMapN((_, _) => ExitCode.Success)
} yield exitCode
}
+6 -6
View File
@@ -40,14 +40,14 @@ class DBService(log: Logger[IO]) extends DataServiceTrait {
}
}
def save(fileName: String, content: String): IO[Either[Throwable, String]] = {
def save(fileName: String, content: String): IO[String] = {
val path = Paths.get(s"$dataPath/$fileName")
IO(Files.writeString(path, content))
.redeemWith(
error => IO(Left(error)) // <* log.error(s"Write file '$fileName' failed with error: ${error.getMessage}")
,
_ => IO(Right(fileName)) // <* log.info(s"write: $fileName")
)
.attempt
.flatMap {
case Left(error) => log.error(s"Write file '$fileName' failed with error: ${error.getMessage}") *> IO.raiseError(error)
case Right(_) => log.info(s"write: $fileName").as(fileName)
}
}
def readFile(fileName: String): IO[List[String]] = {
+9 -8
View File
@@ -9,7 +9,7 @@ import org.typelevel.log4cats.slf4j.Slf4jLogger
import java.time.{Instant, LocalDate, LocalDateTime, ZoneId}
trait DataServiceTrait {
def save(fileName: String, content: String): IO[Either[Throwable, String]]
def save(fileName: String, content: String): IO[String]
def readFile(fileName: String): IO[List[String]]
def getInRange(from: LocalDateTime, to: LocalDateTime): IO[List[String]]
def getDates: IO[List[LocalDate]]
@@ -46,14 +46,15 @@ class DataService private(
}
}
def save(fileName: String, content: String): IO[Either[Throwable, String]] = {
dbService.save(fileName, content).flatMap {
case Right(savedFileName) =>
def save(fileName: String, content: String): IO[String] = {
dbService.save(fileName, content).redeemWith(
error => IO.raiseError(error),
savedFileName => {
state.update(st => st.updated(savedFileName, content.split("\n").toList)) *>
filterState *>
logState.as(Right(savedFileName))
case e@Left(_) => IO.pure(e)
}
filterState *>
logState.as(savedFileName)
}
).onError(error => log.info(s"errr... $error"))
}
def readFile(fileName: String): IO[List[String]] = dbService.readFile(fileName)
@@ -27,10 +27,7 @@ class FileFetchScheduler(dataService: DataServiceTrait, fetch: FetchService, fil
case Right((name, content)) =>
dataService.save(name, content).attempt.flatMap {
case Left(err) => log.error(s"error: $err")
case Right(saveResult) => saveResult match {
case Left(err) => log.error(s"error: $err")
case Right(savedName) => log.info(s"saved: $savedName")
}
case Right(savedName) => log.info(s"saved: $savedName")
}
}
}
+1 -1
View File
@@ -59,7 +59,7 @@ class Server(dataService: DataService, fetch: FetchService, log: Logger[IO]) {
fetchServiceError = fetchResultEither.left.toOption.map(e => s"FetchServiceError: ${e.getMessage}").toList
fetchResult = fetchResultEither.getOrElse(List.empty)
(fetchErrors, successDownloads) = fetchResult.partitionMap(identity)
saveResults <- successDownloads.traverse { case (name, content) => dataService.save(name, content) }
saveResults <- successDownloads.traverse { case (name, content) => dataService.save(name, content).attempt }
(saveErrors, successSaves) = saveResults.partitionMap(identity)
// successes = successDownloads.map(s => s"fetched: ${s._1}") ++ successSaves.map(s => s"saved: $s")
successes = successSaves
+23
View File
@@ -4,6 +4,7 @@ import cats.effect.unsafe.implicits.global
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.matchers.should.Matchers
import java.io.IOException
import java.time.format.DateTimeFormatter
import java.time.{LocalDate, LocalDateTime}
@@ -32,4 +33,26 @@ class DBServiceSpec extends AnyFunSuite with Matchers {
lines.length shouldBe 2142
}
test("DBService.save should return correct result") {
val dbService = DBService.of.unsafeRunSync()
val fileName = "testFile.txt"
val fileContent = "test content..."
dbService.save(fileName, fileContent).unsafeRunSync() shouldEqual fileName
}
test("DBService.save returns error on invalid file name") {
val dbService = DBService.of.unsafeRunSync()
val fileName = "/invalid/file/name"
val fileContent = "test content..."
val result = dbService.save(fileName, fileContent).attempt.unsafeRunSync()
result match {
case Left(e) =>
assert(e.isInstanceOf[IOException])
case Right(_) => fail("Expected failure did not occur")
}
}
}
@@ -20,8 +20,8 @@ class FileFetchSchedulerSpec extends AsyncWordSpec with Matchers with IOSuite {
refScheduler <- Ref.of[IO, Option[Either[Throwable, (String, String)]]](None)
log <- Slf4jLogger.create[IO]
dbService = new DBService(log) {
override def save(fileName: String, content: String): IO[Either[Throwable, String]] = {
refDb.set(Some(fileName)).as(Right(fileName))
override def save(fileName: String, content: String): IO[String] = {
refDb.set(Some(fileName)).as(fileName)
}
}
fileNameService = new FileNameService {