From 0480ab37103bf21972b0a8daeaaacc93e15c413f Mon Sep 17 00:00:00 2001 From: "dmitry.shakhtarin" Date: Wed, 20 Jun 2018 17:45:26 +0300 Subject: [PATCH] add travis, add custom grpc protocol to list, small refactoring and bug fixes, add strict int type for parameters of TCP and UDP protocol --- .travis.yml | 19 +++++++ build.sbt | 1 + .../scala/fluence/multiaddr/Multiaddr.scala | 24 +++++---- .../fluence/multiaddr/MultiaddrParser.scala | 50 ++++++++++++++----- .../fluence/multiaddr/ProtoParameter.scala | 25 ++++++++++ .../scala/fluence/multiaddr/Protocol.scala | 3 ++ .../multiaddr/MultiaddrParseSpec.scala | 36 ++++++++----- 7 files changed, 124 insertions(+), 34 deletions(-) create mode 100644 .travis.yml create mode 100644 core/src/main/scala/fluence/multiaddr/ProtoParameter.scala diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..571230d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +sudo: required + +language: scala +scala: + - 2.12.5 +jdk: + - oraclejdk8 + +# These directories are cached to S3 at the end of the build +cache: + directories: + - $HOME/.ivy2/cache + - $HOME/.sbt/boot + - $HOME/.sbt/launchers + +before_cache: + # Tricks to avoid unnecessary cache updates + - find $HOME/.sbt -name "*.lock" | xargs rm + - find $HOME/.ivy2 -name "ivydata-*.properties" | xargs rm \ No newline at end of file diff --git a/build.sbt b/build.sbt index ef9e307..03250ef 100644 --- a/build.sbt +++ b/build.sbt @@ -39,6 +39,7 @@ lazy val `scala-multiaddr-core` = crossProject(JVMPlatform, JSPlatform) commons, libraryDependencies ++= Seq( "com.beachape" %%% "enumeratum" % "1.5.13", +// "org.typelevel" %%% "cats-core" % "1.1.0", "org.scalatest" %%% "scalatest" % "3.0.+" % Test ) ) diff --git a/core/src/main/scala/fluence/multiaddr/Multiaddr.scala b/core/src/main/scala/fluence/multiaddr/Multiaddr.scala index e5bce06..57db5e4 100644 --- a/core/src/main/scala/fluence/multiaddr/Multiaddr.scala +++ b/core/src/main/scala/fluence/multiaddr/Multiaddr.scala @@ -17,16 +17,18 @@ package fluence.multiaddr -case class Multiaddr private(stringAddress: String, protocolsWithParameters: List[(Protocol, Option[String])]) { - override def toString: String = stringAddress +import fluence.multiaddr.Multiaddr.ErrorMessage + +case class Multiaddr private(address: String, protoParameters: List[ProtoParameter]) { + override def toString: String = address /** * Wraps a given Multiaddr, returning the resulting joined Multiaddr. * * @return new joined Multiaddr */ - def encapsulate(addr: Multiaddr): Either[Throwable, Multiaddr] = { - Multiaddr(stringAddress + addr.toString) + def encapsulate(addr: Multiaddr): Either[ErrorMessage, Multiaddr] = { + Multiaddr(address + addr.toString) } /** @@ -34,20 +36,22 @@ case class Multiaddr private(stringAddress: String, protocolsWithParameters: Lis * * @return decapsulated Multiaddr */ - def decapsulate(addr: Multiaddr): Either[Throwable, Multiaddr] = { + def decapsulate(addr: Multiaddr): Either[ErrorMessage, Multiaddr] = { val strAddr = addr.toString - val lastIndex = stringAddress.lastIndexOf(strAddr) + val lastIndex = address.lastIndexOf(strAddr) if (lastIndex < 0) - Right(this.copy()) + Right(this) else - Multiaddr(stringAddress.slice(0, lastIndex)) + Multiaddr(address.slice(0, lastIndex)) } } object Multiaddr { - def apply(addr: String): Either[Throwable, Multiaddr] = MultiaddrParser.parse(addr).map { - case (trimmed, protocols) => new Multiaddr(trimmed, protocols) + type ErrorMessage = String + + def apply(addr: String): Either[ErrorMessage, Multiaddr] = MultiaddrParser.parse(addr).map { + case (trimmed, protoParameters) => new Multiaddr(trimmed, protoParameters) } } diff --git a/core/src/main/scala/fluence/multiaddr/MultiaddrParser.scala b/core/src/main/scala/fluence/multiaddr/MultiaddrParser.scala index 05d663a..fc4a13e 100644 --- a/core/src/main/scala/fluence/multiaddr/MultiaddrParser.scala +++ b/core/src/main/scala/fluence/multiaddr/MultiaddrParser.scala @@ -17,18 +17,22 @@ package fluence.multiaddr +import fluence.multiaddr.Multiaddr.ErrorMessage +import fluence.multiaddr.Protocol._ + import scala.annotation.tailrec +import scala.util.Try private[multiaddr] object MultiaddrParser { - def parse(addr: String): Either[Throwable, (String, List[(Protocol, Option[String])])] = { + def parse(addr: String): Either[ErrorMessage, (String, List[ProtoParameter])] = { if (!addr.startsWith("/")) { - Left(new IllegalArgumentException("Address must be started with '/'.")) + Left("Address must be started with '/'.") } else { val parts = addr.stripPrefix("/").stripSuffix("/").split("/").toList if (parts.isEmpty) { - Left(new IllegalArgumentException("Address must be non-empty.")) + Left("Address must be non-empty.") } else { parsePrepared(parts).map(protocols ⇒ (addr.stripSuffix("/"), protocols)) @@ -36,31 +40,53 @@ private[multiaddr] object MultiaddrParser { } } - private def parsePrepared(list: List[String]): Either[Throwable, List[(Protocol, Option[String])]] = { + private def parseParameter(parameter: String, protocol: Protocol): Either[String, ProtoParameter] = { + protocol match { + case TCP | UDP ⇒ + Try(parameter.toInt).toEither.right + .map(n ⇒ IntProtoParameter(protocol, n)) + .left + .map(_ ⇒ s"Parameter for protocol $protocol must be a number.") + case _ ⇒ + Right(StringProtoParameter(protocol, parameter)) + } + } + + private def parsePrepared(list: List[String]): Either[ErrorMessage, List[ProtoParameter]] = { @tailrec def parseRec( list: List[String], - res: Either[Throwable, List[(Protocol, Option[String])]] - ): Either[Throwable, List[(Protocol, Option[String])]] = { + accum: Either[ErrorMessage, List[ProtoParameter]] + ): Either[ErrorMessage, List[ProtoParameter]] = { list match { - case Nil ⇒ res + case Nil ⇒ accum case head :: tail ⇒ //todo per-protocol validation val protocolOp = Protocol.withNameOption(head) protocolOp match { case None ⇒ - Left(new IllegalArgumentException(s"There is no protocol with name '$head'.")) + Left(s"There is no protocol with name '$head'.") case Some(protocol) ⇒ protocol.size match { - case 0 ⇒ parseRec(tail, res.map(els ⇒ els :+ (protocol, None))) + case 0 ⇒ + parseRec(tail, accum.map(els ⇒ els :+ EmptyProtoParameter(protocol))) case _ ⇒ tail match { case Nil ⇒ - Left(new IllegalArgumentException(s"There is no parameter for protocol with name '$head'.")) - case innerHead :: innerTail ⇒ - parseRec(innerTail, res.map(els ⇒ els :+ (protocol, Some(innerHead)))) + Left(s"There is no parameter for protocol with name '$head'.") + case parameter :: innerTail ⇒ + val partialResult = + for { + elements ← accum + parameter ← parseParameter(parameter, protocol) + } yield elements :+ parameter + + parseRec( + innerTail, + partialResult + ) } } } diff --git a/core/src/main/scala/fluence/multiaddr/ProtoParameter.scala b/core/src/main/scala/fluence/multiaddr/ProtoParameter.scala new file mode 100644 index 0000000..525bbec --- /dev/null +++ b/core/src/main/scala/fluence/multiaddr/ProtoParameter.scala @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2017 Fluence Labs Limited + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package fluence.multiaddr + +sealed trait ProtoParameter + +//todo generalize parameters per protocol type +case class EmptyProtoParameter(protocol: Protocol) extends ProtoParameter +case class StringProtoParameter(protocol: Protocol, parameter: String) extends ProtoParameter +case class IntProtoParameter(protocol: Protocol, parameter: Int) extends ProtoParameter diff --git a/core/src/main/scala/fluence/multiaddr/Protocol.scala b/core/src/main/scala/fluence/multiaddr/Protocol.scala index 284fb62..ab422f6 100644 --- a/core/src/main/scala/fluence/multiaddr/Protocol.scala +++ b/core/src/main/scala/fluence/multiaddr/Protocol.scala @@ -61,4 +61,7 @@ object Protocol extends Enum[Protocol] { case object P2PWebrtcStar extends Protocol(275, 0, "p2p-webrtc-star") case object P2PWebrtcDirect extends Protocol(276, 0, "p2p-webrtc-direct") case object P2PCircuit extends Protocol(290, 0, "p2p-circuit") + + //custom + case object GRPC extends Protocol(1001, 0, "grpc") } diff --git a/core/src/test/scala/fluence/multiaddr/MultiaddrParseSpec.scala b/core/src/test/scala/fluence/multiaddr/MultiaddrParseSpec.scala index 05603ba..7511699 100644 --- a/core/src/test/scala/fluence/multiaddr/MultiaddrParseSpec.scala +++ b/core/src/test/scala/fluence/multiaddr/MultiaddrParseSpec.scala @@ -27,7 +27,7 @@ class MultiaddrParseSpec extends WordSpec with Matchers { "throw exception if there is no leading '/'" in { val m = Multiaddr("ip4/127.0.0.1/tcp/123") m.isLeft shouldBe true - m.left.get.getMessage shouldBe "Address must be started with '/'." + m.left.get shouldBe "Address must be started with '/'." } "parse correct multiaddresses right" in { @@ -35,27 +35,33 @@ class MultiaddrParseSpec extends WordSpec with Matchers { val m1Either = Multiaddr(addr1) m1Either.isRight shouldBe true val m1 = m1Either.right.get - m1.protocolsWithParameters shouldBe List((IP4, Some("127.0.0.1")), (TCP, Some("123"))) - m1.stringAddress shouldBe addr1 + + m1.protoParameters shouldBe List(StringProtoParameter(IP4, "127.0.0.1"), IntProtoParameter(TCP, 123)) + m1.address shouldBe addr1 val addr2 = "/ip6/2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095/udp/5000/https" val m2Either = Multiaddr(addr2) m2Either.isRight shouldBe true val m2 = m2Either.right.get - m2.protocolsWithParameters shouldBe List((IP6, Some("2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095")), (UDP, Some("5000")), (HTTPS, None)) - m2.stringAddress shouldBe addr2 + + m2.protoParameters shouldBe List( + StringProtoParameter(IP6, "2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095"), + IntProtoParameter(UDP, 5000), + EmptyProtoParameter(HTTPS) + ) + m2.address shouldBe addr2 } "throw exception if there is no protocol" in { val m = Multiaddr("/ip4/127.0.0.1/tc/123") m.isLeft shouldBe true - m.left.get.getMessage shouldBe "There is no protocol with name 'tc'." + m.left.get shouldBe "There is no protocol with name 'tc'." } "throw exception if there is no parameter in protocol with parameter" in { val m = Multiaddr("/ip4/127.0.0.1/tcp/") m.isLeft shouldBe true - m.left.get.getMessage shouldBe "There is no parameter for protocol with name 'tcp'." + m.left.get shouldBe "There is no parameter for protocol with name 'tcp'." } "encapsulate and decapsulate correct multiaddr" in { @@ -73,11 +79,17 @@ class MultiaddrParseSpec extends WordSpec with Matchers { m3Either.isRight shouldBe true val m3 = m3Either.right.get - val result = List((IP4, Some("127.0.0.1")), (TCP, Some("123")), (IP6, Some("2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095")), (UDP, Some("5000")), (HTTPS, None)) - m3.protocolsWithParameters shouldBe result - m3.stringAddress shouldBe (addr1 + addr2) + val result = List( + StringProtoParameter(IP4, "127.0.0.1"), + IntProtoParameter(TCP, 123), + StringProtoParameter(IP6, "2001:8a0:7ac5:4201:3ac9:86ff:fe31:7095"), + IntProtoParameter(UDP, 5000), + EmptyProtoParameter(HTTPS) + ) + m3.protoParameters shouldBe result + m3.address shouldBe (addr1 + addr2) - m3.decapsulate(m2).right.get.stringAddress shouldBe addr1 + m3.decapsulate(m2).right.get.address shouldBe addr1 } "decapsulate correct multiaddr" in { @@ -87,7 +99,7 @@ class MultiaddrParseSpec extends WordSpec with Matchers { val m1 = m1Either.right.get val decapsulated = m1.decapsulate(Multiaddr("/sctp/5678").right.get).right.get - decapsulated.stringAddress shouldBe "/ip4/127.0.0.1/udp/1234" + decapsulated.address shouldBe "/ip4/127.0.0.1/udp/1234" } } }