From ee0ddd33cd3cb5865ba45ccde70434543a83bc9f Mon Sep 17 00:00:00 2001 From: yago Date: Fri, 22 May 2020 19:29:24 +0200 Subject: [PATCH 1/2] New Versioning - first approach --- .scalafmt.conf | 2 +- build.sbt | 1 + .../data/V1__add_protocols_table.sql | 2 +- .../metadata/V1__add_metaprotocols_table.sql | 2 +- .../higherkindness/compendium/Main.scala | 4 +- .../compendium/core/CompendiumService.scala | 8 +- .../compendium/core/doobie/implicits.scala | 11 +- .../compendium/core/refinements.scala | 17 +- .../compendium/http/QueryParams.scala | 17 +- .../compendium/http/RootService.scala | 8 +- .../compendium/metadata/MetadataStorage.scala | 7 +- .../metadata/pg/PgMetadataStorage.scala | 22 +- .../compendium/metadata/pg/Queries.scala | 15 +- .../compendium/models/ProtocolMetadata.scala | 2 +- .../compendium/models/ProtocolVersion.scala | 195 ++++++++++++++++++ .../compendium/models/Tagged.scala | 61 ++++++ .../compendium/models/errors.scala | 3 +- .../compendium/models/package.scala | 47 +++++ .../compendium/storage/Storage.scala | 3 +- .../storage/files/FileStorage.scala | 6 +- .../compendium/storage/pg/PgStorage.scala | 4 +- .../compendium/storage/pg/Queries.scala | 4 +- .../SkeuomorphProtocolTransformer.scala | 4 +- .../compendium/CompendiumArbitrary.scala | 33 ++- .../core/CompendiumServiceStub.scala | 9 +- .../compendium/core/ProtocolUtilsSpec.scala | 9 +- .../compendium/core/refinementsSpec.scala | 66 ++++++ .../compendium/http/RootServiceSpec.scala | 62 +++--- .../metadata/MetadataStorageStub.scala | 23 ++- .../metadata/pg/MetadataQueriesSpec.scala | 5 +- .../metadata/pg/PgMetadataStorageSpec.scala | 15 +- .../models/ProtocolVersionSpec.scala | 112 ++++++++++ .../compendium/storage/StorageStub.scala | 6 +- .../storage/files/FileStorageSpec.scala | 17 +- .../compendium/storage/pg/PgStorageSpec.scala | 13 +- .../storage/pg/StorageQueriesSpec.scala | 8 +- .../SkeuomorphProtocolTransformerSpec.scala | 7 +- 37 files changed, 680 insertions(+), 150 deletions(-) create mode 100644 src/main/scala/higherkindness/compendium/models/ProtocolVersion.scala create mode 100644 src/main/scala/higherkindness/compendium/models/Tagged.scala create mode 100644 src/main/scala/higherkindness/compendium/models/package.scala create mode 100644 src/test/scala/higherkindness/compendium/core/refinementsSpec.scala create mode 100644 src/test/scala/higherkindness/compendium/models/ProtocolVersionSpec.scala diff --git a/.scalafmt.conf b/.scalafmt.conf index 75b038f..67a7e3b 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,6 +1,6 @@ version = "2.5.2" style = defaultWithAlign -maxColumn = 100 +maxColumn = 120 continuationIndent.callSite = 2 diff --git a/build.sbt b/build.sbt index 2909fde..c3f5eb7 100644 --- a/build.sbt +++ b/build.sbt @@ -114,6 +114,7 @@ lazy val commonSettings = Seq( %%("specs2-core", V.specs2) % Test, %%("specs2-scalacheck", V.specs2) % Test, %%("doobie-specs2", V.doobie) % Test, + "org.specs2" %% "specs2-cats" % V.specs2 % Test, "io.chrisdavenport" %% "cats-scalacheck" % V.catsScalacheck % Test, "io.chrisdavenport" %% "testcontainers-specs2" % "0.2.0-M2" % Test, "org.testcontainers" % "postgresql" % "1.14.2" % Test diff --git a/src/main/resources/db/migration/data/V1__add_protocols_table.sql b/src/main/resources/db/migration/data/V1__add_protocols_table.sql index 4452a40..459ec6b 100644 --- a/src/main/resources/db/migration/data/V1__add_protocols_table.sql +++ b/src/main/resources/db/migration/data/V1__add_protocols_table.sql @@ -1,6 +1,6 @@ CREATE TABLE protocols ( id CHARACTER VARYING(255) NOT NULL, - version INTEGER NOT NULL, + version VARCHAR(20) NOT NULL CHECK (version ~ '^(\d+\.)?(\d+\.)?(\*|\d+)$'), protocol BYTEA, PRIMARY KEY (id, version) ); diff --git a/src/main/resources/db/migration/metadata/V1__add_metaprotocols_table.sql b/src/main/resources/db/migration/metadata/V1__add_metaprotocols_table.sql index 411102b..2ab017b 100644 --- a/src/main/resources/db/migration/metadata/V1__add_metaprotocols_table.sql +++ b/src/main/resources/db/migration/metadata/V1__add_metaprotocols_table.sql @@ -3,5 +3,5 @@ CREATE TYPE idl AS ENUM ('avro', 'protobuf', 'mu', 'openapi', 'scala'); CREATE TABLE metaprotocols ( id CHARACTER VARYING(255) PRIMARY KEY, idl_name idl NOT NULL, - version INTEGER NOT NULL + version VARCHAR(20) NOT NULL CHECK (version ~ '^(\d+\.)?(\d+\.)?(\*|\d+)$') ); diff --git a/src/main/scala/higherkindness/compendium/Main.scala b/src/main/scala/higherkindness/compendium/Main.scala index 3fa0107..2a1ef7a 100644 --- a/src/main/scala/higherkindness/compendium/Main.scala +++ b/src/main/scala/higherkindness/compendium/Main.scala @@ -81,9 +81,7 @@ object CompendiumStreamApp { metadataConf: CompendiumMetadataConfig ): Stream[F, MetadataStorage[F]] = Stream.eval( - Migrations.metadataLocation.flatMap(l => - Migrations.makeMigrations(metadataConf.storage, l :: Nil) - ) + Migrations.metadataLocation.flatMap(l => Migrations.makeMigrations(metadataConf.storage, l :: Nil)) ) >> Stream.resource(createTransactor(metadataConf.storage).map(PgMetadataStorage[F](_))) diff --git a/src/main/scala/higherkindness/compendium/core/CompendiumService.scala b/src/main/scala/higherkindness/compendium/core/CompendiumService.scala index dc27a39..56037cd 100644 --- a/src/main/scala/higherkindness/compendium/core/CompendiumService.scala +++ b/src/main/scala/higherkindness/compendium/core/CompendiumService.scala @@ -18,7 +18,7 @@ package higherkindness.compendium.core import cats.effect.Sync import cats.implicits._ -import higherkindness.compendium.core.refinements._ +import higherkindness.compendium.core.refinements.ProtocolId import higherkindness.compendium.metadata.MetadataStorage import higherkindness.compendium.models._ import higherkindness.compendium.storage.Storage @@ -47,8 +47,10 @@ object CompendiumService { idlName: IdlName ): F[ProtocolVersion] = for { - _ <- ProtocolUtils[F].validateProtocol(protocol, idlName) - version <- MetadataStorage[F].store(id, idlName) + _ <- ProtocolUtils[F].validateProtocol(protocol, idlName) + existingVersion <- MetadataStorage[F].versionOf(id) + newVersion = existingVersion.getOrElse(ProtocolVersion.initial) + version <- MetadataStorage[F].store(id, newVersion, idlName) _ <- Storage[F].store(id, version, protocol) } yield version diff --git a/src/main/scala/higherkindness/compendium/core/doobie/implicits.scala b/src/main/scala/higherkindness/compendium/core/doobie/implicits.scala index 0e42478..d259ac4 100644 --- a/src/main/scala/higherkindness/compendium/core/doobie/implicits.scala +++ b/src/main/scala/higherkindness/compendium/core/doobie/implicits.scala @@ -16,11 +16,11 @@ package higherkindness.compendium.core.doobie -import cats.instances.all._ +import cats.implicits._ import doobie.util.{Get, Put} import doobie.util.meta.Meta -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} -import higherkindness.compendium.models.{IdlName, Protocol} +import higherkindness.compendium.core.refinements.ProtocolId +import higherkindness.compendium.models._ object implicits { @@ -33,6 +33,7 @@ object implicits { implicit val IdlNamesMeta: Meta[IdlName] = Meta[String].timap(IdlName.withName)(_.entryName) - implicit val protocolVersionPut: Put[ProtocolVersion] = Put[Int].contramap(_.value) - implicit val protocolVersionGet: Get[ProtocolVersion] = Get[Int].temap(ProtocolVersion.from) + implicit val protocolVersionPut: Put[ProtocolVersion] = Put[String].contramap(_.show) + implicit val protocolVersionGet: Get[ProtocolVersion] = + Get[String].temap(ProtocolVersion.fromString(_).leftMap(_.msg)) } diff --git a/src/main/scala/higherkindness/compendium/core/refinements.scala b/src/main/scala/higherkindness/compendium/core/refinements.scala index 5770d8c..ac5975d 100644 --- a/src/main/scala/higherkindness/compendium/core/refinements.scala +++ b/src/main/scala/higherkindness/compendium/core/refinements.scala @@ -24,8 +24,8 @@ import eu.timepit.refined.boolean.{And, AnyOf} import eu.timepit.refined.char.LetterOrDigit import eu.timepit.refined.collection.{Forall, MaxSize} import eu.timepit.refined.generic.Equal -import eu.timepit.refined.numeric.Positive -import higherkindness.compendium.models.{ProtocolIdError, ProtocolVersionError} +import eu.timepit.refined.string.MatchesRegex +import higherkindness.compendium.models._ import shapeless.{::, HNil} object refinements { @@ -37,19 +37,20 @@ object refinements { type ProtocolIdConstraints = And[MaxProtocolIdSize, ValidProtocolIdChars] type ProtocolId = String Refined ProtocolIdConstraints - object ProtocolId extends RefinedTypeOps[ProtocolId, String] { def parseOrRaise[F[_]: Sync](id: String): F[ProtocolId] = F.fromEither(ProtocolId.from(id).leftMap(ProtocolIdError)) } - type ProtocolVersion = Int Refined Positive + /** An String that matches with format xx.yy.zz, xx.yy, xx */ + type ProtocolVersionRefined = + String Refined MatchesRegex[W.`"""^(\\d+\\.)?(\\d+\\.)?(\\*|\\d+)$"""`.T] + object ProtocolVersionRefined extends RefinedTypeOps[ProtocolVersionRefined, String] { - object ProtocolVersion extends RefinedTypeOps[ProtocolVersion, Int] { def parseOrRaise[F[_]: Sync](version: String): F[ProtocolVersion] = for { - number <- F.delay(version.toInt) - protoVersion <- F.fromEither(ProtocolVersion.from(number).leftMap(ProtocolVersionError)) - } yield protoVersion + versionRefined <- F.fromEither(ProtocolVersionRefined.from(version).leftMap(ProtocolVersionError)) + protocolVersion <- F.fromEither(ProtocolVersion.fromString(versionRefined.value)) + } yield protocolVersion } } diff --git a/src/main/scala/higherkindness/compendium/http/QueryParams.scala b/src/main/scala/higherkindness/compendium/http/QueryParams.scala index def180f..2d204cb 100644 --- a/src/main/scala/higherkindness/compendium/http/QueryParams.scala +++ b/src/main/scala/higherkindness/compendium/http/QueryParams.scala @@ -16,13 +16,10 @@ package higherkindness.compendium.http -import higherkindness.compendium.core.refinements.ProtocolVersion +import higherkindness.compendium.core.refinements.ProtocolVersionRefined import higherkindness.compendium.models.IdlName import org.http4s.QueryParamDecoder -import org.http4s.dsl.impl.{ - OptionalValidatingQueryParamDecoderMatcher, - ValidatingQueryParamDecoderMatcher -} +import org.http4s.dsl.impl.{OptionalValidatingQueryParamDecoderMatcher, ValidatingQueryParamDecoderMatcher} object QueryParams { @@ -32,10 +29,10 @@ object QueryParams { object TargetParam extends ValidatingQueryParamDecoderMatcher[IdlName]("target") object IdlNameParam extends ValidatingQueryParamDecoderMatcher[IdlName]("idlName") - implicit val versionQueryParamDecoder: QueryParamDecoder[ProtocolVersion] = - QueryParamDecoder.fromUnsafeCast[ProtocolVersion](param => - ProtocolVersion.unsafeFrom(param.value.toInt) - )("ProtocolVersion") + implicit val versionQueryParamDecoder: QueryParamDecoder[ProtocolVersionRefined] = + QueryParamDecoder.fromUnsafeCast[ProtocolVersionRefined](param => ProtocolVersionRefined.unsafeFrom(param.value))( + "ProtocolVersion" + ) - object ProtoVersion extends OptionalValidatingQueryParamDecoderMatcher[ProtocolVersion]("version") + object ProtoVersion extends OptionalValidatingQueryParamDecoderMatcher[ProtocolVersionRefined]("version") } diff --git a/src/main/scala/higherkindness/compendium/http/RootService.scala b/src/main/scala/higherkindness/compendium/http/RootService.scala index 754919a..55c992f 100644 --- a/src/main/scala/higherkindness/compendium/http/RootService.scala +++ b/src/main/scala/higherkindness/compendium/http/RootService.scala @@ -39,10 +39,12 @@ object RootService { F.fromValidated(idlNameValidated.leftMap(errs => UnknownIdlName(errs.toList.mkString))) def versionValidation( - maybeVersionValidated: Option[ValidatedNel[ParseFailure, ProtocolVersion]] + maybeVersionValidated: Option[ValidatedNel[ParseFailure, ProtocolVersionRefined]] ): F[Option[ProtocolVersion]] = maybeVersionValidated.traverse { validated => - val validation = validated.leftMap(errs => ProtocolVersionError(errs.toList.mkString)) + val validation = validated + .leftMap(errs => ProtocolVersionError(errs.toList.mkString)) + .andThen(v => ProtocolVersion.fromString(v.value).toValidated) F.fromValidated(validation) } @@ -53,7 +55,7 @@ object RootService { idlName <- idlValidation(idlNameValidated) protocol <- req.as[Protocol] version <- CompendiumService[F].storeProtocol(protocolId, protocol, idlName) - response <- Created(version.value) + response <- Created(version.show) } yield response.putHeaders(Location(req.uri.withPath(s"${req.uri.path}"))) case GET -> Root / "protocol" / id :? ProtoVersion(maybeVersionValidated) => diff --git a/src/main/scala/higherkindness/compendium/metadata/MetadataStorage.scala b/src/main/scala/higherkindness/compendium/metadata/MetadataStorage.scala index f677580..0f5d308 100644 --- a/src/main/scala/higherkindness/compendium/metadata/MetadataStorage.scala +++ b/src/main/scala/higherkindness/compendium/metadata/MetadataStorage.scala @@ -16,14 +16,15 @@ package higherkindness.compendium.metadata -import higherkindness.compendium.models.{IdlName, ProtocolMetadata} -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} +import higherkindness.compendium.models._ +import higherkindness.compendium.core.refinements.ProtocolId trait MetadataStorage[F[_]] { - def store(id: ProtocolId, idlName: IdlName): F[ProtocolVersion] + def store(id: ProtocolId, protocolVersion: ProtocolVersion, idlName: IdlName): F[ProtocolVersion] def retrieve(id: ProtocolId): F[ProtocolMetadata] def exists(id: ProtocolId): F[Boolean] def ping: F[Boolean] + def versionOf(id: ProtocolId): F[Option[ProtocolVersion]] } object MetadataStorage { diff --git a/src/main/scala/higherkindness/compendium/metadata/pg/PgMetadataStorage.scala b/src/main/scala/higherkindness/compendium/metadata/pg/PgMetadataStorage.scala index e5223b8..7f3a0a9 100644 --- a/src/main/scala/higherkindness/compendium/metadata/pg/PgMetadataStorage.scala +++ b/src/main/scala/higherkindness/compendium/metadata/pg/PgMetadataStorage.scala @@ -20,29 +20,37 @@ import cats.effect.Async import doobie.implicits._ import doobie.util.transactor.Transactor import higherkindness.compendium.core.doobie.implicits._ -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} +import higherkindness.compendium.core.refinements.ProtocolId +import higherkindness.compendium.models.ProtocolVersion import higherkindness.compendium.metadata.MetadataStorage -import higherkindness.compendium.models.{IdlName, ProtocolMetadata, ProtocolNotFound} +import higherkindness.compendium.models._ object PgMetadataStorage { def apply[F[_]: Async](xa: Transactor[F]): MetadataStorage[F] = new MetadataStorage[F] { - override def store(id: ProtocolId, idlName: IdlName): F[ProtocolVersion] = + def store( + id: ProtocolId, + protocolVersion: ProtocolVersion, + idlName: IdlName + ): F[ProtocolVersion] = Queries - .store(id, idlName.entryName) + .store(id, protocolVersion, idlName.entryName) .withUniqueGeneratedKeys[ProtocolVersion]("version") .transact(xa) - override def retrieve(id: ProtocolId): F[ProtocolMetadata] = + def retrieve(id: ProtocolId): F[ProtocolMetadata] = F.handleErrorWith(Queries.retrieve(id).unique.transact(xa)) { e => F.raiseError(ProtocolNotFound(e.getMessage)) } - override def exists(id: ProtocolId): F[Boolean] = + def exists(id: ProtocolId): F[Boolean] = Queries.exists(id).unique.transact(xa) - override def ping: F[Boolean] = Queries.checkConn.unique.transact(xa) + def ping: F[Boolean] = Queries.checkConn.unique.transact(xa) + + def versionOf(id: ProtocolId): F[Option[ProtocolVersion]] = + Queries.checkVersion.option(id).transact(xa) } } diff --git a/src/main/scala/higherkindness/compendium/metadata/pg/Queries.scala b/src/main/scala/higherkindness/compendium/metadata/pg/Queries.scala index 6342ec0..713b542 100644 --- a/src/main/scala/higherkindness/compendium/metadata/pg/Queries.scala +++ b/src/main/scala/higherkindness/compendium/metadata/pg/Queries.scala @@ -17,10 +17,10 @@ package higherkindness.compendium.metadata.pg import doobie.implicits._ -import doobie.{Query0, Update0} +import doobie._ import higherkindness.compendium.core.doobie.implicits._ import higherkindness.compendium.core.refinements.ProtocolId -import higherkindness.compendium.models.ProtocolMetadata +import higherkindness.compendium.models._ object Queries { @@ -29,11 +29,11 @@ object Queries { SELECT exists (SELECT true FROM metaprotocols WHERE id=$id) """.query[Boolean] - def store(id: ProtocolId, idl_name: String): Update0 = + def store(id: ProtocolId, protocol_version: ProtocolVersion, idl_name: String): Update0 = sql""" INSERT INTO metaprotocols (id, idl_name, version) - VALUES ($id, $idl_name::idl, 1) - ON CONFLICT (id) DO UPDATE SET version = metaprotocols.version + 1 + VALUES ($id, $idl_name::idl, $protocol_version) + ON CONFLICT (id) DO UPDATE SET version = ${protocol_version.incRevision} RETURNING version """.update @@ -44,4 +44,9 @@ object Queries { def checkConn: Query0[Boolean] = sql"SELECT exists (SELECT 1)".query[Boolean] + + val checkVersion: Query[ProtocolId, ProtocolVersion] = + Query( + "SELECT version from metaprotocols WHERE id = ?" + ) } diff --git a/src/main/scala/higherkindness/compendium/models/ProtocolMetadata.scala b/src/main/scala/higherkindness/compendium/models/ProtocolMetadata.scala index cc68ddc..af0ea72 100644 --- a/src/main/scala/higherkindness/compendium/models/ProtocolMetadata.scala +++ b/src/main/scala/higherkindness/compendium/models/ProtocolMetadata.scala @@ -16,6 +16,6 @@ package higherkindness.compendium.models -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} +import higherkindness.compendium.core.refinements.ProtocolId case class ProtocolMetadata(id: ProtocolId, idlName: IdlName, version: ProtocolVersion) diff --git a/src/main/scala/higherkindness/compendium/models/ProtocolVersion.scala b/src/main/scala/higherkindness/compendium/models/ProtocolVersion.scala new file mode 100644 index 0000000..c87255c --- /dev/null +++ b/src/main/scala/higherkindness/compendium/models/ProtocolVersion.scala @@ -0,0 +1,195 @@ +/* + * Copyright 2018-2020 47 Degrees, LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package higherkindness.compendium.models + +import cats.{Eq, Show} +import cats.implicits._ +import cats.kernel.Monoid + +/** + * A case class that contains the version of a Protocol. + * Used this as guideline: https://snowplowanalytics.com/blog/2014/05/13/introducing-schemaver-for-semantic-versioning-of-schemas/ + * + * This versioning is quite similar to SemVer, but applied to Schemas. + * + * @param model equivalent to Major. Changes that will break interaction with historical data. + * @param revision equivalent to Minor. Changes that may, or may not, break interaction with historical data. + * @param addition equivalent to Patch. Changes that do not break the interaction with all historical data. + */ + +final case class ProtocolVersion( + model: ModelVersion, + revision: RevisionVersion, + addition: AdditionVersion +) + +object ProtocolVersion { + + /** + * This function pretends to set a new patch version. + * E.g.: + * + * val ver = ProtocolVersion(ModelVersion(0), RevisionVersion(0), AdditionVersion(0)) + * val newPatch = AdditionVersion(100) + * ProtocolVersion.setPatch(ver, newPatch) == ProtocolVersion(ModelVersion(0), RevisionVersion(0), AdditionVersion(100)) + * + * @param pv Protocol Version + * @return a nre ProtocolVersion + */ + def setAddition(pv: ProtocolVersion, newAddition: AdditionVersion): ProtocolVersion = + pv.copy(addition = newAddition) + + /** + * This function pretends to set a new minor version. + * When a new Revision is set, the Addition is reset to 0. + * E.g.: + * + * val ver = ProtocolVersion(ModelVersion(0), RevisionVersion(0), AdditionVersion(10)) + * val newMinor = RevisionVersion(100) + * ProtocolVersion.setMinor(ver, newMinor) == ProtocolVersion(ModelVersion(0), RevisionVersion(100), AdditionVersion(0)) + * + * @param pv Protocol Version + * @return a nre ProtocolVersion + */ + def setRevision(pv: ProtocolVersion, newRevision: RevisionVersion): ProtocolVersion = + setAddition(pv.copy(revision = newRevision), Monoid[AdditionVersion].empty) + + /** + * This function pretends to set a new minor version. + * When a new model is set, the Revision and the Addition are set to 0. + * E.g.: + * + * val ver = ProtocolVersion(ModelVersion(0), RevisionVersion(1), AdditionVersion(2)) + * val newMajor = ModelVersion(100) + * ProtocolVersion.setMinor(ver, newMajor) == ProtocolVersion(ModelVersion(100), RevisionVersion(0), AdditionVersion(0)) + * + * @param pv Protocol Version + * @return a new ProtocolVersion + */ + def setModel(pv: ProtocolVersion, newModel: ModelVersion): ProtocolVersion = + setRevision(pv.copy(model = newModel), Monoid[RevisionVersion].empty) + + /** + * This function pretends to increment the addition version by one. + * E.g.: + * + * val ver = ProtocolVersion(ModelVersion(0), RevisionVersion(0), AdditionVersion(0)) + * ProtocolVersion.incPatch(ver) == ProtocolVersion(ModelVersion(0), RevisionVersion(0), AdditionVersion(1)) + * + * @param pv Protocol Version + * @return a new ProtocolVersion + */ + def incAddition(pv: ProtocolVersion): ProtocolVersion = + setAddition(pv, pv.addition |+| AdditionVersion(1)) + + /** + * This function pretends to increment the minor version by one. + * E.g.: + * + * val ver = ProtocolVersion(ModelVersion(0), RevisionVersion(1), AdditionVersion(0)) + * ProtocolVersion.incMinor(ver) == ProtocolVersion(ModelVersion(0), RevisionVersion(1), AdditionVersion(0)) + * + * @param pv Protocol Version + * @return a new ProtocolVersion + */ + def incRevision(pv: ProtocolVersion): ProtocolVersion = + setRevision(pv, pv.revision |+| RevisionVersion(1)) + + /** + * This function pretends to increment the minor version by one. + * E.g.: + * + * val ver = ProtocolVersion(ModelVersion(0), RevisionVersion(0), AdditionVersion(0)) + * ProtocolVersion.incMajor(ver) == ProtocolVersion(ModelVersion(1), RevisionVersion(0), AdditionVersion(0)) + * + * @param pv Protocol Version + * @return a new ProtocolVersion + */ + def incModel(pv: ProtocolVersion): ProtocolVersion = setModel(pv, pv.model |+| ModelVersion(1)) + + /** + * A simple function for creating a ProtocolVersion from a String + * E.g.: + * val version = "10.1.9" + * ProtocolVersion.fromString(version) == Right( + * ProtocolVersion(ModelVersion(10), RevisionVersion(1), AdditionVersion(9)) + * ) + * + * @param s the string to be parsed + * @return a ProtocolVersion + */ + def fromString(s: String): Either[ProtocolVersionError, ProtocolVersion] = { + val matcher = "([0-9]+)".r + + def protocolRight( + mV: ModelVersion, + rV: RevisionVersion = Monoid[RevisionVersion].empty, + aV: AdditionVersion = Monoid[AdditionVersion].empty + ) = ProtocolVersion(mV, rV, aV).asRight[ProtocolVersionError] + + matcher.findAllIn(s).toList.map(_.toInt) match { + case List(model, revision, addition) => + protocolRight(ModelVersion(model), RevisionVersion(revision), AdditionVersion(addition)) + case List(mode, revision) => + protocolRight(ModelVersion(mode), RevisionVersion(revision)) + case List(mode) => + protocolRight(ModelVersion(mode)) + case _ => ProtocolVersionError(s"$s is not a valid version string.").asLeft + } + } + + /** A simple val fpr defining de initial version: 1.0.0 */ + val initial: ProtocolVersion = + ProtocolVersion(ModelVersion(1), Monoid[RevisionVersion].empty, Monoid[AdditionVersion].empty) + + /** + * E.g.: + * + * val ver = ProtocolVersion(ModelVersion(10), RevisionVersion(3), AdditionVersion(54)) + * Show[ProtocolVersion].show(ProtocolVersion) == "10.3.54" + * + */ + implicit val protocolVersionShow: Show[ProtocolVersion] = + Show.show(pv => s"${pv.model}.${pv.revision}.${pv.addition}") + + /** + * E.g.: + * + * val ver1 = ProtocolVersion(ModelVersion(10), RevisionVersion(3), AdditionVersion(54)) + * val ver2 = ProtocolVersion(ModelVersion(10), RevisionVersion(3), AdditionVersion(54)) + * Eq[ProtocolVersion].eqv(ver1, ver2) == true + * + */ + implicit val protocolVersionEq: Eq[ProtocolVersion] = new Eq[ProtocolVersion] { + def eqv(x: ProtocolVersion, y: ProtocolVersion): Boolean = + x.model === y.model && x.revision === x.revision && x.addition === y.addition + } + + /** + * Syntax object for ProtocolVersion + * + * @param pv + */ + implicit class ProtocolVersionOps(val pv: ProtocolVersion) extends AnyVal { + def incAddition = ProtocolVersion.incAddition(pv) + def incRevision = ProtocolVersion.incRevision(pv) + def incModel = ProtocolVersion.incModel(pv) + def setAddition(newAddition: AdditionVersion) = ProtocolVersion.setAddition(pv, newAddition) + def setRevision(newRevision: RevisionVersion) = ProtocolVersion.setRevision(pv, newRevision) + def setModel(newModel: ModelVersion) = ProtocolVersion.setModel(pv, newModel) + } +} diff --git a/src/main/scala/higherkindness/compendium/models/Tagged.scala b/src/main/scala/higherkindness/compendium/models/Tagged.scala new file mode 100644 index 0000000..a4f9ac9 --- /dev/null +++ b/src/main/scala/higherkindness/compendium/models/Tagged.scala @@ -0,0 +1,61 @@ +/* + * Copyright 2018-2020 47 Degrees, LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package higherkindness.compendium.models + +import cats.{Eq, Show} +import cats.implicits._ +import io.circe.{Decoder, Encoder} +import io.circe._ +import shapeless.tag +import shapeless.tag.@@ +import org.http4s.QueryParamDecoder + +trait Tagged[T] { + sealed trait Tag + type Type = T @@ Tag + def apply(t: T): T @@ Tag = tag[Tag][T](t) +} + +object Tagged { + + abstract class DeriveCodecEqShow[T: Decoder: Encoder: Eq: Show] extends Tagged[T] { + implicit val decoderTagged: Decoder[Type] = + Decoder[T].map(apply) + + implicit val encoderTagged: Encoder[Type] = + Encoder[T].narrow + + implicit val eqTagged: Eq[Type] = + Eq[T].narrow + + implicit val showTagged: Show[Type] = + Show[T].narrow + } + + class Str extends DeriveCodecEqShow[String] { + override implicit val showTagged: Show[Type] = Show[String].narrow + } + + class Number extends DeriveCodecEqShow[Int] { + override implicit val showTagged: Show[Type] = Show[Int].narrow + } + + trait TaggedQueryParamDecoder[T] { self: Tagged[T] => + implicit def queryParamDecoder(implicit base: QueryParamDecoder[T]): QueryParamDecoder[Type] = + base.map(apply) + } +} diff --git a/src/main/scala/higherkindness/compendium/models/errors.scala b/src/main/scala/higherkindness/compendium/models/errors.scala index 9903902..20be3ad 100644 --- a/src/main/scala/higherkindness/compendium/models/errors.scala +++ b/src/main/scala/higherkindness/compendium/models/errors.scala @@ -18,8 +18,7 @@ package higherkindness.compendium.models abstract class CompendiumError(error: String) extends Exception(error) -final case class FileNotFound(fileName: String) - extends CompendiumError(s"File with name $fileName not found") +final case class FileNotFound(fileName: String) extends CompendiumError(s"File with name $fileName not found") final case class ProtocolIdError(msg: String) extends CompendiumError(msg) final case class ProtocolVersionError(msg: String) extends CompendiumError(msg) final case class ProtocolNotFound(msg: String) extends CompendiumError(msg) diff --git a/src/main/scala/higherkindness/compendium/models/package.scala b/src/main/scala/higherkindness/compendium/models/package.scala new file mode 100644 index 0000000..a9c8cff --- /dev/null +++ b/src/main/scala/higherkindness/compendium/models/package.scala @@ -0,0 +1,47 @@ +/* + * Copyright 2018-2020 47 Degrees, LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package higherkindness.compendium + +import cats.kernel.Monoid + +package object models { + + type ModelVersion = ModelVersion.Type + object ModelVersion extends Tagged.Number { + implicit val majorVersionMonoid: Monoid[ModelVersion] = new Monoid[ModelVersion] { + def combine(x: ModelVersion, y: ModelVersion): ModelVersion = ModelVersion(x + y) + def empty: ModelVersion = ModelVersion(0) + } + } + + type RevisionVersion = RevisionVersion.Type + object RevisionVersion extends Tagged.Number { + implicit val minorVersionMonoid: Monoid[RevisionVersion] = new Monoid[RevisionVersion] { + def combine(x: RevisionVersion, y: RevisionVersion): RevisionVersion = RevisionVersion(x + y) + def empty: RevisionVersion = RevisionVersion(0) + } + } + + type AdditionVersion = AdditionVersion.Type + object AdditionVersion extends Tagged.Number { + implicit val patchVersionMonoid: Monoid[AdditionVersion] = new Monoid[AdditionVersion] { + def combine(x: AdditionVersion, y: AdditionVersion): AdditionVersion = AdditionVersion(x + y) + def empty: AdditionVersion = AdditionVersion(0) + } + } + +} diff --git a/src/main/scala/higherkindness/compendium/storage/Storage.scala b/src/main/scala/higherkindness/compendium/storage/Storage.scala index c47e309..dd1833a 100644 --- a/src/main/scala/higherkindness/compendium/storage/Storage.scala +++ b/src/main/scala/higherkindness/compendium/storage/Storage.scala @@ -16,11 +16,10 @@ package higherkindness.compendium.storage -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} +import higherkindness.compendium.core.refinements.ProtocolId import higherkindness.compendium.models._ trait Storage[F[_]] { - def store(id: ProtocolId, version: ProtocolVersion, protocol: Protocol): F[Unit] def retrieve(metadata: ProtocolMetadata): F[FullProtocol] def exists(id: ProtocolId): F[Boolean] diff --git a/src/main/scala/higherkindness/compendium/storage/files/FileStorage.scala b/src/main/scala/higherkindness/compendium/storage/files/FileStorage.scala index 5955540..ed8e2e4 100644 --- a/src/main/scala/higherkindness/compendium/storage/files/FileStorage.scala +++ b/src/main/scala/higherkindness/compendium/storage/files/FileStorage.scala @@ -20,7 +20,7 @@ import java.io.{File, FilenameFilter, PrintWriter} import cats.effect.Sync import cats.implicits._ -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} +import higherkindness.compendium.core.refinements.ProtocolId import higherkindness.compendium.models.config.FileStorageConfig import higherkindness.compendium.models._ import higherkindness.compendium.storage.Storage @@ -32,11 +32,11 @@ object FileStorage { * * - protocol identifier should comply with * [[higherkindness.compendium.core.refinements.ProtocolId]] predicates - * - version should be a positive zero-leftpadded five digits version number like 00001 + * - version should be a version like format: xx.yy.zz, xx.yy, or xx * - extension is `protocol` */ private[storage] def buildFilename(id: ProtocolId, version: ProtocolVersion): String = - s"${id.value}_${f"${version.value}%05d"}.protocol" + s"${id.value}_${version.show}.protocol" def apply[F[_]: Sync](config: FileStorageConfig): Storage[F] = new Storage[F] { diff --git a/src/main/scala/higherkindness/compendium/storage/pg/PgStorage.scala b/src/main/scala/higherkindness/compendium/storage/pg/PgStorage.scala index a10026e..a68b69a 100644 --- a/src/main/scala/higherkindness/compendium/storage/pg/PgStorage.scala +++ b/src/main/scala/higherkindness/compendium/storage/pg/PgStorage.scala @@ -20,8 +20,8 @@ import cats.effect.Bracket import cats.syntax.functor._ import doobie.implicits._ import doobie.util.transactor.Transactor -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} -import higherkindness.compendium.models.{FullProtocol, Protocol, ProtocolMetadata} +import higherkindness.compendium.core.refinements.ProtocolId +import higherkindness.compendium.models._ import higherkindness.compendium.storage.Storage private class PgStorage[F[_]: Bracket[*[_], Throwable]](xa: Transactor[F]) extends Storage[F] { diff --git a/src/main/scala/higherkindness/compendium/storage/pg/Queries.scala b/src/main/scala/higherkindness/compendium/storage/pg/Queries.scala index 5fa5c3f..2ddc48d 100644 --- a/src/main/scala/higherkindness/compendium/storage/pg/Queries.scala +++ b/src/main/scala/higherkindness/compendium/storage/pg/Queries.scala @@ -18,9 +18,9 @@ package higherkindness.compendium.storage.pg import doobie.syntax.string._ import doobie.{Query0, Update0} -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} +import higherkindness.compendium.core.refinements.ProtocolId import higherkindness.compendium.core.doobie.implicits._ -import higherkindness.compendium.models.Protocol +import higherkindness.compendium.models.{Protocol, ProtocolVersion} object Queries { diff --git a/src/main/scala/higherkindness/compendium/transformer/skeuomorph/SkeuomorphProtocolTransformer.scala b/src/main/scala/higherkindness/compendium/transformer/skeuomorph/SkeuomorphProtocolTransformer.scala index edddc76..c9e8746 100644 --- a/src/main/scala/higherkindness/compendium/transformer/skeuomorph/SkeuomorphProtocolTransformer.scala +++ b/src/main/scala/higherkindness/compendium/transformer/skeuomorph/SkeuomorphProtocolTransformer.scala @@ -43,9 +43,7 @@ object SkeuomorphProtocolTransformer { protobuf.ParseProto .parseProto[F, Mu[protobuf.ProtobufF]] .parse(source) - .map(protobuf => - mu.Protocol.fromProtobufProto(mu.CompressionType.Identity, true)(protobuf) - ) + .map(protobuf => mu.Protocol.fromProtobufProto(mu.CompressionType.Identity, true)(protobuf)) .flatMap(p => Sync[F] .fromEither(mu.codegen.protocol(p, streamCtor).leftMap(TransformError).map(_.syntax)) diff --git a/src/test/scala/higherkindness/compendium/CompendiumArbitrary.scala b/src/test/scala/higherkindness/compendium/CompendiumArbitrary.scala index 132f7f8..5982f0e 100644 --- a/src/test/scala/higherkindness/compendium/CompendiumArbitrary.scala +++ b/src/test/scala/higherkindness/compendium/CompendiumArbitrary.scala @@ -17,8 +17,8 @@ package higherkindness.compendium import cats.syntax.apply._ -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} -import higherkindness.compendium.models.{IdlName, Protocol, ProtocolMetadata} +import higherkindness.compendium.core.refinements.ProtocolId +import higherkindness.compendium.models._ import org.scalacheck._ import org.scalacheck.cats.implicits._ @@ -44,7 +44,7 @@ trait CompendiumArbitrary { ( protocolIdArbitrary.arbitrary, idlNamesArbitrary.arbitrary, - Gen.choose(1, 99999).map(ProtocolVersion.unsafeFrom) + protocolVersionArb.arbitrary ).mapN(ProtocolMetadata.apply) } @@ -55,6 +55,33 @@ trait CompendiumArbitrary { } yield DifferentIdentifiers(id1, id2) } + private def genVersion[A](f: Int => A) = Gen.posNum[Int].map(f(_)) + + implicit val additionVersionArb: Arbitrary[AdditionVersion] = Arbitrary( + genVersion(AdditionVersion(_)) + ) + + implicit val revisionVersionArb: Arbitrary[RevisionVersion] = Arbitrary( + genVersion(RevisionVersion(_)) + ) + + implicit val modelVersionArb: Arbitrary[ModelVersion] = Arbitrary( + genVersion(ModelVersion(_)) + ) + + implicit val protocolVersionArb: Arbitrary[ProtocolVersion] = Arbitrary( + (modelVersionArb.arbitrary, revisionVersionArb.arbitrary, additionVersionArb.arbitrary) + .mapN(ProtocolVersion.apply) + ) + + implicit val rawProtocolVersionArb: Arbitrary[String] = Arbitrary( + (Gen.posNum[Int], Gen.posNum[Int], Gen.posNum[Int]).mapN { case (f, m, t) => s"$f.$m.$t" } + ) + + implicit val invalidProtocolVersion: Arbitrary[String] = Arbitrary( + (Gen.alphaChar, Gen.alphaChar, Gen.alphaChar).mapN { case (f, m, t) => s"$f.$m.$t" } + ) + } object CompendiumArbitrary extends CompendiumArbitrary diff --git a/src/test/scala/higherkindness/compendium/core/CompendiumServiceStub.scala b/src/test/scala/higherkindness/compendium/core/CompendiumServiceStub.scala index 95bb875..ccade0c 100644 --- a/src/test/scala/higherkindness/compendium/core/CompendiumServiceStub.scala +++ b/src/test/scala/higherkindness/compendium/core/CompendiumServiceStub.scala @@ -17,18 +17,19 @@ package higherkindness.compendium.core import cats.effect.IO -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} +import higherkindness.compendium.core.refinements.ProtocolId import higherkindness.compendium.models._ -class CompendiumServiceStub(protocolOpt: Option[FullProtocol], exists: Boolean) - extends CompendiumService[IO] { +class CompendiumServiceStub(protocolOpt: Option[FullProtocol], exists: Boolean) extends CompendiumService[IO] { + + val protocolVersion = ProtocolVersion.initial override def storeProtocol( id: ProtocolId, protocol: Protocol, idlName: IdlName ): IO[ProtocolVersion] = - IO.pure(protocolOpt.map(_.metadata.version).getOrElse(ProtocolVersion(1))) + IO.pure(protocolOpt.map(_.metadata.version).getOrElse(protocolVersion)) override def retrieveProtocol( id: ProtocolId, diff --git a/src/test/scala/higherkindness/compendium/core/ProtocolUtilsSpec.scala b/src/test/scala/higherkindness/compendium/core/ProtocolUtilsSpec.scala index b55c67f..b2b9fdf 100644 --- a/src/test/scala/higherkindness/compendium/core/ProtocolUtilsSpec.scala +++ b/src/test/scala/higherkindness/compendium/core/ProtocolUtilsSpec.scala @@ -40,11 +40,10 @@ object ProtocolUtilsSpec extends Specification with ScalaCheck { validator.validateProtocol(protocol, IdlName.Avro).unsafeRunSync === protocol } - "[Avro] Given a raw protocol text raises an error if the protocol is incorrect" >> prop { - protocol: Protocol => - validator - .validateProtocol(protocol, IdlName.Avro) - .unsafeRunSync must throwA[SchemaParseException] + "[Avro] Given a raw protocol text raises an error if the protocol is incorrect" >> prop { protocol: Protocol => + validator + .validateProtocol(protocol, IdlName.Avro) + .unsafeRunSync must throwA[SchemaParseException] } "[Avro] Given multiple protocols validates them sequentially" >> { diff --git a/src/test/scala/higherkindness/compendium/core/refinementsSpec.scala b/src/test/scala/higherkindness/compendium/core/refinementsSpec.scala new file mode 100644 index 0000000..f45a744 --- /dev/null +++ b/src/test/scala/higherkindness/compendium/core/refinementsSpec.scala @@ -0,0 +1,66 @@ +/* + * Copyright 2018-2020 47 Degrees, LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package higherkindness.compendium.core + +import cats.effect.IO +import cats.implicits._ +import org.specs2.mutable.Specification +import org.specs2.matcher.IOMatchers +import org.specs2.ScalaCheck +import org.scalacheck.Prop +import higherkindness.compendium.CompendiumArbitrary._ +import higherkindness.compendium.core.refinements.ProtocolVersionRefined +import higherkindness.compendium.models.ProtocolVersionError + +object refinementsSpec extends Specification with ScalaCheck with IOMatchers { + + private val shortVersion: String => String = _.dropWhile(_ != '.').tail + + "ProtocolVersionRefined" >> { + "parseOrRaise should parse a correct version of type xx.yy.zz" >> Prop.forAll( + rawProtocolVersionArb.arbitrary + ) { rawVersion => + ProtocolVersionRefined.parseOrRaise[IO](rawVersion).map(_.show) must returnValue(rawVersion) + } + + "parseOrRaise should parse a correct version of type xx.yy" >> Prop.forAll( + rawProtocolVersionArb.arbitrary + ) { rawVersion => + val fixedVersion = shortVersion(rawVersion) + ProtocolVersionRefined.parseOrRaise[IO](fixedVersion).map(_.show) must returnValue( + fixedVersion |+| ".0" + ) + } + + "parseOrRaise should parse a correct version of type xx" >> Prop.forAll( + rawProtocolVersionArb.arbitrary + ) { rawVersion => + val fixedVersion = shortVersion(shortVersion(rawVersion)) + ProtocolVersionRefined.parseOrRaise[IO](fixedVersion).map(_.show) must returnValue( + fixedVersion |+| ".0.0" + ) + } + + "parseOrRaise shoudl raise a ProtocolVersionError on error" >> Prop.forAll( + invalidProtocolVersion.arbitrary + ) { rawVersion => + ProtocolVersionRefined + .parseOrRaise[IO](rawVersion) + .unsafeRunSync() must throwA[ProtocolVersionError] + } + } +} diff --git a/src/test/scala/higherkindness/compendium/http/RootServiceSpec.scala b/src/test/scala/higherkindness/compendium/http/RootServiceSpec.scala index 98a4faf..10a1847 100644 --- a/src/test/scala/higherkindness/compendium/http/RootServiceSpec.scala +++ b/src/test/scala/higherkindness/compendium/http/RootServiceSpec.scala @@ -19,7 +19,7 @@ package higherkindness.compendium.http import cats.effect.IO import cats.syntax.all._ import higherkindness.compendium.CompendiumArbitrary._ -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} +import higherkindness.compendium.core.refinements.ProtocolId import higherkindness.compendium.core.CompendiumServiceStub import higherkindness.compendium.models._ import higherkindness.compendium.models.transformer.types.SchemaParseException @@ -38,8 +38,10 @@ object RootServiceSpec extends Specification with ScalaCheck { sequential + private val protocolVersion = ProtocolVersion.initial + private val dummyProtocol: ProtocolId => FullProtocol = (pid: ProtocolId) => - FullProtocol(ProtocolMetadata(pid, IdlName.Avro, ProtocolVersion(1)), Protocol("")) + FullProtocol(ProtocolMetadata(pid, IdlName.Avro, protocolVersion), Protocol("")) "GET /protocol/id?version={version}" >> { "If successs returns a valid protocol" >> prop { id: ProtocolId => @@ -113,14 +115,14 @@ object RootServiceSpec extends Specification with ScalaCheck { response.map(_.status).unsafeRunSync === Status.BadRequest } - "If protocol version is non positive returns bad request" >> { + "If protocol version is non valid returns bad request" >> { implicit val compendiumService = CompendiumServiceStub(Some(dummyProtocol(ProtocolId("id"))), true) val request: Request[IO] = Request[IO]( method = Method.GET, - uri = Uri(path = "/protocol/id", query = Query("version" -> Option("0"))) + uri = Uri(path = "/protocol/id", query = Query("version" -> Option("pa.ta.ta"))) ) val response: IO[Response[IO]] = @@ -153,23 +155,24 @@ object RootServiceSpec extends Specification with ScalaCheck { response.map(_.status).unsafeRunSync === Status.BadRequest } - "If protocol is valid returns Created with id in the body and location in the headers" >> prop { - id: ProtocolId => - implicit val compendiumService = CompendiumServiceStub(None, false) + "If protocol is valid returns Created with id in the body and location in the headers" >> prop { id: ProtocolId => + implicit val compendiumService = CompendiumServiceStub(None, false) - val request: Request[IO] = - Request[IO]( - method = Method.POST, - uri = Uri(path = s"/protocol/${id.value}", query = Query("idlName" -> Option("avro"))) - ).withEntity(dummyProtocol(id).protocol) + val request: Request[IO] = + Request[IO]( + method = Method.POST, + uri = Uri(path = s"/protocol/${id.value}", query = Query("idlName" -> Option("avro"))) + ).withEntity(dummyProtocol(id).protocol) - val response = RootService.rootRouteService[IO].orNotFound(request).unsafeRunSync + val response = RootService.rootRouteService[IO].orNotFound(request).unsafeRunSync - response.status === Status.Created - response.headers.find(_.name == "Location".ci).map(_.value) === Some( - s"/protocol/$id?idlName=avro" - ) - response.as[Int].unsafeRunSync === 1 // Default version number when POSTing new protocols + response.status === Status.Created + response.headers.find(_.name == "Location".ci).map(_.value) === Some( + s"/protocol/$id?idlName=avro" + ) + response + .as[String] + .unsafeRunSync === "1.0.0" // Default version number when POSTing new protocols } "If protocol identifier is malformed returns bad request" >> { @@ -300,23 +303,22 @@ object RootServiceSpec extends Specification with ScalaCheck { response.status === Status.BadRequest } - "If identifier is valid but target is invalid returns bad request" >> prop { - metadata: ProtocolMetadata => - val dummyProto = dummyProtocol(metadata.id) + "If identifier is valid but target is invalid returns bad request" >> prop { metadata: ProtocolMetadata => + val dummyProto = dummyProtocol(metadata.id) - implicit val compendiumService = CompendiumServiceStub(dummyProto.some, true) + implicit val compendiumService = CompendiumServiceStub(dummyProto.some, true) - val request = Request[IO]( - method = Method.GET, - uri = Uri( - path = s"/protocol/${metadata.id.value}/transformation", - query = Query.fromString("target=wrong") - ) + val request = Request[IO]( + method = Method.GET, + uri = Uri( + path = s"/protocol/${metadata.id.value}/transformation", + query = Query.fromString("target=wrong") ) + ) - val response = RootService.rootRouteService[IO].orNotFound(request).unsafeRunSync + val response = RootService.rootRouteService[IO].orNotFound(request).unsafeRunSync - response.status === Status.BadRequest + response.status === Status.BadRequest } "If target is valid but identifier is invalid returns bad request" >> { diff --git a/src/test/scala/higherkindness/compendium/metadata/MetadataStorageStub.scala b/src/test/scala/higherkindness/compendium/metadata/MetadataStorageStub.scala index 3707281..2f05d73 100644 --- a/src/test/scala/higherkindness/compendium/metadata/MetadataStorageStub.scala +++ b/src/test/scala/higherkindness/compendium/metadata/MetadataStorageStub.scala @@ -17,19 +17,24 @@ package higherkindness.compendium.metadata import cats.effect.IO -import higherkindness.compendium.models.{IdlName, ProtocolMetadata, ProtocolNotFound} -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} +import higherkindness.compendium.models._ +import higherkindness.compendium.core.refinements.ProtocolId +import higherkindness.compendium.core.refinements -class MetadataStorageStub(val exists: Boolean, metadata: Option[ProtocolMetadata] = None) - extends MetadataStorage[IO] { - override def store(id: ProtocolId, idlNames: IdlName): IO[ProtocolVersion] = - IO.pure(metadata.map(_.version).getOrElse(ProtocolVersion(1))) - override def exists(id: ProtocolId): IO[Boolean] = IO.pure(exists) - override def ping: IO[Boolean] = IO.pure(exists) +class MetadataStorageStub(val exists: Boolean, metadata: Option[ProtocolMetadata] = None) extends MetadataStorage[IO] { - override def retrieve(id: ProtocolId): IO[ProtocolMetadata] = + private val prtocolVersion = ProtocolVersion.initial + + def store(id: ProtocolId, protocolVersion: ProtocolVersion, idlNames: IdlName): IO[ProtocolVersion] = + IO.pure(metadata.map(_.version).getOrElse(prtocolVersion)) + def exists(id: ProtocolId): IO[Boolean] = IO.pure(exists) + def ping: IO[Boolean] = IO.pure(exists) + + def retrieve(id: ProtocolId): IO[ProtocolMetadata] = metadata.fold(IO.raiseError[ProtocolMetadata](ProtocolNotFound("Protocol not found")))(mp => if (mp.id == id) IO(mp) else IO.raiseError[ProtocolMetadata](ProtocolNotFound("Protocol not found")) ) + + def versionOf(id: refinements.ProtocolId): IO[Option[ProtocolVersion]] = IO.pure(Some(prtocolVersion)) } diff --git a/src/test/scala/higherkindness/compendium/metadata/pg/MetadataQueriesSpec.scala b/src/test/scala/higherkindness/compendium/metadata/pg/MetadataQueriesSpec.scala index cdd4546..634a15e 100644 --- a/src/test/scala/higherkindness/compendium/metadata/pg/MetadataQueriesSpec.scala +++ b/src/test/scala/higherkindness/compendium/metadata/pg/MetadataQueriesSpec.scala @@ -21,19 +21,22 @@ import higherkindness.compendium.core.refinements.ProtocolId import higherkindness.compendium.metadata.MigrationsMode.Metadata import higherkindness.compendium.metadata.PGHelper import org.specs2.specification.Scope +import higherkindness.compendium.models.ProtocolVersion class MetadataQueriesSpec extends PGHelper(Metadata) with IOChecker { "MetadataQueries" should { "match db model" in new context { check(Queries.exists(protocolId)) - check(Queries.store(protocolId, idlName)) + check(Queries.store(protocolId, protocolVersion, idlName)) + check(Queries.checkVersion) } } trait context extends Scope { val protocolId: ProtocolId = ProtocolId("my-test.protocol.id") val idlName: String = "Protobuf" + val protocolVersion = ProtocolVersion.initial } } diff --git a/src/test/scala/higherkindness/compendium/metadata/pg/PgMetadataStorageSpec.scala b/src/test/scala/higherkindness/compendium/metadata/pg/PgMetadataStorageSpec.scala index 189ff68..708ed64 100644 --- a/src/test/scala/higherkindness/compendium/metadata/pg/PgMetadataStorageSpec.scala +++ b/src/test/scala/higherkindness/compendium/metadata/pg/PgMetadataStorageSpec.scala @@ -18,14 +18,15 @@ package higherkindness.compendium.metadata.pg import cats.effect.IO import cats.implicits._ -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} +import higherkindness.compendium.core.refinements.ProtocolId import higherkindness.compendium.metadata.MigrationsMode.Metadata import higherkindness.compendium.metadata.PGHelper -import higherkindness.compendium.models.{IdlName, ProtocolMetadata} +import higherkindness.compendium.models._ class PgMetadataStorageSpec extends PGHelper(Metadata) { private lazy val pg = PgMetadataStorage[IO](transactor) + private val protocolVersion = ProtocolVersion.initial "Postgres Service" should { "insert protocol correctly" in { @@ -33,9 +34,9 @@ class PgMetadataStorageSpec extends PGHelper(Metadata) { val idlName: IdlName = IdlName.Avro val result: IO[ProtocolMetadata] = - pg.store(id, idlName) >> pg.retrieve(id) + pg.store(id, protocolVersion, idlName) >> pg.retrieve(id) - val expected = ProtocolMetadata(id, idlName, ProtocolVersion(1)) + val expected = ProtocolMetadata(id, idlName, protocolVersion) result.unsafeRunSync must_=== expected @@ -46,10 +47,10 @@ class PgMetadataStorageSpec extends PGHelper(Metadata) { val idlName: IdlName = IdlName.Avro val result: IO[ProtocolMetadata] = - pg.store(id, idlName) >> pg.store(id, idlName) >> pg + pg.store(id, protocolVersion, idlName) >> pg.store(id, protocolVersion, idlName) >> pg .retrieve(id) - val expected = ProtocolMetadata(id, idlName, ProtocolVersion(2)) + val expected = ProtocolMetadata(id, idlName, protocolVersion.incRevision) result.unsafeRunSync must_=== expected } @@ -65,7 +66,7 @@ class PgMetadataStorageSpec extends PGHelper(Metadata) { val idlName: IdlName = IdlName.Avro val result: IO[Boolean] = - pg.store(id, idlName) >> pg.exists(id) + pg.store(id, protocolVersion, idlName) >> pg.exists(id) result.unsafeRunSync must_=== true } diff --git a/src/test/scala/higherkindness/compendium/models/ProtocolVersionSpec.scala b/src/test/scala/higherkindness/compendium/models/ProtocolVersionSpec.scala new file mode 100644 index 0000000..2e56e9b --- /dev/null +++ b/src/test/scala/higherkindness/compendium/models/ProtocolVersionSpec.scala @@ -0,0 +1,112 @@ +/* + * Copyright 2018-2020 47 Degrees, LLC. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package higherkindness.compendium.models + +import cats.implicits._ +import cats.kernel.Monoid +import higherkindness.compendium.CompendiumArbitrary._ +import higherkindness.compendium.models._ +import higherkindness.compendium.models.ProtocolVersion._ +import org.specs2.mutable.Specification +import org.specs2.ScalaCheck +import org.scalacheck.Prop + +object ProtocolVersionSpec extends Specification with ScalaCheck { + + "Set" >> { + "setAddition should set the correct addition value" >> prop { (pv: ProtocolVersion, addVer: AdditionVersion) => + pv.setAddition(addVer).addition === addVer + } + + "setRevision should set the correct revision value and reset addition" >> prop { + (pv: ProtocolVersion, revVer: RevisionVersion) => + val version = pv.setRevision(revVer) + version.revision === revVer && version.addition === Monoid[AdditionVersion].empty + } + + "setModel should set the correct model value and reset revision and addition" >> prop { + (pv: ProtocolVersion, modelVer: ModelVersion) => + val version = pv.setModel(modelVer) + version.model === modelVer && version.revision === Monoid[ + RevisionVersion + ].empty && version.addition === Monoid[AdditionVersion].empty + } + } + + "Increment" >> { + "incAddition should increment the addition" >> prop { protocolVersion: ProtocolVersion => + protocolVersion.incAddition === protocolVersion.setAddition( + protocolVersion.addition |+| AdditionVersion(1) + ) + } + + "incRevision should increment the revision" >> prop { protocolVersion: ProtocolVersion => + protocolVersion.incRevision === protocolVersion.setRevision( + protocolVersion.revision |+| RevisionVersion(1) + ) + } + + "incModel should increment the model" >> prop { protocolVersion: ProtocolVersion => + protocolVersion.incModel === protocolVersion.setModel( + protocolVersion.model |+| ModelVersion(1) + ) + } + } + + "Show" >> { + "Show should create the correct string" >> prop { protocolVersion: ProtocolVersion => + protocolVersion.show === show"${protocolVersion.model}.${protocolVersion.revision}.${protocolVersion.addition}" + } + } + + "Eq" >> { + "Eq should work as expected" >> prop { protocolVersion: ProtocolVersion => + protocolVersion === protocolVersion && protocolVersion.incAddition =!= protocolVersion + } + } + + "Parsing" >> { + val shortVersion: String => String = _.dropWhile(_ != '.').tail + + "fromString should parse a correct version of type xx.yy.zz" >> Prop.forAllNoShrink( + rawProtocolVersionArb.arbitrary + ) { rawVersion => + ProtocolVersion.fromString(rawVersion).map(_.show) must_== Right(rawVersion) + } + + "fromString should parse a correct version of type xx.yy" >> Prop.forAllNoShrink( + rawProtocolVersionArb.arbitrary + ) { rawVersion => + val fixedVersion = shortVersion(rawVersion) + ProtocolVersion.fromString(fixedVersion).map(_.show) must_== Right(fixedVersion |+| ".0") + } + + "fromString should parse a correct version of type xx" >> Prop.forAllNoShrink( + rawProtocolVersionArb.arbitrary + ) { rawVersion => + val fixedVersion = shortVersion(shortVersion(rawVersion)) + ProtocolVersion.fromString(fixedVersion).map(_.show) must_== Right(fixedVersion |+| ".0.0") + } + + "fromString should return a Left(ProtocolVersionError) on error" >> Prop.forAllNoShrink( + invalidProtocolVersion.arbitrary + ) { rawVersion => + ProtocolVersion.fromString(rawVersion) must beLeft[ProtocolVersionError] + } + } + +} diff --git a/src/test/scala/higherkindness/compendium/storage/StorageStub.scala b/src/test/scala/higherkindness/compendium/storage/StorageStub.scala index c4ebeb2..90b90a2 100644 --- a/src/test/scala/higherkindness/compendium/storage/StorageStub.scala +++ b/src/test/scala/higherkindness/compendium/storage/StorageStub.scala @@ -17,7 +17,7 @@ package higherkindness.compendium.storage import cats.effect.IO -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} +import higherkindness.compendium.core.refinements.ProtocolId import higherkindness.compendium.models._ import org.specs2.matcher.Matchers @@ -36,9 +36,7 @@ class StorageStub( override def retrieve(metadata: ProtocolMetadata): IO[FullProtocol] = if (metadata.id == identifier && metadata.version == protoVersion) - proto.fold(IO.raiseError[FullProtocol](ProtocolNotFound("Not Found")))(p => - IO.pure(FullProtocol(metadata, p)) - ) + proto.fold(IO.raiseError[FullProtocol](ProtocolNotFound("Not Found")))(p => IO.pure(FullProtocol(metadata, p))) else IO.raiseError[FullProtocol](ProtocolNotFound("Not found")) override def exists(id: ProtocolId): IO[Boolean] = diff --git a/src/test/scala/higherkindness/compendium/storage/files/FileStorageSpec.scala b/src/test/scala/higherkindness/compendium/storage/files/FileStorageSpec.scala index dc517db..504cb38 100644 --- a/src/test/scala/higherkindness/compendium/storage/files/FileStorageSpec.scala +++ b/src/test/scala/higherkindness/compendium/storage/files/FileStorageSpec.scala @@ -21,9 +21,9 @@ import java.nio.file.Paths import cats.effect.IO import higherkindness.compendium.CompendiumArbitrary._ -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} +import higherkindness.compendium.core.refinements.ProtocolId import higherkindness.compendium.models.config.FileStorageConfig -import higherkindness.compendium.models.{FullProtocol, Protocol, ProtocolMetadata} +import higherkindness.compendium.models._ import higherkindness.compendium.storage.{files, Storage} import org.specs2.ScalaCheck import org.specs2.mutable.Specification @@ -71,14 +71,13 @@ object FileStorageSpec extends Specification with ScalaCheck with BeforeAfterAll io.unsafeRunSync() should beTrue } - "Successfully stores and recovers a file" >> prop { - (metadata: ProtocolMetadata, protocol: Protocol) => - val file = for { - _ <- fileStorage.store(metadata.id, metadata.version, protocol) - f <- fileStorage.retrieve(metadata) - } yield f + "Successfully stores and recovers a file" >> prop { (metadata: ProtocolMetadata, protocol: Protocol) => + val file = for { + _ <- fileStorage.store(metadata.id, metadata.version, protocol) + f <- fileStorage.retrieve(metadata) + } yield f - file.unsafeRunSync() must_=== FullProtocol(metadata, protocol) + file.unsafeRunSync() must_=== FullProtocol(metadata, protocol) } "Returns true if there is a file" >> prop { (metadata: ProtocolMetadata, protocol: Protocol) => diff --git a/src/test/scala/higherkindness/compendium/storage/pg/PgStorageSpec.scala b/src/test/scala/higherkindness/compendium/storage/pg/PgStorageSpec.scala index 985e73f..544ef3d 100644 --- a/src/test/scala/higherkindness/compendium/storage/pg/PgStorageSpec.scala +++ b/src/test/scala/higherkindness/compendium/storage/pg/PgStorageSpec.scala @@ -18,20 +18,21 @@ package higherkindness.compendium.storage.pg import cats.effect.IO import cats.implicits._ -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} +import higherkindness.compendium.core.refinements.ProtocolId import higherkindness.compendium.metadata.MigrationsMode.Data import higherkindness.compendium.metadata.PGHelper -import higherkindness.compendium.models.{FullProtocol, IdlName, Protocol, ProtocolMetadata} +import higherkindness.compendium.models._ class PgStorageSpec extends PGHelper(Data) { private lazy val pgStorage = PgStorage[IO](transactor) + private val protocolVersion = ProtocolVersion.initial "Postgres Storage" should { "insert protocol correctly" in { val id = ProtocolId("p1") - val version = ProtocolVersion(1) + val version = protocolVersion val metadata = ProtocolMetadata(id, IdlName.Avro, version) val proto = Protocol("the new protocol content") val fullProto = FullProtocol(metadata, proto) @@ -44,8 +45,8 @@ class PgStorageSpec extends PGHelper(Data) { "update protocol correctly" in { val id = ProtocolId("proto1") - val version1 = ProtocolVersion(1) - val version2 = ProtocolVersion(2) + val version1 = protocolVersion + val version2 = protocolVersion.incModel val proto1 = Protocol("The protocol one content") val proto2 = Protocol("The protocol two content") val metadata = ProtocolMetadata(id, IdlName.Mu, version2) @@ -66,7 +67,7 @@ class PgStorageSpec extends PGHelper(Data) { "return true when the protocol exists" in { val id = ProtocolId("pId3") - val version = ProtocolVersion(1) + val version = protocolVersion val proto = Protocol("Another protocol") val result: IO[Boolean] = pgStorage.store(id, version, proto) >> pgStorage.exists(id) diff --git a/src/test/scala/higherkindness/compendium/storage/pg/StorageQueriesSpec.scala b/src/test/scala/higherkindness/compendium/storage/pg/StorageQueriesSpec.scala index 009aae5..3a57c4d 100644 --- a/src/test/scala/higherkindness/compendium/storage/pg/StorageQueriesSpec.scala +++ b/src/test/scala/higherkindness/compendium/storage/pg/StorageQueriesSpec.scala @@ -17,10 +17,10 @@ package higherkindness.compendium.storage.pg import doobie.specs2._ -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} +import higherkindness.compendium.core.refinements.ProtocolId import higherkindness.compendium.metadata.MigrationsMode.Data import higherkindness.compendium.metadata.PGHelper -import higherkindness.compendium.models.Protocol +import higherkindness.compendium.models._ import org.specs2.specification.Scope class StorageQueriesSpec extends PGHelper(Data) with IOChecker { @@ -35,8 +35,8 @@ class StorageQueriesSpec extends PGHelper(Data) with IOChecker { trait context extends Scope { val protocolId = ProtocolId("my.test.protocol.id") - val version = ProtocolVersion(1) - val protocol = Protocol("Raw protocol content") + val version = ProtocolVersion.initial + val protocol = Protocol("Raw protocol content") } } diff --git a/src/test/scala/higherkindness/compendium/transformer/SkeuomorphProtocolTransformerSpec.scala b/src/test/scala/higherkindness/compendium/transformer/SkeuomorphProtocolTransformerSpec.scala index b79fc9d..cb58941 100644 --- a/src/test/scala/higherkindness/compendium/transformer/SkeuomorphProtocolTransformerSpec.scala +++ b/src/test/scala/higherkindness/compendium/transformer/SkeuomorphProtocolTransformerSpec.scala @@ -17,7 +17,7 @@ package higherkindness.compendium.transformer import cats.effect.IO -import higherkindness.compendium.core.refinements.{ProtocolId, ProtocolVersion} +import higherkindness.compendium.core.refinements.ProtocolId import higherkindness.compendium.models._ import higherkindness.compendium.transformer.skeuomorph.SkeuomorphProtocolTransformer import org.specs2.mutable.Specification @@ -27,10 +27,11 @@ class SkeuomorphProtocolTransformerSpec extends Specification { import protocols._ val transformer = SkeuomorphProtocolTransformer[IO] + val protocolVersion = ProtocolVersion.initial "Skeuomorph based protocol transformer" should { "Transform a simple Avro Schema to Mu" >> { - val protocolMetadata = ProtocolMetadata(ProtocolId("id"), IdlName.Avro, ProtocolVersion(1)) + val protocolMetadata = ProtocolMetadata(ProtocolId("id"), IdlName.Avro, protocolVersion) val fullProtocol = FullProtocol(protocolMetadata, Protocol(simpleAvroExample)) val transformResult = transformer.transform(fullProtocol, IdlName.Mu) @@ -40,7 +41,7 @@ class SkeuomorphProtocolTransformerSpec extends Specification { "Transform a simple Protobuf Schema to Mu" >> { val protocolMetadata = - ProtocolMetadata(ProtocolId("id"), IdlName.Protobuf, ProtocolVersion(1)) + ProtocolMetadata(ProtocolId("id"), IdlName.Protobuf, protocolVersion) val fullProtocol = FullProtocol(protocolMetadata, Protocol(simpleProtobufExample)) val transformResult = transformer.transform(fullProtocol, IdlName.Mu) From e9b199800881a298a5816ea9ff974724e6dbe0b7 Mon Sep 17 00:00:00 2001 From: yago Date: Fri, 22 May 2020 19:57:39 +0200 Subject: [PATCH 2/2] format --- .../compendium/metadata/pg/PgMetadataStorageSpec.scala | 2 +- .../higherkindness/compendium/storage/pg/PgStorageSpec.scala | 2 +- .../compendium/storage/pg/StorageQueriesSpec.scala | 4 ++-- .../transformer/SkeuomorphProtocolTransformerSpec.scala | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/test/scala/higherkindness/compendium/metadata/pg/PgMetadataStorageSpec.scala b/src/test/scala/higherkindness/compendium/metadata/pg/PgMetadataStorageSpec.scala index 708ed64..7299911 100644 --- a/src/test/scala/higherkindness/compendium/metadata/pg/PgMetadataStorageSpec.scala +++ b/src/test/scala/higherkindness/compendium/metadata/pg/PgMetadataStorageSpec.scala @@ -25,7 +25,7 @@ import higherkindness.compendium.models._ class PgMetadataStorageSpec extends PGHelper(Metadata) { - private lazy val pg = PgMetadataStorage[IO](transactor) + private lazy val pg = PgMetadataStorage[IO](transactor) private val protocolVersion = ProtocolVersion.initial "Postgres Service" should { diff --git a/src/test/scala/higherkindness/compendium/storage/pg/PgStorageSpec.scala b/src/test/scala/higherkindness/compendium/storage/pg/PgStorageSpec.scala index 544ef3d..0dface0 100644 --- a/src/test/scala/higherkindness/compendium/storage/pg/PgStorageSpec.scala +++ b/src/test/scala/higherkindness/compendium/storage/pg/PgStorageSpec.scala @@ -25,7 +25,7 @@ import higherkindness.compendium.models._ class PgStorageSpec extends PGHelper(Data) { - private lazy val pgStorage = PgStorage[IO](transactor) + private lazy val pgStorage = PgStorage[IO](transactor) private val protocolVersion = ProtocolVersion.initial "Postgres Storage" should { diff --git a/src/test/scala/higherkindness/compendium/storage/pg/StorageQueriesSpec.scala b/src/test/scala/higherkindness/compendium/storage/pg/StorageQueriesSpec.scala index 3a57c4d..904a0b7 100644 --- a/src/test/scala/higherkindness/compendium/storage/pg/StorageQueriesSpec.scala +++ b/src/test/scala/higherkindness/compendium/storage/pg/StorageQueriesSpec.scala @@ -35,8 +35,8 @@ class StorageQueriesSpec extends PGHelper(Data) with IOChecker { trait context extends Scope { val protocolId = ProtocolId("my.test.protocol.id") - val version = ProtocolVersion.initial - val protocol = Protocol("Raw protocol content") + val version = ProtocolVersion.initial + val protocol = Protocol("Raw protocol content") } } diff --git a/src/test/scala/higherkindness/compendium/transformer/SkeuomorphProtocolTransformerSpec.scala b/src/test/scala/higherkindness/compendium/transformer/SkeuomorphProtocolTransformerSpec.scala index cb58941..b53cfa6 100644 --- a/src/test/scala/higherkindness/compendium/transformer/SkeuomorphProtocolTransformerSpec.scala +++ b/src/test/scala/higherkindness/compendium/transformer/SkeuomorphProtocolTransformerSpec.scala @@ -26,7 +26,7 @@ class SkeuomorphProtocolTransformerSpec extends Specification { import protocols._ - val transformer = SkeuomorphProtocolTransformer[IO] + val transformer = SkeuomorphProtocolTransformer[IO] val protocolVersion = ProtocolVersion.initial "Skeuomorph based protocol transformer" should {