<commit_before>package uk.ac.wellcome.platform.api.controllers

import com.github.xiaodongw.swagger.finatra.SwaggerSupport
import com.twitter.finatra.http.Controller
import com.twitter.inject.annotations.Flag
import io.swagger.models.Operation
import io.swagger.models.parameters.QueryParameter
import io.swagger.models.properties.StringProperty
import javax.inject.{Inject, Singleton}
import uk.ac.wellcome.display.models.{DisplayWork, WorksIncludes}
import uk.ac.wellcome.models.{Error, IdentifiedWork}
import uk.ac.wellcome.platform.api.ApiSwagger
import uk.ac.wellcome.platform.api.models.{DisplayError, DisplayResultList}
import uk.ac.wellcome.platform.api.requests._
import uk.ac.wellcome.platform.api.responses.{ResultListResponse, ResultResponse}
import uk.ac.wellcome.platform.api.services.WorksService
import uk.ac.wellcome.utils.GlobalExecutionContext.context

import scala.collection.JavaConverters._

@Singleton
class WorksController @Inject()(@Flag("api.prefix") apiPrefix: String,
                                @Flag("api.context") apiContext: String,
                                @Flag("api.host") apiHost: String,
                                @Flag("api.scheme") apiScheme: String,
                                @Flag("api.pageSize") defaultPageSize: Int,
                                worksService: WorksService)
    extends Controller
    with SwaggerSupport {

  override implicit protected val swagger = ApiSwagger

  val contextUri: String = s"$apiScheme://$apiHost$apiContext"
  val includesSwaggerParam: QueryParameter = new QueryParameter()
    .name("includes")
    .description("A comma-separated list of extra fields to include")
    .required(false)
    .`type`("array")
    .collectionFormat("csv")
    .items(new StringProperty()._enum(WorksIncludes.recognisedIncludes.asJava))

  prefix(apiPrefix) {
    setupResultListEnpoint("/works")

    setupSingleWorkEnpoint("/works/:id")
  }

  private def setupResultListEnpoint(endpointSuffix: String): Unit = {
    getWithDoc(endpointSuffix) { doc =>
      setupResultListSwaggerDocs(endpointSuffix, doc)
    } { request: MultipleResultsRequest =>
      val pageSize = request.pageSize.getOrElse(defaultPageSize)
      val includes = request.includes.getOrElse(WorksIncludes())

      val eventualResultList = getWorkList(request, pageSize)

      eventualResultList
        .map { resultList =>
          DisplayResultList(
            resultList = resultList,
            pageSize = pageSize,
            includes = includes
          )
        }
        .map(
          displayResultList => {
            ResultListResponse.create(
              contextUri,
              displayResultList,
              request,
              s"$apiScheme://$apiHost"
            )
          }
        )
    }
  }

  private def setupSingleWorkEnpoint(enpointSuffix: String): Unit = {
    getWithDoc(enpointSuffix) { doc =>
      setUpSingleWorkSwaggerDocs(doc)
    } { request: SingleWorkRequest =>
      val includes = request.includes.getOrElse(WorksIncludes())
      worksService
        .findWorkById(canonicalId = request.id, index = request._index)
        .map {

          case Some(work: IdentifiedWork) =>
            if (work.visible) {
              respondWithWork(includes, work)
            } else {
              respondWithGoneError
            }
          case None =>
            respondWithNotFoundError(request)
        }
        .recover {
          // If a user tries to request an ID without escaping it correctly
          // (e.g. "/works/work/zd224ncv]"), we get an IllegalArgumentException with
          // the error:
          //
          //      Illegal character in path at index 20: /works/work/zd224ncv]
          //
          // In this case, we return a 400 Bad Request exception rather than bubbling
          // up as a 500 error.
          case exception: IllegalArgumentException =>
            if (exception.getMessage.startsWith(
              "Illegal character in path at index ")) {
              val result = Error(
                variant = "http-400",
                description =
                  Some(s"Unrecognised character in identifier ${request.id}")
              )
              response.badRequest.json(
                ResultResponse(
                  context = contextUri,
                  result = DisplayError(result)
                )
              )
            }
        }
    }
  }

  private def getWorkList(request: MultipleResultsRequest, pageSize: Int) = {
    val works = request.query match {
      case Some(queryString) =>
        worksService.searchWorks(
          queryString,
          pageSize = pageSize,
          pageNumber = request.page,
          index = request._index
        )
      case None =>
        worksService.listWorks(
          pageSize = pageSize,
          pageNumber = request.page,
          index = request._index
        )
    }
    works
  }

  private def respondWithWork(includes: WorksIncludes, work: IdentifiedWork) = {
    val result = DisplayWork(work = work, includes = includes)
    response.ok.json(
      ResultResponse(context = contextUri, result = result))
  }

  private def respondWithGoneError = {
    val result = Error(
      variant = "http-410",
      description = Some("This work has been deleted")
    )
    response.gone.json(
      ResultResponse(
        context = contextUri,
        result = DisplayError(result)
      )
    )
  }

  private def respondWithNotFoundError(request: SingleWorkRequest) = {
    val result = Error(
      variant = "http-404",
      description =
        Some(s"Work not found for identifier ${request.id}")<commit_msg>Use for comprehensions, fix typos and break up even further<commit_after>package uk.ac.wellcome.platform.api.controllers

import com.github.xiaodongw.swagger.finatra.SwaggerSupport
import com.twitter.finatra.http.Controller
import com.twitter.inject.annotations.Flag
import io.swagger.models.Operation
import io.swagger.models.parameters.QueryParameter
import io.swagger.models.properties.StringProperty
import javax.inject.{Inject, Singleton}
import uk.ac.wellcome.display.models.{DisplayWork, WorksIncludes}
import uk.ac.wellcome.models.{Error, IdentifiedWork}
import uk.ac.wellcome.platform.api.ApiSwagger
import uk.ac.wellcome.platform.api.models.{DisplayError, DisplayResultList}
import uk.ac.wellcome.platform.api.requests._
import uk.ac.wellcome.platform.api.responses.{ResultListResponse, ResultResponse}
import uk.ac.wellcome.platform.api.services.WorksService
import uk.ac.wellcome.utils.GlobalExecutionContext.context

import scala.collection.JavaConverters._

@Singleton
class WorksController @Inject()(@Flag("api.prefix") apiPrefix: String,
                                @Flag("api.context") apiContext: String,
                                @Flag("api.host") apiHost: String,
                                @Flag("api.scheme") apiScheme: String,
                                @Flag("api.pageSize") defaultPageSize: Int,
                                worksService: WorksService)
    extends Controller
    with SwaggerSupport {

  override implicit protected val swagger = ApiSwagger

  val contextUri: String = s"$apiScheme://$apiHost$apiContext"
  val includesSwaggerParam: QueryParameter = new QueryParameter()
    .name("includes")
    .description("A comma-separated list of extra fields to include")
    .required(false)
    .`type`("array")
    .collectionFormat("csv")
    .items(new StringProperty()._enum(WorksIncludes.recognisedIncludes.asJava))

  prefix(apiPrefix) {
    setupResultListEndpoint("/works")
    setupSingleWorkEndpoint("/works/:id")
  }

  private def setupResultListEndpoint(endpointSuffix: String): Unit = {
    getWithDoc(endpointSuffix) { doc =>
      setupResultListSwaggerDocs(endpointSuffix, doc)
    } { request: MultipleResultsRequest =>
      val pageSize = request.pageSize.getOrElse(defaultPageSize)
      val includes = request.includes.getOrElse(WorksIncludes())

      for {
        resultList <- getWorkList(request, pageSize)
        displayResultList = DisplayResultList(
          resultList = resultList,
          pageSize = pageSize,
          includes = includes
        )
      } yield ResultListResponse.create(
        contextUri,
        displayResultList,
        request,
        s"$apiScheme://$apiHost"
      )
    }
  }

  private def setupSingleWorkEndpoint(enpointSuffix: String): Unit = {
    getWithDoc(enpointSuffix) { doc =>
      setUpSingleWorkSwaggerDocs(doc)
    } { request: SingleWorkRequest =>
      val includes = request.includes.getOrElse(WorksIncludes())

      val eventualResponse = for {
        maybeWork <- worksService.findWorkById(canonicalId = request.id, index = request._index)
      } yield generateSingleWorkResponse(maybeWork, includes, request)

        eventualResponse.recover {
          // If a user tries to request an ID without escaping it correctly
          // (e.g. "/works/work/zd224ncv]"), we get an IllegalArgumentException with
          // the error:
          //
          //      Illegal character in path at index 20: /works/work/zd224ncv]
          //
          // In this case, we return a 400 Bad Request exception rather than bubbling
          // up as a 500 error.
          case exception: IllegalArgumentException =>
            if (exception.getMessage.startsWith(
              "Illegal character in path at index ")) {
              val result = Error(
                variant = "http-400",
                description =
                  Some(s"Unrecognised character in identifier ${request.id}")
              )
              response.badRequest.json(
                ResultResponse(
                  context = contextUri,
                  result = DisplayError(result)
                )
              )
            }
        }
    }
  }

  private def getWorkList(request: MultipleResultsRequest, pageSize: Int) = {
    val works = request.query match {
      case Some(queryString) =>
        worksService.searchWorks(
          queryString,
          pageSize = pageSize,
          pageNumber = request.page,
          index = request._index
        )
      case None =>
        worksService.listWorks(
          pageSize = pageSize,
          pageNumber = request.page,
          index = request._index
        )
    }
    works
  }

  private def generateSingleWorkResponse(maybeWork: Option[IdentifiedWork], includes: WorksIncludes, request: SingleWorkRequest) = maybeWork match {
    case Some(work: IdentifiedWork) =>
      if (work.visible) {
        respondWithWork(includes, work)
      } else {
        respondWithGoneError
      }
    case None =>
      respondWithNotFoundError(request)
  }

  private def respondWithWork(includes: WorksIncludes, work: IdentifiedWork) = {
    val result = DisplayWork(work = work, includes = includes)
    response.ok.json(
      ResultResponse(context = contextUri, result = result))
  }

  private def respondWithGoneError = {
    val result = Error(
      variant = "http-410",
      description = Some("This work has been deleted")
    )
    response.gone.json(
      ResultResponse(
        context = contextUri,
        result = DisplayError(result)
      )
    )
  }

  private def respondWithNotFoundError(request: SingleWorkRequest) = {
    val result = Error(
      variant = "http-404",
      description =
        Some(s"Work not found for identifier ${request.id}")
{'fraction_non_alphanumeric': 0.09279342312401609, 'fraction_numerical': 0.004023089032709463, 'mean_word_length': 2.3622463981182005, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '264396', 'n_tokens_mistral': 3119, 'n_tokens_neox': 3003, 'n_words': 667}