Template Service
π
Scala 3
β
β
SMS
text: "Hello %name%, your code is %code%"
params:
- name
- codesubject: 'Booking completed'
external_id: '100122055500'
params:
- name
- dateheading: 'New discount'
text: 'Check your discounts tab'
link: 'acme://discounts'
category: 'offers'Push
owner: '@booking-team'
---
subject: 'Booking completed'
external_id: '100122055500'
params:
- name
- amountowner: '@offers-team'
---
heading: 'New discount'
text: 'Check your discounts tab'
link: 'acme://discounts'
category: 'offers'Push
SMS
owner: '@auth-team'
users:
- '@booking-team'
- '@reservations'
reference: 'https://acme.org/XYZ-1234'
---
text: "Hello %name%, your code is %code%"
params:
- name
- codecase class PushTemplate(
heading: String,
text: String,
link: Option[String],
category: Option[String],
params: Option[List[String]],
owner: String,
users: Option[List[String]]
reference: Option[String]
)def parseFile(fileContent: String): Either[io.circe.Error, PushTemplate] =
io.circe.parser.decode[PushTemplate](fileContent)case class PushTemplate(
heading: String,
text: String,
link: Option[String],
category: Option[String],
params: Option[List[String]],
owner: String,
users: Option[List[String]]
reference: Option[String]
)heading: 'New discount'
text: 'Check your discounts tab'
owner: '@auth-team'β
π
def parseFile(fileContent: String): Either[io.circe.Error, PushTemplate] =
io.circe.parser.decode[PushTemplate](fileContent)case class PushTemplate(
heading: String,
text: String,
link: Option[String],
category: Option[String],
params: Option[List[String]],
owner: String,
users: Option[List[String]]
reference: Option[String]
)text: 'Check your discounts tab'
owner: '@billing-team'π
β
def parseFile(fileContent: String): Either[io.circe.Error, PushTemplate] =
io.circe.parser.decode[PushTemplate](fileContent)case class PushTemplate(
//...
heading: String,
//...
)heading: ''
text: 'Check your discounts tab'
owner: '@billing-team'π
β
case class PushTemplate(
//...
heading: String :| Not[Blank],
//...
)def parseFile(fileContent: String): Either[io.circe.Error, PushTemplate] =
io.circe.parser.decode[PushTemplate](fileContent)heading: ''
text: 'Check your discounts tab'
owner: '@billing-team'π
β
case class PushTemplate(
//...
reference: Option[String],
//...
)π
β
heading: 'New discount'
text: 'Check your discounts tab'
owner: '@billing-team'
reference: 'TBD'case class PushTemplate(
//...
reference: Option[String],
//...
)type Url = String :| ValidURLcase class PushTemplate(
//...
reference: Option[String],
//...
)π
β
heading: 'New discount'
text: 'Check your discounts tab'
owner: '@billing-team'
reference: 'TBD'case class PushTemplate(
//...
reference: Option[Url],
//...
)//https://github.com/Iltotore/iron/blob/main/main/src/io/github/iltotore/iron/constraint/string.scala
type ValidURL =
Match[
"((\\w+:)+\\/\\/)?(([-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6})|(localhost))(:\\d{1,5})?(\\/|\\/([-a-zA-Z0-9@:%_\\+.~#?&//=]*))?"
] DescribedAs "Should be a URL"case class PushTemplate(
//...
reference: Option[String],
//...
)π
β
heading: 'New discount'
text: 'Check your discounts tab'
owner: '@billing-team'
link: 'acme://statement'case class PushTemplate(
//...
link: Option[Url],
//...
)case class PushTemplate(
//...
reference: Option[String],
//...
)π
β
heading: 'New discount'
text: 'Check your discounts tab'
owner: '@billing-team'
link: 'acme://statement'case class PushTemplate(
//...
link: Option[Link],
//...
)type Link = String :| DescribedAs[
ValidURL | Match["^acme://.*$"],
"Should be a valid deep link or URL"
]
case class PushTemplate(
//...
reference: Option[String],
//...
)π
β
heading: 'New discount'
text: 'Check your discounts tab'
owner: '@billing-team'
category: 'default'case class PushTemplate(
//...
category: Option[String],
//...
)val PossibleCategories = Seq("general", "offers")case class PushTemplate(
//...
reference: Option[String],
//...
)π
β
heading: 'New discount'
text: 'Check your discounts tab'
owner: '@billing-team'
category: 'default'case class PushTemplate(
//...
category: Option[PushCategory],
//...
)final class ExistingPushCategory
given Constraint[String, ExistingPushCategory] with {
override inline def test(value: String): Boolean = PossibleCategories.contains(value)
override inline def message: String = "Should be general or offers"
}type PushCategory = String :| ExistingPushCategoryval PossibleCategories = Seq("general", "offers")case class PushTemplate(
//...
reference: Option[String],
//...
)π
β
heading: 'New discount'
text: 'Check your discounts tab'
owner: 'Billing Team'case class PushTemplate(
//...
owner: String,
//...
)case class PushTemplate(
//...
reference: Option[String],
//...
)π
β
heading: 'New discount'
text: 'Check your discounts tab'
owner: 'Billing Team'case class PushTemplate(
//...
owner: SlackHandle,
//...
)type SlackHandle = String :| ValidSlackHandlefinal class ValidSlackHandle
given Constraint[String, ValidSlackHandle] with {
override inline def test(value: String): Boolean = Team.slackHandles().contains(value)
override inline def message: String = "Should be a valid Slack handle"
}enum Team(val name: String, val slackHandle: String, val slackId: String) {
case Operations extends Team("Operations", "@operations-team", "S01231W2333")
case Payments extends Team("Payments and Billing", "@payments", "S1239R123")
case QA extends Team("QA", "@quality", "S123P321")
}
object Team {
def slackHandles(): Set[String] = Team.values.map(_.slackHandle).toSet
}def parseFile(
fileContent: String
): Either[io.circe.Error, PushTemplate] =
io.circe.parser.decode[PushTemplate](fileContent)heading: ''
owner: 'Billing Team'
category: 'default'
reference: 'TBD'def parseFile(
fileContent: String
): ValidatedNel[io.circe.Error, PushTemplate] = {
io.circe.parser.decodeAccumulating[PushTemplate](fileContent)
}push/production/new_offer.yamldef validateAllFiles(
files: List[File]
): ValidatedNel[ValidationError, List[PushTemplate]] = {
List(
validateSchema(files),
validateFileNameConvention(files),
validateStagingExists(files)
).reduce(_ |+| _)
}validateAllFiles(files) match {
case Valid(files) => //succeed
case Invalid(errors) => //fail
}