adds service name parser (copied from akka-management) (#26132)

* adds service name parser

* Apply suggestions from code review

Co-Authored-By: renatocaval <renato@cavalcanti.be>

* dangling parenthesis on last line

* code formatting

* extended tests for Lookup.isValidSrv

* make it illegal to create Lookups with empty or null service name

* improved tests description

* improved error messages

* formatting

* updated copyright header

* removed weird import

* removes trailing dot

* throw NPE when null is passed
This commit is contained in:
Renato Cavalcanti 2019-01-13 18:37:36 +01:00 committed by Patrik Nordwall
parent 5664f4ae88
commit 6f0ea1257e
2 changed files with 153 additions and 0 deletions

View file

@ -128,6 +128,7 @@ object ServiceDiscovery {
* For example `portName` could be used to distinguish between
* Akka remoting ports and HTTP ports.
*
* @throws IllegalArgumentException if [[serviceName]] is 'null' or an empty String
*/
@ApiMayChange
final class Lookup(
@ -135,6 +136,9 @@ final class Lookup(
val portName: Option[String],
val protocol: Option[String]) {
require(serviceName != null, "'serviceName' cannot be null")
require(serviceName.trim.nonEmpty, "'serviceName' cannot be empty")
/**
* Which port for a service e.g. Akka remoting or HTTP.
* Maps to "service" for an SRV records.
@ -194,6 +198,47 @@ case object Lookup {
* and protocol
*/
def create(serviceName: String): Lookup = new Lookup(serviceName, None, None)
private val SrvQuery = """^_(.+?)\._(.+?)\.(.+?)$""".r
private val DomainName = "^((?!-)[A-Za-z0-9-]{1,63}(?<!-)\\.)+[A-Za-z]{2,6}$".r
/**
* Create a service Lookup from a string with format:
* _portName._protocol.serviceName.
* (as specified by https://www.ietf.org/rfc/rfc2782.txt)
*
* If the passed string conforms with this format, a SRV Lookup is returned.
* The serviceName part must be a valid domain name.
*
* The string is parsed and dismembered to build a Lookup as following:
* Lookup(serviceName).withPortName(portName).withProtocol(protocol)
*
*
* @throws NullPointerException If the passed string is null
* @throws IllegalArgumentException If the string doesn't not conform with the SRV format
*/
def parseSrv(str: String): Lookup =
str match {
case SrvQuery(portName, protocol, serviceName) if validDomainName(serviceName)
Lookup(serviceName).withPortName(portName).withProtocol(protocol)
case null throw new NullPointerException("Unable to create Lookup from passed SRV string. Passed value is 'null'")
case _ throw new IllegalArgumentException(s"Unable to create Lookup from passed SRV string, invalid format: $str")
}
/**
* Returns true if passed string conforms with SRV format. Otherwise returns false.
*/
def isValidSrv(srv: String): Boolean =
srv match {
case SrvQuery(_, _, serviceName) validDomainName(serviceName)
case _ false
}
private def validDomainName(name: String): Boolean =
DomainName.pattern.asPredicate().test(name)
}
/**

View file

@ -0,0 +1,108 @@
/*
* Copyright (C) 2017-2019 Lightbend Inc. <https://www.lightbend.com>
*/
package akka.discovery
import org.scalatest.{ Matchers, OptionValues, WordSpec }
class LookupSpec extends WordSpec with Matchers with OptionValues {
// SRV strings with invalid domain names
// should fail to build lookups
val srvWithInvalidDomainNames = List(
"_portName._protocol.service_name.local",
"_portName._protocol.servicename,local",
"_portName._protocol.servicename.local-",
"_portName._protocol.-servicename.local")
// No SRV that should result in simple A/AAAA lookups
val noSrvLookups = List(
"portName.protocol.serviceName.local",
"serviceName.local",
"_portName.serviceName",
"_serviceName.local",
"_serviceName,local",
"-serviceName.local",
"serviceName.local-")
"Lookup.parseSrv" should {
"generate a SRV Lookup from a SRV String" in {
val name = "_portName._protocol.serviceName.local"
val lookup = Lookup.parseSrv(name)
lookup.serviceName shouldBe "serviceName.local"
lookup.portName.value shouldBe "portName"
lookup.protocol.value shouldBe "protocol"
}
"throw an IllegalArgumentException when passing a 'null' SRV String" in {
assertThrows[NullPointerException] {
Lookup.parseSrv(null)
}
}
"throw an IllegalArgumentException when passing an empty SRV String" in {
assertThrows[IllegalArgumentException] {
Lookup.parseSrv("")
}
}
"throw an IllegalArgumentException for any non-conforming SRV String" in {
noSrvLookups.foreach { str
withClue(s"parsing '$str'") {
assertThrows[IllegalArgumentException] {
Lookup.parseSrv(str)
}
}
}
}
"throw an IllegalArgumentException for any SRV with invalid domain names" in {
srvWithInvalidDomainNames.foreach { str
withClue(s"parsing '$str'") {
assertThrows[IllegalArgumentException] {
Lookup.parseSrv(str)
}
}
}
}
}
"Lookup.isValidSrv" should {
"return true for any conforming SRV String" in {
Lookup.isValidSrv("_portName._protocol.serviceName.local") shouldBe true
}
"return false for any non-conforming SRV String" in {
noSrvLookups.foreach { str
withClue(s"checking '$str'") {
Lookup.isValidSrv(str) shouldBe false
}
}
}
"return false if domain part in SRV String is an invalid domain name" in {
srvWithInvalidDomainNames.foreach { str
withClue(s"checking '$str'") {
Lookup.isValidSrv(str) shouldBe false
}
}
}
"return false for empty SRV String" in {
Lookup.isValidSrv("") shouldBe false
}
"return false for 'null' SRV String" in {
Lookup.isValidSrv(null) shouldBe false
}
"return true for a SRV with valid domain name" in {
Lookup.isValidSrv("_portName._protocol.serviceName.local") shouldBe true
}
}
}