Skip to content

Commit

Permalink
Merge pull request #357 from NeQuissimus/ipv6
Browse files Browse the repository at this point in the history
Add IPv6 string validation
  • Loading branch information
fthomas authored Jan 22, 2018
2 parents 09b3f6c + 4a6c50b commit b18606a
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 1 deletion.
4 changes: 3 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,9 @@ lazy val moduleJvmSettings = Def.settings(
ProblemFilters.exclude[MissingClassProblem]("eu.timepit.refined.scalacheck.util.OurMath$"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("eu.timepit.refined.StringValidate.*"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("eu.timepit.refined.types.*"),
ProblemFilters.exclude[ReversedMissingMethodProblem]("eu.timepit.refined.NumericValidate.*")
ProblemFilters.exclude[ReversedMissingMethodProblem]("eu.timepit.refined.NumericValidate.*"),
ProblemFilters.exclude[ReversedMissingMethodProblem](
"eu.timepit.refined.StringValidate.ipv6Validate")
)
}
)
Expand Down
41 changes: 41 additions & 0 deletions modules/core/shared/src/main/scala/eu/timepit/refined/string.scala
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,44 @@ object string extends StringValidate with StringInference {
}
}

/** Predicate that checks if a `String` is a valid IPv6 */
final case class IPv6()
object IPv6 {
val ipv4Chars = "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])"
val ipv4 = s"(${ipv4Chars}\\.){3,3}${ipv4Chars}"
val ipv6Chars = "[0-9a-fA-F]{1,4}"

val ipv6Full = s"(${ipv6Chars}:){7,7}${ipv6Chars}" // 1:2:3:4:5:6:7:8

val ipv6Compact = List(
s"(${ipv6Chars}:){1,7}:", // 1:: .. 1:2:3:4:5:6:7::
s"(${ipv6Chars}:){1,6}:${ipv6Chars}", // 1::8 .. 1:2:3:4:5:6::8
s"(${ipv6Chars}:){1,5}(:${ipv6Chars}){1,2}", // 1::7:8 .. 1:2:3:4:5::8
s"(${ipv6Chars}:){1,4}(:${ipv6Chars}){1,3}", // 1::6:7:8 .. 1:2:3:4::8
s"(${ipv6Chars}:){1,3}(:${ipv6Chars}){1,4}", // 1::5:6:7:8 .. 1:2:3::8
s"(${ipv6Chars}:){1,2}(:${ipv6Chars}){1,5}", // 1::4:5:6:7:8 .. 1:2::8
s"(${ipv6Chars}:)(:${ipv6Chars}){1,6}", // 1::3:4:5:6:7:8 .. 1::8
s":((:${ipv6Chars}){1,7}|:)" // ::2:3:4:5:6:7:8 .. ::
)

val interface = "[0-9a-zA-Z]{1,}"
val ipv6LinkLocal = s"fe80:(:${ipv6Chars}){0,4}%${interface}"

val mappedIpv6 = s"::(ffff(:0{1,4}){0,1}:){0,1}${ipv4}"

val embeddedIpv4 = s"(${ipv6Chars}:){1,4}:${ipv4}"

val formats = List(
ipv6Full,
ipv6LinkLocal,
mappedIpv6,
embeddedIpv4
) ++ ipv6Compact

val regex = formats.map(regex => s"(^${regex}$$)").mkString("|").r.pattern
val predicate: String => Boolean = s => regex.matcher(s).matches
}

/** Predicate that checks if a `String` matches the regular expression `S`. */
final case class MatchesRegex[S](s: S)

Expand Down Expand Up @@ -83,6 +121,9 @@ private[refined] trait StringValidate {
implicit def ipv4Validate[S <: String]: Validate.Plain[String, IPv4] =
Validate.fromPredicate(IPv4.predicate, t => s"${t} is a valid IPv4", IPv4())

implicit def ipv6Validate[S <: String]: Validate.Plain[String, IPv6] =
Validate.fromPredicate(IPv6.predicate, t => s"${t} is a valid IPv6", IPv6())

implicit def matchesRegexValidate[S <: String](
implicit ws: Witness.Aux[S]): Validate.Plain[String, MatchesRegex[S]] =
Validate.fromPredicate(_.matches(ws.value),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,41 @@ class StringValidateSpec extends Properties("StringValidate") {
property("IPv4.showResult.Failed") = secure {
showResult[IPv4]("::1") ?= "Predicate failed: ::1 is a valid IPv4."
}
property("IPv6.isValid.full") = secure {
isValid[IPv6]("2001:0db8:85a3:0000:0000:8a2e:0370:7334")
}

property("IPv6.isValid.noLeadingZeros") = secure {
isValid[IPv6]("2001:db8:85a3:0:0:8a2e:370:7334")
}

property("IPv6.isValid.compact") = secure {
isValid[IPv6]("2001:db8:85a3::8a2e:370:7334")
}

property("IPv6.isValid.local") = secure {
isValid[IPv6]("::1")
}

property("IPv6.isValid.linkLocal") = secure {
isValid[IPv6]("fe80::7:8%eth0")
}

property("IPv6.isValid.mapped") = secure {
isValid[IPv6]("::ffff:255.255.255.255")
}

property("IPv6.isValid.embedded") = secure {
isValid[IPv6]("2001:db8:122:344::192.0.2.33")
}

property("IPv6.showResult.Failed.Random") = secure {
showResult[IPv6]("foo") ?= "Predicate failed: foo is a valid IPv6."
}

property("IPv6.showResult.Failed.DoubleCompact") = secure {
showResult[IPv6]("2001::0::1234") ?= "Predicate failed: 2001::0::1234 is a valid IPv6."
}

private def validNumber[N: Arbitrary, P](name: String, invalidValue: String)(
implicit v: Validate[String, P]) = {
Expand Down

0 comments on commit b18606a

Please sign in to comment.