Skip to content
This repository has been archived by the owner on Nov 30, 2022. It is now read-only.

[WIP] - New Versioning #284

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
version = "2.5.2"
style = defaultWithAlign
maxColumn = 100
maxColumn = 120

continuationIndent.callSite = 2

1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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+)$'),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did this here because of the tests, but I guess the correct way would be to create a migration?

protocol BYTEA,
PRIMARY KEY (id, version)
);
Original file line number Diff line number Diff line change
@@ -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+)$')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above

);
4 changes: 1 addition & 3 deletions src/main/scala/higherkindness/compendium/Main.scala
Original file line number Diff line number Diff line change
@@ -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](_)))

Original file line number Diff line number Diff line change
@@ -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

Original file line number Diff line number Diff line change
@@ -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))
}
17 changes: 9 additions & 8 deletions src/main/scala/higherkindness/compendium/core/refinements.scala
Original file line number Diff line number Diff line change
@@ -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] {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we don't need this anymore... I have to give it a deeper look


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
}
}
17 changes: 7 additions & 10 deletions src/main/scala/higherkindness/compendium/http/QueryParams.scala
Original file line number Diff line number Diff line change
@@ -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")
}
Original file line number Diff line number Diff line change
@@ -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) =>
Original file line number Diff line number Diff line change
@@ -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 {
Original file line number Diff line number Diff line change
@@ -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)
}
}
15 changes: 10 additions & 5 deletions src/main/scala/higherkindness/compendium/metadata/pg/Queries.scala
Original file line number Diff line number Diff line change
@@ -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}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Revision is updated by default, but this can be changed

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 = ?"
)
}
Original file line number Diff line number Diff line change
@@ -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)
195 changes: 195 additions & 0 deletions src/main/scala/higherkindness/compendium/models/ProtocolVersion.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/*
* Copyright 2018-2020 47 Degrees, LLC. <http://www.47deg.com>
*
* 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)
}
}
61 changes: 61 additions & 0 deletions src/main/scala/higherkindness/compendium/models/Tagged.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright 2018-2020 47 Degrees, LLC. <http://www.47deg.com>
*
* 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)
}
}
3 changes: 1 addition & 2 deletions src/main/scala/higherkindness/compendium/models/errors.scala
Original file line number Diff line number Diff line change
@@ -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)
47 changes: 47 additions & 0 deletions src/main/scala/higherkindness/compendium/models/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright 2018-2020 47 Degrees, LLC. <http://www.47deg.com>
*
* 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)
}
}

}
Original file line number Diff line number Diff line change
@@ -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]
Original file line number Diff line number Diff line change
@@ -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] {
Original file line number Diff line number Diff line change
@@ -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] {
Original file line number Diff line number Diff line change
@@ -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 {

Original file line number Diff line number Diff line change
@@ -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))
33 changes: 30 additions & 3 deletions src/test/scala/higherkindness/compendium/CompendiumArbitrary.scala
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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,
Original file line number Diff line number Diff line change
@@ -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" >> {
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2018-2020 47 Degrees, LLC. <http://www.47deg.com>
*
* 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]
}
}
}
62 changes: 32 additions & 30 deletions src/test/scala/higherkindness/compendium/http/RootServiceSpec.scala
Original file line number Diff line number Diff line change
@@ -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" >> {
Original file line number Diff line number Diff line change
@@ -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))
}
Original file line number Diff line number Diff line change
@@ -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
}

}
Original file line number Diff line number Diff line change
@@ -18,24 +18,25 @@ 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 {
val id: ProtocolId = ProtocolId("pId")
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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright 2018-2020 47 Degrees, LLC. <http://www.47deg.com>
*
* 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]
}
}

}
Original file line number Diff line number Diff line change
@@ -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] =
Original file line number Diff line number Diff line change
@@ -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) =>
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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")
}

}
Original file line number Diff line number Diff line change
@@ -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)