简体   繁体   English

在Scala中通过验证来构建对象,并在Anorm解析器中使用它

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

I have a simple case class Amount as below 我有一个简单的案例类金额如下

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

And an accompanying object to convert a string currency code into a Currency object 还有一个附带的对象,用于将字符串货币代码转换为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]")
    }
  }
}

Invocation : 调用方式:

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

When I read some data from the database, I have a custom parser such as 当我从数据库中读取一些数据时,我有一个自定义解析器,例如

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

However, the compiler complains 但是,编译器抱怨

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")

How do I make this work ? 我该如何工作?

UPDATE UPDATE

After understanding the response from @MikeAllen, I decided to leave the case class Amount and the object Amount as is, instead I wrote a custom parser for the Amount as below 了解了@MikeAllen的响应后,我决定保留case class Amountobject Amount不变,而是为Amount编写了一个自定义解析器,如下所示

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

The Scala compiler will automatically generate an Amount.apply factory method for creating case class instances, which is why you're getting this error - because you have multiple Amount.apply methods. Scala编译器将自动生成一个Amount.apply工厂方法来创建case class实例,这就是为什么会出现此错误的原因-因为您有多个Amount.apply方法。 One of these takes arguments of type ( Long , Currency ) and the other takes arguments of type ( Long , String ). 其中一个采用类型( LongCurrency )的参数,另一个采用类型( LongString )的参数。 The error message suggests that you need to select one of these from the overloaded alternatives reported through reflection . 错误消息表明,您需要从通过反射报告的重载替代项中选择一种。

Alternatively, your case class and companion might be reworked as follows: 或者,您的案例类和同伴可以按以下方式重做:

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)
}

This is not quite as elegant, admittedly, as you now have a field, currency , that isn't one of the case class's parameters and that isn't available for pattern matching, while also carrying around the string form. 诚然,这并不那么优雅,因为您现在有了一个字段currency ,它不是case类的参数之一,并且不能用于模式匹配,同时还带有字符串形式。

A better solution would be to keep your original case class and convert the currency code field from a String into a Currency , before creating the Amount instance, as part of the parser: 更好的解决方案是保留原始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)
    }
  }
}

You can then use this to parse rows, for example with: 然后,您可以使用它来分析行,例如:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM