diff --git a/modules/core/shared/src/main/scala/eu/timepit/refined/string.scala b/modules/core/shared/src/main/scala/eu/timepit/refined/string.scala index 15f69cde0..d6578d05b 100644 --- a/modules/core/shared/src/main/scala/eu/timepit/refined/string.scala +++ b/modules/core/shared/src/main/scala/eu/timepit/refined/string.scala @@ -15,6 +15,9 @@ object string extends StringValidate with StringInference { /** Predicate that checks if a `String` ends with the suffix `S`. */ final case class EndsWith[S](s: S) + /** Predicate that checks if a `String` is a valid IPv4 */ + final case class IPv4() + /** Predicate that checks if a `String` matches the regular expression `S`. */ final case class MatchesRegex[S](s: S) @@ -40,14 +43,34 @@ object string extends StringValidate with StringInference { final case class XPath() } -private[refined] trait StringValidate { +object Predicates { + object IPv4 { + val regex = "^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$".r.pattern + val maxOctet = 255 + val predicate: String => Boolean = s => { + val matcher = regex.matcher(s) + matcher.find() && matcher.matches() && { + val octet1 = matcher.group(1).toInt + val octet2 = matcher.group(2).toInt + val octet3 = matcher.group(3).toInt + val octet4 = matcher.group(4).toInt + + (octet1 <= maxOctet) && (octet2 <= maxOctet) && (octet3 <= maxOctet) && (octet4 <= maxOctet) + } + } + } +} +private[refined] trait StringValidate { implicit def endsWithValidate[S <: String]( implicit ws: Witness.Aux[S]): Validate.Plain[String, EndsWith[S]] = Validate.fromPredicate(_.endsWith(ws.value), t => s""""$t".endsWith("${ws.value}")""", EndsWith(ws.value)) + implicit def ipv4Validate[S <: String]: Validate.Plain[String, IPv4] = + Validate.fromPredicate(Predicates.IPv4.predicate, t => s"${t} is a valid IPv4", IPv4()) + implicit def matchesRegexValidate[S <: String]( implicit ws: Witness.Aux[S]): Validate.Plain[String, MatchesRegex[S]] = Validate.fromPredicate(_.matches(ws.value), diff --git a/modules/core/shared/src/test/scala/eu/timepit/refined/StringValidateSpec.scala b/modules/core/shared/src/test/scala/eu/timepit/refined/StringValidateSpec.scala index eadc89589..9180efba7 100644 --- a/modules/core/shared/src/test/scala/eu/timepit/refined/StringValidateSpec.scala +++ b/modules/core/shared/src/test/scala/eu/timepit/refined/StringValidateSpec.scala @@ -69,4 +69,16 @@ class StringValidateSpec extends Properties("StringValidate") { property("Uuid.showResult.Failed") = secure { showResult[Uuid]("whops") ?= "Uuid predicate failed: Invalid UUID string: whops" } + + property("IPv4.isValid") = secure { + isValid[IPv4]("10.0.0.1") + } + + property("IPv4.showResult.InvalidOctet") = secure { + showResult[IPv4]("10.0.256.1") ?= "Predicate failed: 10.0.256.1 is a valid IPv4." + } + + property("IPv4.showResult.Failed") = secure { + showResult[IPv4]("::1") ?= "Predicate failed: ::1 is a valid IPv4." + } }