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:
parent
5664f4ae88
commit
6f0ea1257e
2 changed files with 153 additions and 0 deletions
|
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
108
akka-discovery/src/test/scala/akka/discovery/LookupSpec.scala
Normal file
108
akka-discovery/src/test/scala/akka/discovery/LookupSpec.scala
Normal 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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue