我最近将一个 playframework 项目从 scala2 迁移到 scala3,我发现这个错误只发生在 scala 3 中,但不会发生在 scala 2 中:
play.sbt.PlayExceptions$CompilationException: Compilation error[Instance not found: 'Conversion[models.ErrorCode, _ <: Product]']
at play.sbt.PlayExceptions$CompilationException$.apply(PlayExceptions.scala:28)
at play.sbt.PlayExceptions$CompilationException$.apply(PlayExceptions.scala:28)
at scala.Option.map(Option.scala:230)
at play.sbt.run.PlayReload$.$anonfun$taskFailureHandler$8(PlayReload.scala:119)
at scala.Option.map(Option.scala:230)
at play.sbt.run.PlayReload$.taskFailureHandler(PlayReload.scala:92)
at play.sbt.run.PlayReload$.$anonfun$compile$3(PlayReload.scala:144)
at scala.util.Either$LeftProjection.map(Either.scala:573)
at play.sbt.run.PlayReload$.compile(PlayReload.scala:144)
at sbt.PlayRun$.$anonfun$playRunTask$5(PlayRun.scala:99)
导致错误的行是:
implicit val formatError: OFormat[ErrorCode] = Json.format[ErrorCode]
implicit val formatAccountException: OFormat[AccountException] = Json.format[AccountException]
由于它是一个实例未找到异常,我假设我需要在代码的某个地方使用 new ErrorCode() 指令,但我不知道它应该在哪里,因为这段代码在 scala 2 中完美工作,并且这个错误只发生在我的 scala 3 项目中。真的很混乱
我一直在查看播放框架的文档,但我还没有发现任何与该错误远程相关的内容。 我有轻微的怀疑,我必须在有冲突的控制器内实例化“新的错误代码”,而不是在类中,但我可能是错的。 我将在这里留下导致错误的类的代码:
ErrorCode 只是一个抽象类,用于创建可扩展的类型。我尝试将其设为一个案例类,并使用 AccountException 案例类作为返回它的函数,但这并不是我的应用程序中真正需要的行为,我需要它返回某些类型。
abstract class ErrorCode {
val code: String = ""
val title: String = ""
val detail: Option[String] = None
}
//case class ErrorCode(code: String, title: String, detail: Option[String])
object ErrorCode {
def apply(codeValue: String, titleValue: String, detailValue: Option[String]): ErrorCode = new ErrorCode {
override val code: String = codeValue
override val title: String = titleValue
override val detail: Option[String] = detailValue
}
def unapply(err: ErrorCode): Option[(String, String, Option[String])] =
Some((err.code, err.title, err.detail))
}
这是 AccountException 异常,非常长:
abstract class AccountException(val errorCode: ErrorCode) {}
object AccountExceptions {
def apply(errorCode: ErrorCode): AccountException = new AccountException(errorCode) {}
def unapply(accEx: AccountException): Option[ErrorCode] = Some(accEx.errorCode)
case class AccountNotFoundException(resource: String) extends AccountException(AccountNotFound(resource))
case class AppNotFoundException(appKey: String) extends AccountException(AppNotFound(appKey))
case class AppTackConfigurationException(resource: String) extends AccountException(AppTackConfigurationError(resource))
case class InvalidCredentialsException(username: String) extends AccountException(InvalidCredentials(username))
case class MalformedAppleTokenException(userResource: String) extends AccountException(MalformedAppleToken(userResource))
case class MalformedGoogleTokenException(userResource: String) extends AccountException(MalformedGoogleToken(userResource))
case class ResourceNotFoundException(resource: String) extends AccountException(ResourceNotFound(resource))
case class UnknownException(username: String) extends AccountException(UnknownError(username))
case class UserAlreadyExistsException(username: String) extends AccountException(UserAlreadyExists(username))
case class UserNotFoundException(username: String) extends AccountException(UserNotFound(username))
}
object AccountErrorCodes {
case class AccountNotFound(resource: String) extends ErrorCode
{
override val code: String = "OAC07"
override val title: String = "Account not found"
override val detail: Option[String] = Some(resource)
}
case class AppNotFound(appKey: String) extends ErrorCode
{
override val code: String = "AME01"
override val title: String = "App not found"
override val detail: Option[String] = Some(appKey)
}
case class AppTackConfigurationError(configErrorDetail: String) extends ErrorCode
{
override val code: String = "AME07"
override val title: String = "Apptack configuration error"
override val detail: Option[String] = Some(configErrorDetail)
}
case class InvalidCredentials(username: String)extends ErrorCode
{
override val code: String = "OAC02"
override val title: String = "Credential invalid"
override val detail: Option[String] = Some(username)
}
case class MalformedAppleToken(userResource: String)extends ErrorCode
{
override val code: String = "OAC06"
override val title: String = "Malformed Apple sso token"
override val detail: Option[String] = Some(userResource)
}
case class MalformedGoogleToken(userResource: String) extends ErrorCode
{
override val code: String = "OAC06"
override val title: String = "Malformed google sso token"
override val detail: Option[String] = Some(userResource)
}
case class ResourceNotFound(resource: String) extends ErrorCode
{
override val code: String = "OAC07"
override val title: String = "Resource not found"
override val detail: Option[String] = Some(resource)
}
case class UnknownError(username: String) extends ErrorCode
{
override val code: String = "OAC05"
override val title: String = "Unexpected error"
override val detail: Option[String] = Some(username)
}
case class UserAlreadyExists(username: String) extends ErrorCode
{
override val code: String = "OAC03"
override val title: String = "User already exists"
override val detail: Option[String] = Some(username)
}
case class UserNotFound(username: String) extends ErrorCode
{
override val code: String = "OAC01"
override val title: String = "User not found"
override val detail: Option[String] = Some(username)
}
}
我可以继续将 ErrorCode 从 AbstractClass 更改为 Case Class 方法,并更改返回 ErrorCode 的函数的 AccountErrorCodes case 类,但我使用这些特定类型来管理 HTTP 响应,所以我想保留这种行为:
//this function takes place in the controller
private def exceptionToResult(exception: AccountException): Result = {
exception match {
case e: AccountExceptions.AccountNotFoundException => NotFound(Json.toJson(exception))
case e: AccountExceptions.AppTackConfigurationException => BadRequest(Json.toJson(exception))
case e: AccountExceptions.InvalidCredentialsException => Unauthorized(Json.toJson(exception))
case e: AccountExceptions.MalformedAppleTokenException => BadRequest(Json.toJson(exception))
case e: AccountExceptions.MalformedGoogleTokenException => BadRequest(Json.toJson(exception))
case e: AccountExceptions.ResourceNotFoundException => NotFound(Json.toJson(exception))
case e: AccountExceptions.UnknownException => InternalServerError(Json.toJson(exception))
case e: AccountExceptions.UserAlreadyExistsException => Conflict(Json.toJson(exception))
case e: AccountExceptions.UserNotFoundException => NotFound(Json.toJson(exception))
case _ => InternalServerError(Json.toJson(exception))
}
}
此代码在 scala 3 中编译并运行 3
import play.api.libs.json.{JsValue, Json, OWrites, Writes}
import play.api.mvc.Result
import play.api.mvc.Results.{InternalServerError, NotFound}
case class ErrorCode(code: String, title: String, detail: Option[String])
implicit val errorCodeWrites: OWrites[ErrorCode] = Json.writes[ErrorCode]
implicit val accountNotFoundExceptionWrites: OWrites[AccountException.AccountNotFoundException] =
Json.writes[AccountException.AccountNotFoundException]
implicit val appNotFoundExceptionWrites: OWrites[AccountException.AppNotFoundException] =
Json.writes[AccountException.AppNotFoundException]
//implicit val accountExceptionWrites: OWrites[AccountException] =
// Json.writes[AccountException]
implicit val accountExceptionWrites: Writes[AccountException] = new Writes[AccountException]:
def writes(o: AccountException): JsValue =
val builder = Json.newBuilder
builder += "code" -> o.errorCode.code
builder += "title" -> o.errorCode.title
builder += "detail" -> o.errorCode.detail
builder.result()
enum AccountException(val errorCode: ErrorCode) {
case AccountNotFoundException(resource: String)
extends AccountException(
ErrorCode(code = "OAC07", title = "Account Not Found", Some(resource))
)
case AppNotFoundException(appKey: String)
extends AccountException(
ErrorCode(code = "AME01", title = "App Not Found", Some(appKey))
)
}
def exceptionToResult(exception: AccountException): Result = {
exception match {
case AccountException.AccountNotFoundException(_) =>
NotFound(Json.toJson(exception))
// ....
case _ => InternalServerError(Json.toJson(exception))
}
}
我没有转换你所有的代码。我希望它有帮助。