簡體   English   中英

在Scala中通過驗證來構建對象,並在Anorm解析器中使用它

[英]Object construction with validation in Scala, using that in an Anorm parser

我有一個簡單的案例類金額如下

case class Amount(value: Long, currency: Currency)

還有一個附帶的對象,用於將字符串貨幣代碼轉換為Currency對象

object Amount {
  private val log = Logger(getClass)
  def apply(value: Long, currencyCode: String) : Amount = {
    try {
      Amount(value, Currency.getInstance(currencyCode))
    } catch {
      case e: Exception =>
        log.error(s"Invalid currency code [$currencyCode]")
        throw new Exception(s"Invalid currency code [$currencyCode]")
    }
  }
}

調用方式:

val amount : Amount = Amount(1234, "USD")

當我從數據庫中讀取一些數據時,我有一個自定義解析器,例如

implicit val amountParser = Macro.parser[Amount]("value", "currencyCode")

但是,編譯器抱怨

scala.ScalaReflectionException: value apply encapsulates multiple overloaded alternatives and cannot be treated as a method. Consider invoking `<offending symbol>.asTerm.alternatives` and manually picking the required method
[error]     at scala.reflect.api.Symbols$SymbolApi$class.asMethod(Symbols.scala:228)
[error]     at scala.reflect.internal.Symbols$SymbolContextApiImpl.asMethod(Symbols.scala:84)
[error]     at anorm.Macro$.parserImpl(Macro.scala:70)
[error]     at anorm.Macro$.namedParserImpl_(Macro.scala:25)
[error]     implicit val amountParser = Macro.parser[Amount]("value", "currencyCode")

我該如何工作?

UPDATE

了解了@MikeAllen的響應后,我決定保留case class Amountobject Amount不變,而是為Amount編寫了一個自定義解析器,如下所示

    implicit private val amountParser = for {
        value <- long("value")
        currencyCode <- str("currency_code")
      } yield { 
           Amount(value, currencyCode) 
      }

Scala編譯器將自動生成一個Amount.apply工廠方法來創建case class實例,這就是為什么會出現此錯誤的原因-因為您有多個Amount.apply方法。 其中一個采用類型( LongCurrency )的參數,另一個采用類型( LongString )的參數。 錯誤消息表明,您需要從通過反射報告的重載替代項中選擇一種。

或者,您的案例類和同伴可以按以下方式重做:

final case class Amount(value: Long, currencyCode: String) {

  /** Currency. Will create an exception on instantiation if code is invalid. */
  val currency: Currency = {
    try {
      Currency.getInstance(currencyCode)
    }
    catch {
      case e: Exception =>
        Amount.log.error(s"Invalid currency code [$currencyCode]")
        throw new Exception(s"Invalid currency code [$currencyCode]")
    }
  }
}

object Amount {
  private val log = Logger(getClass)
}

誠然,這並不那么優雅,因為您現在有了一個字段currency ,它不是case類的參數之一,並且不能用於模式匹配,同時還帶有字符串形式。

更好的解決方案是保留原始case class並將貨幣代碼字段從String轉換為Currency ,然后再創建Amount實例,作為解析器的一部分:

val amountMapping = {
  get[Long]("value") ~ get[String]("currencyCode") map {
    case value ~ currencyCode => {
      val currency = {
        try {
          Currency.getInstance(currencyCode)
        }
        catch {
          case e: Exception =>
            Amount.log.error(s"Invalid currency code [$currencyCode]")
            throw new Exception(s"Invalid currency code [$currencyCode]")
        }
      }
      Amount(value, currency)
    }
  }
}

然后,您可以使用它來分析行,例如:

def amounts(): List[Amount] = SQL("select * from amounts").as(amountMapping *)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM