Создание общей многократно используемой функции с неявными и типами в Scala

У меня есть блок кода, который реплицируется буквально повсюду. Этот блок кода (около 10 строк) обрабатывает входящие действия, проверяет их, обрабатывает сериализацию JSON и вызывает внутреннюю функцию, подготавливая результат.

Я знаю, что это можно свести к однострочному вызову общей вспомогательной функции, но из-за имплицитов, типов и моего не очень хорошего знания синтаксиса Scala я продолжаю сталкиваться с проблемами.

Код (два отдельных примера, чтобы продемонстрировать, чем они отличаются):

val authenticate = GPAuthenticatedAction(parse.json) { implicit request =>
    request.body.validate[AuthenticationRequest] match {
        case JsSuccess(request, _) => {
            val (status, response) = performAuthentication(request)
            status(Json.toJson(response.asInstanceOf[AuthenticationResponse]))
        }

        case e: JsError => NotAcceptable(Json.toJson(GPError.MalformedJSON.value(e.toString + " REQUEST: " + request.body.toString)))
    }
}

val register = GPAuthenticatedAction(parse.json) { implicit request =>
    request.body.validate[RegistrationRequest] match {
        case JsSuccess(request, _) => {
            val (status, response) = performRegistration(request)
            status(Json.toJson(response.asInstanceOf[RegistrationResponse]))
        }

        case e: JsError => NotAcceptable(Json.toJson(GPError.MalformedJSON.value(e.toString + " REQUEST: " + request.body.toString)))
    }
}

Как видите, они почти идентичны, за исключением типа запроса (AuthenticationRequest против RegistrationRequest) и типа ответа (AuthenticationResponse против RegistrationResponse). В противном случае это шаблон.

Должен быть способ перегнать это к чему-то вроде:

val register = GPAuthenticatedAction(parse.son) from(RegistrationRequest, RegistrationResponse)

Я попытался определить from[I,O](request: I, response: O), но это привело к множеству проблем (нет информации о том, что JSON десериализует и т. д.). Итак, я попытался немного абстрагироваться, создав пару трейтов, GPRequest и GPResult:

trait GPRequest[T] {
    implicit val format = Json.format[T]
}

trait GPResponse[T] {
    implicit val format: Format[T] = Json.format[T]
    def from(error: GPError): GPResponse
}

А затем попытался определить такую ​​​​функцию, как:

def from[I: GPRequest, O: GPResponse](request: Request) { implicit request =>
    request.body.validate[I] match {
        case JsSuccess(request, _) => {
            val (status, response) = performAuthentication(request)
            status(Json.toJson(response.asInstanceOf[O]))
        }

        case e: JsError => NotAcceptable(Json.toJson(GPError.MalformedJSON.value(e.toString + " REQUEST: " + request.body.toString)))
    }
}

Но это приводит ко всем видам проблем. Я вставил ошибки компилятора ниже, но общий смысл таков:

  1. JSON не может понять, что делать с неявной сериализацией (формат/чтение/запись). Не удается найти apply и unapply фактического типа.
  2. Ошибки параметров типа в GPRequest, GPResponse.
  3. Не могу объявить запрос как implicit (вероятно, мой синтаксис Scala здесь испорчен).

И становится хуже.

Суть вопроса: Есть ли у кого-нибудь чистый шаблон проектирования, который достигает того, что я здесь ищу? (Или, в качестве альтернативы, может ли кто-нибудь, кто знает Play и Scala, дать некоторые рекомендации о том, что делать дальше? ). Это действительно кажется, что это должно быть возможно, но я застрял, выясняя следующий шаг.

Для бесстрашных вот ошибки:

[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/controllers/GPSecurityService.scala:33: trait GPRequest takes type parameters
[error]     object AuthenticationRequest extends GPRequest {
[error]                                          ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPResponseMapping.scala:121: trait GPResponse takes type parameters
[error]     def okResponse(response: GPProcedureResult, instance: GPResponse): (Results.Status, GPResponse) = { (resultCodeFor(response.code), instance) }
[error]                                                                                         ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPResponseMapping.scala:121: trait GPResponse takes type parameters
[error]     def okResponse(response: GPProcedureResult, instance: GPResponse): (Results.Status, GPResponse) = { (resultCodeFor(response.code), instance) }
[error]                                                           ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPResponseMapping.scala:109: trait GPResponse takes type parameters
[error]     def okResponse(response: Results.Status, instance: GPResponse): (Results.Status, GPResponse) = { (response, instance) }
[error]                                                                                      ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPResponseMapping.scala:109: trait GPResponse takes type parameters
[error]     def okResponse(response: Results.Status, instance: GPResponse): (Results.Status, GPResponse) = { (response, instance) }
[error]                                                        ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/controllers/GPSecurityService.scala:37: trait GPResponse takes type parameters
[error]     case class AuthenticationResponse(token: Option[GPToken], id: Option[GPID], error: Option[GPError]) extends GPResponse {
[error]                                                                                                                 ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPResponseMapping.scala:98: trait GPResponse takes type parameters
[error]     def errorResponse(error: GPProcedureResult, instance: GPResponse): (Results.Status, GPResponse) = { (resultCodeFor(error.code), instance.from(error.failed.getOrElse(GPError.errorForCode(error.code)))) }
[error]                                                                                         ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPResponseMapping.scala:98: trait GPResponse takes type parameters
[error]     def errorResponse(error: GPProcedureResult, instance: GPResponse): (Results.Status, GPResponse) = { (resultCodeFor(error.code), instance.from(error.failed.getOrElse(GPError.errorForCode(error.code)))) }
[error]                                                           ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPResponseMapping.scala:85: trait GPResponse takes type parameters
[error]     def errorResponse(error: GPError, instance: GPResponse): (Results.Status, GPResponse) = { (resultCodeFor(error), instance.from(error)) }
[error]                                                                               ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPResponseMapping.scala:85: trait GPResponse takes type parameters
[error]     def errorResponse(error: GPError, instance: GPResponse): (Results.Status, GPResponse) = { (resultCodeFor(error), instance.from(error)) }
[error]                                                 ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/controllers/GPSecurityService.scala:43: trait GPResponse takes type parameters
[error]     object AuthenticationResponse extends GPResponse {
[error]                                           ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/controllers/GPSecurityService.scala:53: trait GPRequest takes type parameters
[error]     object RegistrationRequest extends GPRequest {
[error]                                        ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/controllers/GPSecurityService.scala:57: trait GPResponse takes type parameters
[error]     case class RegistrationResponse(token: Option[GPToken], id: Option[GPID], error: Option[GPError]) extends GPResponse {
[error]                                                                                                               ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/controllers/GPSecurityService.scala:63: trait GPResponse takes type parameters
[error]     object RegistrationResponse extends GPResponse {
[error]                                         ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/controllers/GPSecurityService.scala:73: trait GPRequest takes type parameters
[error]     object LogoutRequest extends GPRequest {
[error]                                  ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/controllers/GPSecurityService.scala:77: trait GPResponse takes type parameters
[error]     case class LogoutResponse(error: Option[GPError] = None) extends GPResponse {
[error]                                                                      ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/controllers/GPSecurityService.scala:81: trait GPResponse takes type parameters
[error]     object LogoutResponse extends GPResponse {
[error]                                   ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/controllers/GPSecurityService.scala:88: trait Request takes type parameters
[error]     def from[I: GPRequest, O: GPResponse](request: Request) { implicit request =>
[error]                                                    ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/controllers/GPSecurityService.scala:88: missing parameter type
[error]     def from[I: GPRequest, O: GPResponse](request: Request) { implicit request =>
[error]                                                                        ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/controllers/GPSecurityService.scala:91: type mismatch;
[error]  found   : Any
[error]  required: controllers.GPSecurityService.AuthenticationRequest
[error]                 val (status, response) = performAuthentication(request)
[error]                                                                ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/controllers/GPSecurityService.scala:92: Any does not take parameters
[error]                 status(Json.toJson(response.asInstanceOf[O]))
[error]                       ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPResponseMapping.scala:15: No unapply or unapplySeq function found
[error]     implicit val format = Json.format[T]
[error]                                      ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPResponseMapping.scala:19: No unapply or unapplySeq function found
[error]     implicit val format: Format[T] = Json.format[T]
[error]                                                 ^
[error] /Users/zbeckman/Projects/Glimpulse/Server/project/glimpulse-server/app/utility/GPResponseMapping.scala:20: trait GPResponse takes type parameters
[error]     def from(error: GPError): GPResponse

person Zac    schedule 11.06.2015    source источник


Ответы (1)


Вы можете написать родительские case-классы вместо своих трейтов и расширить существующие типы:

AuthenticationRequest, RegistrationRequest, AuthenticationResponse, RegistrationResponse

будучи подклассом ваших новых родительских классов case.

При этом вы можете изменить свою подпись, чтобы расширить свои общие типы следующим образом:

def from[I <: GPRequest, O <: GPResponse]
person Thiago Pereira    schedule 11.06.2015