diff --git a/auth/src/it/scala/janstenpickle/vault/auth/UserPassIT.scala b/auth/src/it/scala/janstenpickle/vault/auth/UserPassIT.scala index 7d9c8c7..e654e58 100644 --- a/auth/src/it/scala/janstenpickle/vault/auth/UserPassIT.scala +++ b/auth/src/it/scala/janstenpickle/vault/auth/UserPassIT.scala @@ -1,5 +1,6 @@ package janstenpickle.vault.auth +import janstenpickle.scala.syntax.AsyncResultSyntax._ import janstenpickle.vault.core.VaultSpec import janstenpickle.vault.manage.Auth import org.scalacheck.{Gen, Prop} @@ -23,20 +24,20 @@ class UserPassIT extends VaultSpec with ScalaCheck { lazy val userAdmin = janstenpickle.vault.manage.UserPass(config) def setupClient(client: String) = authAdmin.enable("userpass", Some(client)) - .attemptRun(_.getMessage()) must beOk + .attemptRun must beRight def setupUser(username: String, password: String, client: String) = userAdmin.create(username, password, 30, None, client) - .attemptRun(_.getMessage()) + .attemptRun def removeClient(client: String) = - authAdmin.disable(client).attemptRun(_.getMessage()) must beOk + authAdmin.disable(client).attemptRun must beRight def authPass = test((username, password, client, ttl) => setupClient(client) and - (setupUser(username, password, client) must beOk) and + (setupUser(username, password, client) must beRight) and (underTest.authenticate(username, password, ttl, client) - .attemptRun(_.getMessage()) must beOk) and + .attemptRun must beRight) and removeClient(client) ) @@ -45,27 +46,27 @@ class UserPassIT extends VaultSpec with ScalaCheck { def badClient = test{ (username, password, client, ttl) => val badClientName = "nic-kim-cage-client" setupClient(badClientName) and - (setupUser(username, password, client) must beFail) and + (setupUser(username, password, client) must beLeft) and (underTest.authenticate(username, password, ttl, client) - .attemptRun(_.getMessage()) must beFail) and + .attemptRun must beLeft) and removeClient(badClientName) } def badUser = test{ (username, password, client, ttl) => val badUserName = "nic-kim-cage-user" setupClient(client) and - (setupUser(username, password, client) must beOk) and + (setupUser(username, password, client) must beRight) and (underTest.authenticate(badUserName, password, ttl, client) - .attemptRun(_.getMessage()) must beFail) and + .attemptRun must beLeft) and removeClient(client) } def badPassword = test{ (username, password, client, ttl) => val badPasswordValue = "nic-kim-cage-password" setupClient(client) and - (setupUser(username, password, client) must beOk) and + (setupUser(username, password, client) must beRight) and (underTest.authenticate(username, badPasswordValue, ttl, client) - .attemptRun(_.getMessage()) must beFail) and + .attemptRun must beLeft) and removeClient(client) } diff --git a/auth/src/main/scala/janstenpickle/vault/auth/UserPass.scala b/auth/src/main/scala/janstenpickle/vault/auth/UserPass.scala index daa4c6a..11c636a 100644 --- a/auth/src/main/scala/janstenpickle/vault/auth/UserPass.scala +++ b/auth/src/main/scala/janstenpickle/vault/auth/UserPass.scala @@ -5,7 +5,7 @@ import janstenpickle.scala.syntax.AsyncResultSyntax._ import janstenpickle.scala.syntax.SyntaxRequest._ import janstenpickle.scala.syntax.ResponseSyntax._ import janstenpickle.vault.core.WSClient -import uscala.concurrent.result.AsyncResult +import janstenpickle.scala.result._ import scala.concurrent.ExecutionContext @@ -16,7 +16,7 @@ case class UserPass(wsClient: WSClient) { password: String, ttl: Int, client: String = "userpass" - )(implicit ec: ExecutionContext): AsyncResult[String, UserPassResponse] = + )(implicit ec: ExecutionContext): AsyncResult[UserPassResponse] = wsClient.path(s"auth/$client/login/$username"). post(Map("password" -> password, "ttl" -> s"${ttl}s")). toAsyncResult. diff --git a/build.sbt b/build.sbt index a665c01..f72f7a6 100644 --- a/build.sbt +++ b/build.sbt @@ -2,9 +2,12 @@ import sbt.Keys._ name := "vault" -lazy val uscalaVersion = "0.5.1" +lazy val scalaVersion211 = "2.11.11" +lazy val scalaVersion212 = "2.12.4" + lazy val specs2Version = "3.8.8" -lazy val circeVersion = "0.7.0" +lazy val catsVersion = "1.0.1" +lazy val circeVersion = "0.9.0" lazy val dispatchVersion = "0.11.3" lazy val startVaultTask = TaskKey[Unit]( "startVaultTask", @@ -38,7 +41,8 @@ val pomInfo = ( lazy val commonSettings = Seq( version := "0.4.2-SNAPSHOT", - scalaVersion := "2.11.11", + scalaVersion := scalaVersion211, + crossScalaVersions := Seq(scalaVersion211, scalaVersion212), organization := "janstenpickle.vault", pomExtra := pomInfo, autoAPIMappings := true, @@ -50,11 +54,9 @@ lazy val commonSettings = Seq( url("https://github.com/janstenpickle/scala-vault/blob/master/LICENSE") ), resolvers ++= Seq(Resolver.sonatypeRepo("releases"), Resolver.jcenterRepo), + libraryDependencies += "org.typelevel" %% "cats-core" % catsVersion, libraryDependencies ++= Seq( "net.databinder.dispatch" %% "dispatch-core" % dispatchVersion, - "org.uscala" %% "uscala-result" % uscalaVersion, - "org.uscala" %% "uscala-result-async" % uscalaVersion, - "org.uscala" %% "uscala-result-specs2" % uscalaVersion % "it,test", "org.specs2" %% "specs2-core" % specs2Version % "it,test", "org.specs2" %% "specs2-scalacheck" % specs2Version % "it,test", "org.specs2" %% "specs2-junit" % specs2Version % "it,test" @@ -77,7 +79,9 @@ lazy val commonSettings = Seq( "-unchecked", "-deprecation", "-feature", - "-language:implicitConversions"), + "-language:implicitConversions", + "-Ypartial-unification" + ), javacOptions in Compile ++= Seq( "-source", "1.8", "-target", "1.8", diff --git a/core/src/it/scala/janstenpickle/vault/core/SecretsIT.scala b/core/src/it/scala/janstenpickle/vault/core/SecretsIT.scala index 7cbdca1..9873a50 100644 --- a/core/src/it/scala/janstenpickle/vault/core/SecretsIT.scala +++ b/core/src/it/scala/janstenpickle/vault/core/SecretsIT.scala @@ -1,5 +1,7 @@ package janstenpickle.vault.core +import janstenpickle.scala.syntax.AsyncResultSyntax._ +import janstenpickle.scala.result._ import org.scalacheck.Prop import org.specs2.ScalaCheck @@ -33,20 +35,20 @@ trait SecretsTests extends VaultSpec with ScalaCheck { lazy val badServer = Secrets(badServerConfig, backend) def set = Prop.forAllNoShrink(strGen, strGen) { (key, value) => - good.set(key, value).attemptRun(_.getMessage()) must beOk + good.set(key, value).attemptRun must beRight } def get = Prop.forAllNoShrink(strGen, strGen) { (key, value) => - (good.set(key, value).attemptRun(_.getMessage()) must beOk) and - (good.get(key).attemptRun(_.getMessage()) must beOk.like { + (good.set(key, value).attemptRun must beRight) and + (good.get(key).attemptRun must beRight.like { case a => a === value }) } def list = Prop.forAllNoShrink(strGen, strGen, strGen) { (key1, key2, value) => - (good.set(key1, value).attemptRun(_.getMessage()) must beOk) and - (good.set(key2, value).attemptRun(_.getMessage()) must beOk) and - (good.list.attemptRun(_.getMessage()) must beOk[List[String]].like { + (good.set(key1, value).attemptRun must beRight) and + (good.set(key2, value).attemptRun must beRight) and + (good.list.attemptRun must beRight[List[String]].like { case a => a must containAllOf(Seq(key1, key2)) }) } @@ -56,29 +58,29 @@ trait SecretsTests extends VaultSpec with ScalaCheck { good.set( "nicolas-cage", Map(key1 -> value1, key2 -> value2) - ).attemptRun(_.getMessage()) must beOk + ).attemptRun must beRight } def getSetMulti = Prop.forAllNoShrink( strGen, strGen, strGen, strGen, strGen ) { (key1, key2, value1, value2, mainKey) => val testData = Map(key1 -> value1, key2 -> value2) - (good.set(mainKey, testData).attemptRun(_.getMessage()) must beOk) and - (good.getAll(mainKey).attemptRun(_.getMessage()) must beOk.like { + (good.set(mainKey, testData).attemptRun must beRight) and + (good.getAll(mainKey).attemptRun must beRight.like { case a => a === testData }) } - def failGet = good.get("john").attemptRun(_.getMessage()) must beFail. + def failGet = good.get("john").attemptRun must beLeft. like { case err => err must contain("Received failure response from server: 404") } def failSetBadServer = badServer.set( "nic", "cage" - ).attemptRun(_.getMessage()) must beFail + ).attemptRun must beLeft def failSetBadToken = badToken.set( "nic", "cage" - ).attemptRun(_.getMessage()) must beFail + ).attemptRun must beLeft } diff --git a/core/src/it/scala/janstenpickle/vault/core/VaultSpec.scala b/core/src/it/scala/janstenpickle/vault/core/VaultSpec.scala index 43f31cd..77b08a9 100644 --- a/core/src/it/scala/janstenpickle/vault/core/VaultSpec.scala +++ b/core/src/it/scala/janstenpickle/vault/core/VaultSpec.scala @@ -2,25 +2,27 @@ package janstenpickle.vault.core import java.net.URL +import janstenpickle.scala.result._ +import janstenpickle.scala.syntax.AsyncResultSyntax._ import janstenpickle.scala.syntax.SyntaxRequest._ import janstenpickle.scala.syntax.ResponseSyntax._ import janstenpickle.scala.syntax.VaultConfigSyntax._ import org.scalacheck.Gen import org.specs2.Specification +import org.specs2.matcher.EitherMatchers import org.specs2.specification.core.Fragments -import uscala.result.specs2.ResultMatchers import scala.concurrent.ExecutionContext import scala.io.Source -trait VaultSpec extends Specification with ResultMatchers { +trait VaultSpec extends Specification with EitherMatchers { implicit val errConverter: Throwable => String = _.getMessage implicit val ec: ExecutionContext = ExecutionContext.global - lazy val rootToken = Source.fromFile("/tmp/.root-token").mkString.trim - lazy val roleId = Source.fromFile("/tmp/.role-id").mkString.trim - lazy val secretId = Source.fromFile("/tmp/.secret-id").mkString.trim + lazy val rootToken: String = Source.fromFile("/tmp/.root-token").mkString.trim + lazy val roleId: String = Source.fromFile("/tmp/.role-id").mkString.trim + lazy val secretId: String = Source.fromFile("/tmp/.secret-id").mkString.trim lazy val rootConfig: VaultConfig = VaultConfig( WSClient(new URL("http://localhost:8200")), rootToken @@ -38,7 +40,7 @@ trait VaultSpec extends Specification with ResultMatchers { "con-air" ) - def check = config.token.attemptRun(_.getMessage) must beOk + def check = config.token.attemptRun should beRight override def map(fs: => Fragments) = s2""" diff --git a/core/src/main/scala/janstenpickle/scala/result/package.scala b/core/src/main/scala/janstenpickle/scala/result/package.scala new file mode 100644 index 0000000..bb85a98 --- /dev/null +++ b/core/src/main/scala/janstenpickle/scala/result/package.scala @@ -0,0 +1,30 @@ +package janstenpickle.scala + +import scala.concurrent.Future +import cats.implicits._ + + +import scala.language.postfixOps + +package object result { + type Result[R] = Either[String, R] + val Result: Either.type = Either + + implicit class ResultCompanionOps(dc: Either.type ){ + def pure[R](r: R): Either[String, R] = Either right r + + def fail[R](f: String): Either[String, R] = Either left f + } + + type AsyncResult[R] = Future[Result[R]] + + type AsyncEitherT[R] = cats.data.EitherT[Future, String, R] + + object AsyncResult { + def pure[R](r: R): AsyncResult[R] = { + Future.successful(Either right r) + } + } + + +} diff --git a/core/src/main/scala/janstenpickle/scala/syntax/syntax.scala b/core/src/main/scala/janstenpickle/scala/syntax/syntax.scala index 2413efd..b9880a1 100644 --- a/core/src/main/scala/janstenpickle/scala/syntax/syntax.scala +++ b/core/src/main/scala/janstenpickle/scala/syntax/syntax.scala @@ -1,21 +1,19 @@ package janstenpickle.scala.syntax +import cats.data.EitherT import com.ning.http.client.Response import dispatch.{Http, Req} import io.circe._ import io.circe.parser._ import io.circe.syntax._ import janstenpickle.vault.core.VaultConfig -import uscala.concurrent.result.AsyncResult -import uscala.result.Result +import janstenpickle.scala.result._ +import cats.implicits._ +import scala.concurrent.duration._ -import scala.concurrent.{ExecutionContext, Future} - -object ConversionSyntax { - implicit def toResult[A, B](xor: Either[A, B]): Result[A, B] = xor.fold( - Result.fail, Result.ok - ) -} +import scala.concurrent.{Await, ExecutionContext, Future} +import scala.language.postfixOps +import scala.util.control.NonFatal object OptionSyntax { implicit class ToTuple[T](opt: Option[T]) { @@ -25,91 +23,128 @@ object OptionSyntax { } object AsyncResultSyntax { + implicit class FutureToAsyncResult[T](future: Future[T]) (implicit ec: ExecutionContext) { - def toAsyncResult: AsyncResult[String, T] = AsyncResult( - future.map(Result.ok) - ) + def toAsyncResult: AsyncResult[T] = { + future.map(Result.pure[T]).recover { + case NonFatal(e) => Result.fail[T](f = e.getMessage) + } + } + } + + implicit class AsyncResultOps[R](f: AsyncResult[R]) { + def eiT: AsyncEitherT[R] = EitherT[Future, String, R](f) + + def attemptRun(implicit ec: ExecutionContext): Result[R] = + Await.result(f, 1 minute) + } + + implicit class AsyncResultOpsEitherT[R](f: Either[String, R]) { + def eiT(implicit ec: ExecutionContext): AsyncEitherT[R] = + EitherT.fromEither[Future](f) + } + + implicit class AsyncResultOpsAny[R](r: R) { + def eiT: AsyncEitherT[R] = + EitherT.apply(Future.successful(Either right r)) } implicit class ReqToAsyncResult(req: Req) (implicit ec: ExecutionContext) { - def toAsyncResult: AsyncResult[String, Response] = Http(req).toAsyncResult + def toAsyncResult: AsyncResult[Response] = Http(req).toAsyncResult } implicit def toAsyncResult[T](future: scala.concurrent.Future[T]) - (implicit ec: ExecutionContext): AsyncResult[String, T] = + (implicit ec: ExecutionContext): AsyncResult[T] = future.toAsyncResult } object VaultConfigSyntax { + import AsyncResultSyntax._ final val VaultTokenHeader = "X-Vault-Token" implicit class RequestHelper(config: VaultConfig) { def authenticatedRequest(path: String)(req: Req => Req) - (implicit ec: ExecutionContext): AsyncResult[String, Req] = - config.token.map[Req](token => - req(config.wsClient.path(path).setHeader(VaultTokenHeader, token)) - ) + (implicit ec: ExecutionContext): AsyncResult[Req] ={ + val r = for { + token <- config.token.eiT + } yield req(config.wsClient.path(path).setHeader(VaultTokenHeader, token)) + r.value + } } } object JsonSyntax { - import ConversionSyntax._ + import AsyncResultSyntax._ - implicit class JsonHandler(json: AsyncResult[String, Json]) { - def extractFromJson[T](jsonPath: HCursor => ACursor = _.downArray) - ( + implicit class JsonHandler(json: AsyncResult[Json]) { + def extractFromJson[T](jsonPath: HCursor => ACursor = _.downArray)( implicit decode: Decoder[T], ec: ExecutionContext - ): AsyncResult[String, T] = - json.flatMapR(j => decode.tryDecode( - jsonPath(j.hcursor) - ).leftMap(_.message)) + ): AsyncResult[T] = { + val r = for { + j <- json.eiT + e <- decode.tryDecode(jsonPath(j.hcursor)).leftMap(_.message).eiT + } yield e + r.value + } + } } object ResponseSyntax { - import ConversionSyntax._ + import AsyncResultSyntax._ import JsonSyntax._ - implicit class ResponseHandler(resp: AsyncResult[String, Response]) { + implicit class ResponseHandler(resp: AsyncResult[Response]) { def acceptStatusCodes(codes: Int*) - (implicit ec: ExecutionContext): AsyncResult[String, Response] = - resp.flatMapR( - response => - if (codes.contains(response.getStatusCode)) { - Result.ok(response) - } - else { - Result.fail( - s"Received failure response from server:" + - s" ${response.getStatusCode}\n ${response.getResponseBody}" - ) - } - ) - - def extractJson(implicit ec: ExecutionContext): AsyncResult[String, Json] = - resp.flatMapR(response => - parse(response.getResponseBody).leftMap(_.message) - ) + (implicit ec: ExecutionContext): AsyncResult[Response] = { + val r = for { + response <- resp.eiT + r <- Result.cond( + test = codes.contains(response.getStatusCode), + right = response, + left = s"Received failure response from server:" + + s" ${response.getStatusCode}\n ${response.getResponseBody}" + ).eiT + } yield r + r.value + } + + def extractJson(implicit ec: ExecutionContext): + AsyncResult[Json] = { + val r = for { + response <- resp.eiT + r <- parse(response.getResponseBody).leftMap(_.message).eiT + } yield r + r.value + } + def extractFromJson[T](jsonPath: HCursor => ACursor = _.downArray) ( implicit decode: Decoder[T], ec: ExecutionContext - ): AsyncResult[String, T] = + ): AsyncResult[T] = resp.extractJson.extractFromJson[T](jsonPath) } } object SyntaxRequest { + import AsyncResultSyntax._ - implicit class ExecuteRequest(req: AsyncResult[String, Req]) + implicit class ExecuteRequest(req: AsyncResult[Req]) (implicit ec: ExecutionContext) { - def execute: AsyncResult[String, Response] = - req.flatMapF(Http(_)) + def execute: AsyncResult[Response] = { + val r = for { + request <- req.eiT + response <- Http(request).toAsyncResult.eiT + } yield response + r.value + } + } implicit class HttpOps(req: Req) { diff --git a/core/src/main/scala/janstenpickle/vault/core/Secrets.scala b/core/src/main/scala/janstenpickle/vault/core/Secrets.scala index 32f53a0..82f0fdc 100644 --- a/core/src/main/scala/janstenpickle/vault/core/Secrets.scala +++ b/core/src/main/scala/janstenpickle/vault/core/Secrets.scala @@ -1,44 +1,52 @@ package janstenpickle.vault.core import com.ning.http.client.Response +import janstenpickle.scala.syntax.AsyncResultSyntax._ import janstenpickle.scala.syntax.SyntaxRequest._ import janstenpickle.scala.syntax.ResponseSyntax._ import janstenpickle.scala.syntax.VaultConfigSyntax._ -import uscala.concurrent.result.AsyncResult -import uscala.result.Result +import janstenpickle.scala.result._ +import cats.implicits._ import scala.concurrent.ExecutionContext // scalastyle:off magic.number case class Secrets(config: VaultConfig, backend: String) { def get(key: String, subKey: String = "value") - (implicit ec: ExecutionContext): AsyncResult[String, String] = - getAll(key).flatMapR(x => - Result.fromOption(x.get(subKey), - s"Cannot find sub-key $subKey in secret $key")) + (implicit ec: ExecutionContext): AsyncResult[String] = { + val r = for { + x <- getAll(key).eiT + r <- Either.fromOption( + x.get(subKey), + s"Cannot find sub-key $subKey in secret $key" + ).eiT + } yield r + r.value + } + def getAll(key: String) - (implicit ec: ExecutionContext): AsyncResult[String, Map[String, String]] = + (implicit ec: ExecutionContext): AsyncResult[Map[String, String]] = config.authenticatedRequest(path(key))(_.get). execute. acceptStatusCodes(200). extractFromJson[Map[String, String]](_.downField("data")) def set(key: String, value: String) - (implicit ec: ExecutionContext): AsyncResult[String, Response] = + (implicit ec: ExecutionContext): AsyncResult[Response] = set(key, "value", value) def set(key: String, subKey: String, value: String) - (implicit ec: ExecutionContext): AsyncResult[String, Response] = + (implicit ec: ExecutionContext): AsyncResult[Response] = set(key, Map(subKey -> value)) def set(key: String, values: Map[String, String]) - (implicit ec: ExecutionContext): AsyncResult[String, Response] = + (implicit ec: ExecutionContext): AsyncResult[Response] = config.authenticatedRequest(path(key))(_.post(values)). execute. acceptStatusCodes(204) - def list(implicit ec: ExecutionContext): AsyncResult[String, List[String]] = + def list(implicit ec: ExecutionContext): AsyncResult[List[String]] = config.authenticatedRequest(backend)( _.addQueryParameter("list", true.toString).get). execute. diff --git a/core/src/main/scala/janstenpickle/vault/core/VaultConfig.scala b/core/src/main/scala/janstenpickle/vault/core/VaultConfig.scala index 67544a9..68495bf 100644 --- a/core/src/main/scala/janstenpickle/vault/core/VaultConfig.scala +++ b/core/src/main/scala/janstenpickle/vault/core/VaultConfig.scala @@ -8,11 +8,11 @@ import io.circe.syntax._ import janstenpickle.scala.syntax.AsyncResultSyntax._ import janstenpickle.scala.syntax.SyntaxRequest._ import janstenpickle.scala.syntax.ResponseSyntax._ -import uscala.concurrent.result.AsyncResult +import janstenpickle.scala.result._ import scala.concurrent.ExecutionContext -case class VaultConfig(wsClient: WSClient, token: AsyncResult[String, String]) +case class VaultConfig(wsClient: WSClient, token: AsyncResult[String]) @deprecated("Vault 0.6.5 deprecated AppId in favor of AppRole", "0.4.0") case class AppId(app_id: String, user_id: String) case class AppRole(role_id: String, secret_id: String) @@ -50,13 +50,15 @@ object VaultConfig { def apply(wsClient: WSClient, token: String) (implicit ec: ExecutionContext): VaultConfig = - VaultConfig(wsClient, AsyncResult.ok[String, String](token)) + VaultConfig(wsClient, AsyncResult.pure(token)) } -case class WSClient(server: URL, - version: String = "v1") { +case class WSClient( + server: URL, + version: String = "v1" +) { def path(p: String): Req = url(s"${server.toString}/$version/$p"). setContentType("application/json", "UTF-8") diff --git a/manage/src/it/scala/janstenpickle/vault/manage/AuthIT.scala b/manage/src/it/scala/janstenpickle/vault/manage/AuthIT.scala index 0e112a6..27a16f2 100644 --- a/manage/src/it/scala/janstenpickle/vault/manage/AuthIT.scala +++ b/manage/src/it/scala/janstenpickle/vault/manage/AuthIT.scala @@ -1,5 +1,6 @@ package janstenpickle.vault.manage +import janstenpickle.scala.syntax.AsyncResultSyntax._ import janstenpickle.vault.core.VaultSpec import org.scalacheck.{Prop, Gen} import org.specs2.ScalaCheck @@ -19,15 +20,15 @@ class AuthIT extends VaultSpec with ScalaCheck { def happy = Prop.forAllNoShrink( backends, longerStrGen, Gen.option(longerStrGen))((backend, mount, desc) => (underTest.enable(backend, Some(mount), desc) - .attemptRun(_.getMessage()) must beOk) and - (underTest.disable(mount).attemptRun(_.getMessage()) must beOk) + .attemptRun must beRight) and + (underTest.disable(mount).attemptRun must beRight) ) def enableFail = Prop.forAllNoShrink( longerStrGen.suchThat(!backendNames.contains(_)), longerStrGen, Gen.option(longerStrGen))((backend, mount, desc) => - underTest.enable(mount).attemptRun(_.getMessage()) must beFail + underTest.enable(mount).attemptRun must beLeft ) } diff --git a/manage/src/it/scala/janstenpickle/vault/manage/MountIT.scala b/manage/src/it/scala/janstenpickle/vault/manage/MountIT.scala index 449a418..e95bc0a 100644 --- a/manage/src/it/scala/janstenpickle/vault/manage/MountIT.scala +++ b/manage/src/it/scala/janstenpickle/vault/manage/MountIT.scala @@ -6,7 +6,8 @@ import janstenpickle.vault.core.VaultSpec import janstenpickle.vault.manage.Model.{Mount, MountConfig} import org.scalacheck.{Gen, Prop} import org.specs2.ScalaCheck -import uscala.result.Result +import janstenpickle.scala.syntax.AsyncResultSyntax._ +import janstenpickle.scala.result._ class MountIT extends VaultSpec with ScalaCheck { import MountIT._ @@ -27,32 +28,35 @@ class MountIT extends VaultSpec with ScalaCheck { longerStrGen, Gen.option(longerStrGen))((mount, mountPoint, remountPoint, desc) => { (underTest.mount(mount.`type`, Some(mountPoint), desc, Some(mount)) - .attemptRun(_.getMessage()) must beOk) and + .attemptRun must beRight) and (underTest.remount(mountPoint, remountPoint) - .attemptRun(_.getMessage()) must beOk) and + .attemptRun must beRight) and (underTest.delete(remountPoint) - .attemptRun(_.getMessage()) must beOk) and + .attemptRun must beRight) and (underTest.delete(mountPoint) - .attemptRun(_.getMessage()) must beOk) + .attemptRun must beRight) }) - def listSuccess = (processMountTypes((acc, mount) => - acc.flatMap(_ => underTest.mount(mount) - .attemptRun(_.getMessage()))) must beOk) and - (underTest.list.attemptRun(_.getMessage()) must beOk.like { + def listSuccess = { + processMountTypes((acc, mount) => + acc.right.flatMap(_ => underTest.mount(mount).attemptRun) + ) must beRight + } and { + underTest.list.attemptRun must beRight.like { case a => a.map(_._2.`type`) must containAllOf(mountTypes) - }) and - (processMountTypes((acc, mount) => - acc.flatMap(_ => - underTest.delete(mount).attemptRun(_.getMessage())) - ) must beOk) + } + } and { + processMountTypes((acc, mount) => + acc.right.flatMap(_ => underTest.delete(mount).attemptRun) + ) must beRight + } def enableFail = Prop.forAllNoShrink( longerStrGen.suchThat(!mountTypes.contains(_)), longerStrGen, Gen.option(longerStrGen))((`type`, mount, desc) => underTest.mount(`type`, Some(mount), desc) - .attemptRun(_.getMessage()) must beFail + .attemptRun must beLeft ) } @@ -75,9 +79,8 @@ object MountIT { forceNoCache <- Gen.option(Gen.oneOf(true, false)) } yield Mount(mountType, description, Some(MountConfig(defaultTtl, maxTtl, forceNoCache))) - def processMountTypes(op: (Result[String, Response], String) => Result[String, - Response]) = - mountTypes.foldLeft[Result[String, Response]](Result.ok(new + def processMountTypes(op: (Result[Response], String) => Result[Response]) = + mountTypes.foldLeft[Result[Response]](Result.pure(new JDKResponse(null, null, null)))(op) } diff --git a/manage/src/it/scala/janstenpickle/vault/manage/PolicyIT.scala b/manage/src/it/scala/janstenpickle/vault/manage/PolicyIT.scala index c8a4b38..88824b8 100644 --- a/manage/src/it/scala/janstenpickle/vault/manage/PolicyIT.scala +++ b/manage/src/it/scala/janstenpickle/vault/manage/PolicyIT.scala @@ -4,9 +4,10 @@ import janstenpickle.vault.core.VaultSpec import janstenpickle.vault.manage.Model.Rule import org.scalacheck.{Gen, Prop} import org.specs2.ScalaCheck -import uscala.result.Result +import janstenpickle.scala.syntax.AsyncResultSyntax._ +import org.specs2.matcher.EitherMatchers -class PolicyIT extends VaultSpec with ScalaCheck { +class PolicyIT extends VaultSpec with ScalaCheck with EitherMatchers { import PolicyIT._ import VaultSpec._ @@ -23,17 +24,17 @@ class PolicyIT extends VaultSpec with ScalaCheck { Gen.listOf(ruleGen(longerStrGen, policyGen, capabilitiesGen)). suchThat(_.nonEmpty)) { (name, rules) => (underTest.set(name.toLowerCase, rules) - .attemptRun(_.getMessage()) must beOk) and + .attemptRun must beRight) and (underTest.inspect(name.toLowerCase) - .attemptRun(_.getMessage()) must beOk) and - (underTest.delete(name.toLowerCase).attemptRun(_.getMessage()) must beOk) + .attemptRun must beRight) and + (underTest.delete(name.toLowerCase).attemptRun must beRight) } // cannot use generated values here as // vault seems to have a failure rate limit def sad = underTest.set( "nic", List(Rule("cage", Some(List("kim", "copolla")))) - ).attemptRun(_.getMessage()) must beFail + ).attemptRun must beLeft } object PolicyIT { diff --git a/manage/src/it/scala/janstenpickle/vault/manage/UserPassIT.scala b/manage/src/it/scala/janstenpickle/vault/manage/UserPassIT.scala index d8795fc..6c5f2bb 100644 --- a/manage/src/it/scala/janstenpickle/vault/manage/UserPassIT.scala +++ b/manage/src/it/scala/janstenpickle/vault/manage/UserPassIT.scala @@ -3,8 +3,10 @@ package janstenpickle.vault.manage import janstenpickle.vault.core.VaultSpec import org.scalacheck.{Gen, Prop} import org.specs2.ScalaCheck +import janstenpickle.scala.syntax.AsyncResultSyntax._ +import org.specs2.matcher.EitherMatchers -class UserPassIT extends VaultSpec with ScalaCheck { +class UserPassIT extends VaultSpec with ScalaCheck with EitherMatchers { import UserPassIT._ import VaultSpec._ @@ -20,17 +22,17 @@ class UserPassIT extends VaultSpec with ScalaCheck { def good = Prop.forAllNoShrink(longerStrGen, longerStrGen, longerStrGen, Gen.posNum[Int], longerStrGen, policyGen)( (username, password, newPassword, ttl, client, policy) => - (authAdmin.enable("userpass", Some(client)).attemptRun(_.getMessage()) must beOk) and - (underTest.create(username, password, ttl, None, client).attemptRun(_.getMessage()) must beOk) and - (underTest.setPassword(username, newPassword, client).attemptRun(_.getMessage()) must beOk) and - (underTest.setPolicies(username, policy, client).attemptRun(_.getMessage()) must beOk) and - (underTest.delete(username, client).attemptRun(_.getMessage()) must beOk) and - (authAdmin.disable(client).attemptRun(_.getMessage()) must beOk) + (authAdmin.enable("userpass", Some(client)).attemptRun must beRight) and + (underTest.create(username, password, ttl, None, client).attemptRun must beRight) and + (underTest.setPassword(username, newPassword, client).attemptRun must beRight) and + (underTest.setPolicies(username, policy, client).attemptRun must beRight) and + (underTest.delete(username, client).attemptRun must beRight) and + (authAdmin.disable(client).attemptRun must beRight) ) def badClient = Prop.forAllNoShrink(longerStrGen, longerStrGen, Gen.posNum[Int], longerStrGen)( (username, password, ttl, client) => - underTest.create(username, password, ttl, None, client).attemptRun(_.getMessage()) must beFail + underTest.create(username, password, ttl, None, client).attemptRun must beLeft ) def badPolicy = Prop.forAllNoShrink(longerStrGen, @@ -39,9 +41,9 @@ class UserPassIT extends VaultSpec with ScalaCheck { longerStrGen, Gen.listOf(longerStrGen.suchThat(!policies.contains(_))))( (username, password, ttl, client, policy) => - (authAdmin.enable("userpass", Some(client)).attemptRun(_.getMessage()) must beOk) and - (underTest.create(username, password, ttl, Some(policy), client).attemptRun(_.getMessage()) must beOk) and - (authAdmin.disable(client).attemptRun(_.getMessage()) must beOk) + (authAdmin.enable("userpass", Some(client)).attemptRun must beRight) and + (underTest.create(username, password, ttl, Some(policy), client).attemptRun must beRight) and + (authAdmin.disable(client).attemptRun must beRight) ) } diff --git a/manage/src/main/scala/janstenpickle/vault/manage/UserPass.scala b/manage/src/main/scala/janstenpickle/vault/manage/UserPass.scala index 7e1211b..97a3a43 100644 --- a/manage/src/main/scala/janstenpickle/vault/manage/UserPass.scala +++ b/manage/src/main/scala/janstenpickle/vault/manage/UserPass.scala @@ -6,7 +6,7 @@ import janstenpickle.scala.syntax.SyntaxRequest._ import janstenpickle.scala.syntax.ResponseSyntax._ import janstenpickle.scala.syntax.VaultConfigSyntax._ import janstenpickle.vault.core.VaultConfig -import uscala.concurrent.result.AsyncResult +import janstenpickle.scala.result._ import scala.concurrent.ExecutionContext @@ -18,7 +18,7 @@ case class UserPass(config: VaultConfig) { ttl: Int, policies: Option[List[String]] = None, client: String = DefaultClient) - (implicit ec: ExecutionContext): AsyncResult[String, Response] = + (implicit ec: ExecutionContext): AsyncResult[Response] = config.authenticatedRequest(s"auth/$client/users/$username")( _.post(policies.map(_.mkString(",")).toMap("policies") ++ Map("username" -> username, @@ -27,7 +27,7 @@ case class UserPass(config: VaultConfig) { ).execute.acceptStatusCodes(204) def delete(username: String, client: String = DefaultClient) - (implicit ec: ExecutionContext): AsyncResult[String, Response] = + (implicit ec: ExecutionContext): AsyncResult[Response] = config.authenticatedRequest(s"auth/$client/users/$username")(_.delete). execute. acceptStatusCodes(204) @@ -36,7 +36,7 @@ case class UserPass(config: VaultConfig) { username: String, password: String, client: String = DefaultClient - )(implicit ec: ExecutionContext): AsyncResult[String, Response] = + )(implicit ec: ExecutionContext): AsyncResult[Response] = config.authenticatedRequest(s"auth/$client/users/$username/password")( _.post(Map("username" -> username, "password" -> password)) ).execute.acceptStatusCodes(204) @@ -45,7 +45,7 @@ case class UserPass(config: VaultConfig) { username: String, policies: List[String], client: String = DefaultClient - )(implicit ec: ExecutionContext): AsyncResult[String, Response] = + )(implicit ec: ExecutionContext): AsyncResult[Response] = config.authenticatedRequest(s"auth/$client/users/$username/policies")( _.post(Map("username" -> username, "policies" -> policies.mkString(","))) ).execute.acceptStatusCodes(204) diff --git a/manage/src/main/scala/janstenpickle/vault/manage/manage.scala b/manage/src/main/scala/janstenpickle/vault/manage/manage.scala index a681b8a..00fb0e4 100644 --- a/manage/src/main/scala/janstenpickle/vault/manage/manage.scala +++ b/manage/src/main/scala/janstenpickle/vault/manage/manage.scala @@ -9,8 +9,7 @@ import janstenpickle.scala.syntax.ResponseSyntax._ import janstenpickle.scala.syntax.VaultConfigSyntax._ import janstenpickle.vault.core.VaultConfig import janstenpickle.vault.manage.Model._ -import uscala.concurrent.result.AsyncResult -import uscala.result.Result +import janstenpickle.scala.result._ import scala.concurrent.ExecutionContext @@ -19,13 +18,13 @@ case class Auth(config: VaultConfig) { def enable(`type`: String, mountPoint: Option[String] = None, description: Option[String] = None) - (implicit ec: ExecutionContext): AsyncResult[String, Response] = + (implicit ec: ExecutionContext): AsyncResult[Response] = config.authenticatedRequest(s"sys/auth/${mountPoint.getOrElse(`type`)}")( _.post(description.toMap("description") + ("type" -> `type`)) ).execute.acceptStatusCodes(204) def disable(mountPoint: String) - (implicit ec: ExecutionContext): AsyncResult[String, Response] = + (implicit ec: ExecutionContext): AsyncResult[Response] = config.authenticatedRequest(s"sys/auth/$mountPoint")(_.delete). execute. acceptStatusCodes(204) @@ -33,13 +32,13 @@ case class Auth(config: VaultConfig) { case class Mounts(config: VaultConfig) { def remount(from: String, to: String) - (implicit ec: ExecutionContext): AsyncResult[String, Response] = + (implicit ec: ExecutionContext): AsyncResult[Response] = config.authenticatedRequest("sys/remount")( _.post(Map("from" -> from, "to" -> to)) ).execute.acceptStatusCodes(204) def list(implicit ec: ExecutionContext): - AsyncResult[String, Map[String, Mount]] = + AsyncResult[Map[String, Mount]] = config.authenticatedRequest("sys/mounts")(_.get). execute. acceptStatusCodes(200). @@ -49,13 +48,13 @@ case class Mounts(config: VaultConfig) { mountPoint: Option[String] = None, description: Option[String] = None, conf: Option[Mount] = None) - (implicit ec: ExecutionContext): AsyncResult[String, Response] = + (implicit ec: ExecutionContext): AsyncResult[Response] = config.authenticatedRequest(s"sys/mounts/${mountPoint.getOrElse(`type`)}")( _.post(MountRequest(`type`, description, conf).asJson) ).execute.acceptStatusCodes(204) def delete(mountPoint: String) - (implicit ec: ExecutionContext): AsyncResult[String, Response] = + (implicit ec: ExecutionContext): AsyncResult[Response] = config.authenticatedRequest(s"sys/mounts/$mountPoint")(_.delete). execute. acceptStatusCodes(204) @@ -63,7 +62,7 @@ case class Mounts(config: VaultConfig) { case class Policy(config: VaultConfig) { - def list(implicit ec: ExecutionContext): AsyncResult[String, List[String]] = + def list(implicit ec: ExecutionContext): AsyncResult[List[String]] = config.authenticatedRequest("sys/policy")(_.get). execute. acceptStatusCodes(200). @@ -71,21 +70,21 @@ case class Policy(config: VaultConfig) { // NOTE: `rules` is not valid Json def inspect(policy: String)(implicit ec: ExecutionContext): - AsyncResult[String, String] = + AsyncResult[String] = config.authenticatedRequest(s"sys/policy/$policy")(_.get). execute. acceptStatusCodes(200) .extractFromJson[String](_.downField("rules")) def set(policy: String, rules: List[Rule]) - (implicit ec: ExecutionContext): AsyncResult[String, Response] = + (implicit ec: ExecutionContext): AsyncResult[Response] = config.authenticatedRequest(s"sys/policy/$policy")( _.post(PolicySetting(policy, rules).asJson)). execute. acceptStatusCodes(204) def delete(policy: String)(implicit ec: ExecutionContext): - AsyncResult[String, Response] = + AsyncResult[Response] = config.authenticatedRequest(s"sys/policy/$policy")(_.delete). execute. acceptStatusCodes(204) @@ -105,7 +104,7 @@ object Model { ) case class PolicySetting(name: String, rules: Option[String]) { - lazy val decodeRules: Option[Result[String, List[Rule]]] = rules.filter( + lazy val decodeRules: Option[Result[List[Rule]]] = rules.filter( _.nonEmpty).map(Rule.decode) } object PolicySetting { @@ -137,7 +136,7 @@ object Model { val capabilitiesRegex = """\s+capabilities\s+=\s+\[(.+)\]""".r val policyRegex = """\s+policy\s+=\s+"(\S+)"""".r - def decode(ruleString: String): Result[String, List[Rule]] = { + def decode(ruleString: String): Result[List[Rule]] = { val rules = ruleString.split("""\s*}\s+\n""").toList val decoded = rules.foldLeft(List.empty[Rule])( (acc, v) => acc ++ pathRegex.findFirstMatchIn(v).map(_.group(1)).map(path => @@ -150,10 +149,10 @@ object Model { ) ) if (decoded.isEmpty) { - Result.fail(s"Could not find any valid rules in string: $ruleString") + Result fail s"Could not find any valid rules in string: $ruleString" } else { - Result.ok(decoded) + Result pure decoded } } } diff --git a/manage/src/test/scala/janstenpickle/vault/manage/RuleSpec.scala b/manage/src/test/scala/janstenpickle/vault/manage/RuleSpec.scala index c6ec359..a41e1b5 100644 --- a/manage/src/test/scala/janstenpickle/vault/manage/RuleSpec.scala +++ b/manage/src/test/scala/janstenpickle/vault/manage/RuleSpec.scala @@ -2,10 +2,10 @@ package janstenpickle.vault.manage import janstenpickle.vault.manage.Model.Rule import org.scalacheck.{Gen, Prop} +import org.specs2.matcher.EitherMatchers import org.specs2.{ScalaCheck, Specification} -import uscala.result.specs2.ResultMatchers -class RuleSpec extends Specification with ScalaCheck with ResultMatchers { +class RuleSpec extends Specification with ScalaCheck with EitherMatchers { import RuleSpec._ override def is = @@ -15,13 +15,13 @@ class RuleSpec extends Specification with ScalaCheck with ResultMatchers { """ def passes = Prop.forAllNoShrink(Gen.listOf(ruleGen).suchThat(_.nonEmpty)) (rules => - Rule.decode(rules.map(_.encode).mkString("\n")) must beOk.like { + Rule.decode(rules.map(_.encode).mkString("\n")) must beRight.like { case a => a must containAllOf(rules) } ) def fails = Prop.forAllNoShrink(Gen.listOf(badRuleGen).suchThat(_.nonEmpty)) (rules => - Rule.decode(rules.mkString("\n")) must beFail + Rule.decode(rules.mkString("\n")) must beLeft ) }