我正在尝试使用
TypeLevel Scala
编写一个示例 HTTP 服务,该服务会访问 National Weather Service API。我遇到类型不匹配错误并且
EntityEncoder
来自 cats.effect.IO
包导入。
build.sbt
:
lazy val http4sVersion = "1.0.0-M37"
lazy val circeVersion = "0.14.5"
lazy val sttpVersion = "3.9.0"
lazy val munitVersion = "0.7.29"
lazy val logbackVersion = "1.4.7"
ThisBuild / scalaVersion := "2.13.12"
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-blaze-server" % http4sVersion,
"org.http4s" %% "http4s-circe" % http4sVersion,
"org.http4s" %% "http4s-dsl" % http4sVersion,
"io.circe" %% "circe-generic" % circeVersion,
"io.circe" %% "circe-literal" % circeVersion,
"com.softwaremill.sttp.client3" %% "core" % sttpVersion,
"com.softwaremill.sttp.client3" %% "circe" % sttpVersion,
"org.typelevel" %% "cats-effect" % "3.5.1",
"org.scalameta" %% "munit" % munitVersion % Test,
"ch.qos.logback" % "logback-classic" % logbackVersion
)
WeatherServer.scala
:
package weather
import cats.effect._
import org.http4s._
import org.http4s.dsl.io._
import org.http4s.implicits._
import org.http4s.blaze.server.BlazeServerBuilder
import org.http4s.circe._
import io.circe.generic.auto._
import sttp.client3._
import sttp.client3.circe._
object WeatherServer extends IOApp {
case class WeatherResponse(forecast: String, temperatureType: String)
def classifyTemperature(temp: Double): String = {
if (temp < 10) "cold"
else if (temp > 25) "hot"
else "moderate"
}
def getWeather(latitude: Double, longitude: Double): IO[WeatherResponse] = {
val backend = HttpURLConnectionBackend()
val request = basicRequest
.get(uri"https://api.weather.gov/points/$latitude,$longitude")
.response(asJson[Map[String, Any]])
IO.fromEither(request.send(backend).body)
.flatMap { body =>
val forecastUrl = body("properties").asInstanceOf[Map[String, Any]]("forecast").toString
val forecastRequest = basicRequest.get(uri"$forecastUrl").response(asJson[Map[String, Any]])
IO.fromEither(forecastRequest.send(backend).body)
.map { forecastBody =>
val forecastData = forecastBody("properties").asInstanceOf[Map[String, Any]]("periods").asInstanceOf[List[Map[String, Any]]]
val todayForecast = forecastData.head("shortForecast").toString
val temperature = forecastData.head("temperature").toString.toDouble
WeatherResponse(todayForecast, classifyTemperature(temperature))
}
}
}
val weatherService = HttpRoutes.of[IO] {
case GET -> Root / "weather" :? LatitudeQueryParamMatcher(lat) +& LongitudeQueryParamMatcher(lon) =>
getWeather(lat, lon).flatMap { weather =>
Ok(weather)
}
}
val httpApp = weatherService.orNotFound
def run(args: List[String]): IO[ExitCode] =
BlazeServerBuilder[IO](runtime.compute)
.bindHttp(8080, "0.0.0.0")
.withHttpApp(httpApp)
.serve
.compile
.drain
.as(ExitCode.Success)
}
object LatitudeQueryParamMatcher extends QueryParamDecoderMatcher[Double]("latitude")
object LongitudeQueryParamMatcher extends QueryParamDecoderMatcher[Double]("longitude")
当我使用
sbt run
运行它时:
[info] welcome to sbt 1.10.2 (Oracle Corporation Java 21)
[info] loading project definition from /Users/pnwlover/weatherservice/project
[info] loading settings for project weatherservice from build.sbt ...
[info] set current project to weatherservice (in build file:/Users/pnwlover/weatherservice/)
[info] compiling 2 Scala sources to /Users/pnwlover/weatherservice/target/scala-2.13/classes ...
[error] /Users/pnwlover/weatherservice/src/main/scala/weather/WeatherServer.scala:26:12: type mismatch;
[error] found : StringContext
[error] required: ?{def uri(x$1: ? >: Double, x$2: ? >: Double): ?}
[error] Note that implicit conversions are not applicable because they are ambiguous:
[error] both method http4sLiteralsSyntax in trait LiteralsSyntax of type (sc: StringContext): org.http4s.syntax.LiteralsOps
[error] and method UriContext in trait UriInterpolator of type (sc: StringContext): sttp.client3.package.UriContext
[error] are possible conversion functions from StringContext to ?{def uri(x$1: ? >: Double, x$2: ? >: Double): ?}
[error] .get(uri"https://api.weather.gov/points/$latitude,$longitude")
[error] ^
[error] /Users/pnwlover/weatherservice/src/main/scala/weather/WeatherServer.scala:27:23: could not find implicit value for evidence parameter of type io.circe.Decoder[Map[String,Any]]
[error] .response(asJson[Map[String, Any]])
[error] ^
[error] /Users/pnwlover/weatherservice/src/main/scala/weather/WeatherServer.scala:46:11: Cannot convert from weather.WeatherServer.WeatherResponse to an Entity, because no EntityEncoder[cats.effect.IO, weather.WeatherServer.WeatherResponse] instance could be found.
[error] Ok(weather)
[error] ^
[error] three errors found
[error] (Compile / compileIncremental) Compilation failed
[error] Total time: 2 s, completed Sep 25, 2024, 3:00:05PM
对
build.sbt
文件进行以下更改即可使其正常工作:
lazy val http4sVersion = "0.23.28"
lazy val circeVersion = "0.14.5"
lazy val sttpVersion = "3.8.3"
ThisBuild / scalaVersion := "2.13.14"
libraryDependencies ++= Seq(
"org.http4s" %% "http4s-ember-server" % http4sVersion,
"org.http4s" %% "http4s-circe" % http4sVersion,
"org.http4s" %% "http4s-dsl" % http4sVersion,
"io.circe" %% "circe-generic" % circeVersion,
"io.circe" %% "circe-parser" % circeVersion,
"com.softwaremill.sttp.client3" %% "core" % sttpVersion,
"com.softwaremill.sttp.client3" %% "circe" % sttpVersion,
"com.comcast" %% "ip4s-core" % "3.0.1",
"org.typelevel" %% "log4cats-slf4j" % "2.6.0",
"ch.qos.logback" % "logback-classic" % "1.4.7",
// Test dependencies
"org.scalatest" %% "scalatest" % "3.2.15" % Test,
"org.typelevel" %% "cats-effect-testing-scalatest" % "1.5.0" % Test,
"org.http4s" %% "http4s-client" % http4sVersion % Test
)
Compile / run / fork := true
// Add this line to enable parallel execution of tests
Test / parallelExecution := true