Skip to content

Commit

Permalink
APIS-4691-Regex - Try strong type for value of regex validation rule (#…
Browse files Browse the repository at this point in the history
…70)

* APIS-4691-Regex - Try strong type for value of regex validation rule

* APIS-4691 - Add ftp exclusion to url validation

* APIS-4691 - Refined type use for field names
  • Loading branch information
AndySpaven authored Apr 20, 2020
1 parent 658231a commit 3d82d4f
Show file tree
Hide file tree
Showing 27 changed files with 276 additions and 189 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,20 @@ import play.modules.reactivemongo.ReactiveMongoComponent
import uk.gov.hmrc.apisubscriptionfields.RequestHeaders
import uk.gov.hmrc.apisubscriptionfields.model.JsonFormatters._
import uk.gov.hmrc.apisubscriptionfields.model._
import Types.Fields

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
import uk.gov.hmrc.apisubscriptionfields.TestData
import cats.data.NonEmptyList
import uk.gov.hmrc.apisubscriptionfields.FieldsDefinitionTestData

trait AcceptanceTestSpec extends FeatureSpec
with GivenWhenThen
with BeforeAndAfterAll
with Matchers
with GuiceOneServerPerSuite
with TestData {
with FieldsDefinitionTestData {

protected val ValidRequest = FakeRequest()
.withHeaders(RequestHeaders.ACCEPT_HMRC_JSON_HEADER)
Expand Down
18 changes: 14 additions & 4 deletions app/uk/gov/hmrc/apisubscriptionfields/model/JsonFormatters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ import java.util.UUID

import cats.data.{NonEmptyList => NEL}
import julienrf.json.derived
import julienrf.json.derived.TypeTagSetting
import play.api.libs.json._
import julienrf.json.derived.TypeTagSetting.ShortClassName
import play.api.libs.json.Reads._
import play.api.libs.functional.syntax._
import uk.gov.hmrc.apisubscriptionfields.model.FieldDefinitionType.FieldDefinitionType
import Types._

trait SharedJsonFormatters {
implicit val SubscriptionFieldsIdJF = new Format[SubscriptionFieldsId] {
Expand Down Expand Up @@ -56,15 +57,22 @@ trait NonEmptyListFormatters {

trait JsonFormatters extends SharedJsonFormatters with NonEmptyListFormatters {
import be.venneborg.refined.play.RefinedJsonFormats._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.auto._
import play.api.libs.json._

implicit val validationRuleFormat: OFormat[ValidationRule] = derived.withTypeTag.oformat(TypeTagSetting.ShortClassName)
implicit val FieldNameFormat = formatRefined[String, FieldNameRegex, Refined]

implicit val FieldsFormat: Format[Fields] = refinedMapFormat[String, FieldNameRegex, Refined]

implicit val validationRuleFormat: OFormat[ValidationRule] = derived.withTypeTag.oformat(ShortClassName)

implicit val ValidationJF = Json.format[ValidationGroup]

implicit val FieldDefinitionTypeReads = Reads.enumNameReads(FieldDefinitionType)

val fieldDefinitionReads: Reads[FieldDefinition] = (
(JsPath \ "name").read[String] and
(JsPath \ "name").read[FieldName] and
(JsPath \ "description").read[String] and
((JsPath \ "hint").read[String] or Reads.pure("")) and
(JsPath \ "type").read[FieldDefinitionType] and
Expand All @@ -81,10 +89,12 @@ trait JsonFormatters extends SharedJsonFormatters with NonEmptyListFormatters {
implicit val FieldsDefinitionResponseJF = Json.format[FieldsDefinitionResponse]
implicit val BulkFieldsDefinitionsResponseJF = Json.format[BulkFieldsDefinitionsResponse]
implicit val SubscriptionFieldsResponseJF = Json.format[SubscriptionFieldsResponse]
implicit val SubscriptionFieldsJF = Json.format[SubscriptionFields]

implicit val BulkSubscriptionFieldsResponseJF = Json.format[BulkSubscriptionFieldsResponse]

implicit val SubsFieldValidationResponseJF: OFormat[SubsFieldValidationResponse] = derived.withTypeTag.oformat(TypeTagSetting.ShortClassName)

implicit val SubsFieldValidationResponseJF: OFormat[SubsFieldValidationResponse] = derived.withTypeTag.oformat(ShortClassName)
implicit val InvalidSubsFieldValidationResponseJF = Json.format[InvalidSubsFieldValidationResponse]
}

Expand Down
14 changes: 8 additions & 6 deletions app/uk/gov/hmrc/apisubscriptionfields/model/Model.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package uk.gov.hmrc.apisubscriptionfields.model
import java.util.UUID
import cats.data.{NonEmptyList => NEL}
import uk.gov.hmrc.apisubscriptionfields.model.FieldDefinitionType.FieldDefinitionType
import eu.timepit.refined._
import Types._

case class ClientId(value: String) extends AnyVal

Expand All @@ -32,15 +34,13 @@ sealed trait ValidationRule {
def validate(value: String): Boolean
}

case class RegexValidationRule(regex: String) extends ValidationRule {
def validate(value: String): Boolean = value.matches(regex)
case class RegexValidationRule(regex: RegexExpr) extends ValidationRule {
def validate(value: String): Boolean = value.matches(regex.value)
}

case object UrlValidationRule extends ValidationRule {
import eu.timepit.refined.string._
import eu.timepit.refined._

def validate(value: String): Boolean = refineV[Url](value).isRight
def validate(value: String): Boolean = refineV[NonFtpUrl](value).isRight
}

case class ValidationGroup(errorMessage: String, rules: NEL[ValidationRule])
Expand All @@ -55,4 +55,6 @@ object FieldDefinitionType extends Enumeration {
val STRING = Value("STRING")
}

case class FieldDefinition(name: String, description: String, hint: String = "", `type`: FieldDefinitionType, shortDescription: String, validation: Option[ValidationGroup] = None)
case class FieldDefinition(name: FieldName, description: String, hint: String = "", `type`: FieldDefinitionType, shortDescription: String, validation: Option[ValidationGroup] = None)

case class SubscriptionFields(clientId: String, apiContext: String, apiVersion: String, fieldsId: UUID, fields: Fields)
1 change: 1 addition & 0 deletions app/uk/gov/hmrc/apisubscriptionfields/model/Requests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package uk.gov.hmrc.apisubscriptionfields.model

import cats.data.NonEmptyList
import Types._

case class SubscriptionFieldsRequest(fields: Fields)

Expand Down
3 changes: 2 additions & 1 deletion app/uk/gov/hmrc/apisubscriptionfields/model/Responses.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package uk.gov.hmrc.apisubscriptionfields.model
import play.api.libs.json.Json.JsValueWrapper
import play.api.libs.json.{JsObject, Json}
import cats.data.NonEmptyList
import Types._

case class SubscriptionFieldsResponse(clientId: String, apiContext: String, apiVersion: String, fieldsId: SubscriptionFieldsId, fields: Fields)

Expand All @@ -32,7 +33,7 @@ sealed trait SubsFieldValidationResponse

case object ValidSubsFieldValidationResponse extends SubsFieldValidationResponse

case class InvalidSubsFieldValidationResponse(errorResponses: Map[String, String]) extends SubsFieldValidationResponse
case class InvalidSubsFieldValidationResponse(errorResponses: Map[FieldName, String]) extends SubsFieldValidationResponse

object ErrorCode extends Enumeration {
type ErrorCode = Value
Expand Down
50 changes: 50 additions & 0 deletions app/uk/gov/hmrc/apisubscriptionfields/model/Types.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2020 HM Revenue & Customs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package uk.gov.hmrc.apisubscriptionfields.model

import eu.timepit.refined._
import eu.timepit.refined.api.Refined
import eu.timepit.refined.string._
import eu.timepit.refined.boolean._
import eu.timepit.refined.api.RefType.applyRef

object Types {
type RegexExpr = String Refined Regex
type NonFtpUrl = Url And Not[StartsWith[W.`"ftp"`.T]]

type FieldNameRegex = MatchesRegex[W.`"^[a-zA-Z]+$"`.T]
type FieldName = Refined[String,FieldNameRegex]
object FieldName {
def unsafeApply(s: String): FieldName = Refined.unsafeApply(s)
}

type FieldValue = String

type Fields = Map[FieldName, String]

type ErrorMessage = String

type FieldError = (FieldName, ErrorMessage)

type FieldErrorMap = Map[FieldName, ErrorMessage]
object FieldErrorMap {
val empty = Map.empty[FieldName, ErrorMessage]
}

type IsInsert = Boolean

}
25 changes: 0 additions & 25 deletions app/uk/gov/hmrc/apisubscriptionfields/model/package.scala

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ import play.api.libs.json._
import reactivemongo.api.indexes.IndexType
import reactivemongo.bson.BSONObjectID
import reactivemongo.play.json.collection.JSONCollection
import uk.gov.hmrc.apisubscriptionfields.model.{ApiContext, ApiVersion, IsInsert}
import uk.gov.hmrc.mongo.ReactiveRepository
import uk.gov.hmrc.mongo.json.ReactiveMongoFormats
import uk.gov.hmrc.apisubscriptionfields.model._
import Types._

import scala.concurrent.Future

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import reactivemongo.api.Cursor

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import uk.gov.hmrc.apisubscriptionfields.model.IsInsert
import uk.gov.hmrc.apisubscriptionfields.model.Types.IsInsert

trait MongoCrudHelper[T] extends MongoIndexCreator with MongoErrorHandler {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ package uk.gov.hmrc.apisubscriptionfields.repository

import play.api.Logger
import reactivemongo.api.commands.{UpdateWriteResult, WriteResult}
import uk.gov.hmrc.apisubscriptionfields.model.IsInsert
import uk.gov.hmrc.apisubscriptionfields.model.Types.IsInsert

trait MongoErrorHandler {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import play.api.libs.json.Json
import uk.gov.hmrc.apisubscriptionfields.model.JsonFormatters

trait MongoFormatters extends JsonFormatters {
implicit val SubscriptionFieldsJF = Json.format[SubscriptionFields]
implicit val FieldsDefinitionJF = Json.format[FieldsDefinition]
}

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ import reactivemongo.bson.BSONObjectID
import reactivemongo.play.json._
import reactivemongo.play.json.collection.JSONCollection
import uk.gov.hmrc.apisubscriptionfields.model._
import Types.IsInsert
import uk.gov.hmrc.mongo.ReactiveRepository
import uk.gov.hmrc.mongo.json.ReactiveMongoFormats

import scala.concurrent.Future

@ImplementedBy(classOf[SubscriptionFieldsMongoRepository])
Expand All @@ -48,15 +48,18 @@ trait SubscriptionFieldsRepository {

@Singleton
class SubscriptionFieldsMongoRepository @Inject()(mongoDbProvider: MongoDbProvider)
extends ReactiveRepository[SubscriptionFields, BSONObjectID]("subscriptionFields", mongoDbProvider.mongo,
MongoFormatters.SubscriptionFieldsJF, ReactiveMongoFormats.objectIdFormats)
extends ReactiveRepository[SubscriptionFields, BSONObjectID](
"subscriptionFields",
mongoDbProvider.mongo,
JsonFormatters.SubscriptionFieldsJF,
ReactiveMongoFormats.objectIdFormats
)
with SubscriptionFieldsRepository
with MongoCrudHelper[SubscriptionFields] {
with MongoCrudHelper[SubscriptionFields]
with MongoFormatters {

override val mongoCollection: JSONCollection = collection

private implicit val format = MongoFormatters.SubscriptionFieldsJF

override def indexes = Seq(
createCompoundIndex(
indexFieldMappings = Seq(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import uk.gov.hmrc.apisubscriptionfields.repository.{FieldsDefinition, FieldsDef
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import cats.data.NonEmptyList
import Types._

@Singleton
class FieldsDefinitionService @Inject() (repository: FieldsDefinitionRepository) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ import java.util.UUID

import javax.inject._
import uk.gov.hmrc.apisubscriptionfields.model._
import uk.gov.hmrc.apisubscriptionfields.repository.{SubscriptionFields, SubscriptionFieldsRepository}
import Types._

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import cats.data.NonEmptyList
import uk.gov.hmrc.apisubscriptionfields.service.SubscriptionFieldsService.FieldErrorMap
import uk.gov.hmrc.apisubscriptionfields.repository.SubscriptionFieldsRepository

@Singleton
class UUIDCreator {
Expand Down Expand Up @@ -102,14 +102,7 @@ class SubscriptionFieldsService @Inject() (repository: SubscriptionFieldsReposit
}

object SubscriptionFieldsService {

type FieldName = String
type ErrorMessage = String
type FieldError = (FieldName, ErrorMessage)
type FieldErrorMap = Map[FieldName, ErrorMessage]
object FieldErrorMap {
val empty = Map.empty[FieldName, ErrorMessage]
}
import Types._

// True - passed
def validateAgainstGroup(group: ValidationGroup, value: String): Boolean = {
Expand Down
Loading

0 comments on commit 3d82d4f

Please sign in to comment.