Skip to content

Commit

Permalink
generalise links so they can be contain a list of links
Browse files Browse the repository at this point in the history
  • Loading branch information
jbwheatley authored and jbwheatley committed Oct 27, 2021
1 parent f2bef3c commit e736cde
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 299 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package com.itv.scalapact.argonaut62

import java.time.format.DateTimeFormatter

import argonaut.Argonaut._
import argonaut.{Parse, _}
import argonaut._
import com.itv.scalapact.shared.Notice._
import com.itv.scalapact.shared._

import java.time.format.DateTimeFormatter
import scala.util.{Failure, Success, Try}

object PactImplicits {
Expand All @@ -18,12 +17,40 @@ object PactImplicits {
"templated"
)

implicit lazy val linkDecodeJson: DecodeJson[Link] = {
implicit val linkValues: DecodeJson[LinkValues] = jdecode4L(LinkValues.apply)(
"title",
"name",
"href",
"templated"
)
val linkList: DecodeJson[LinkList] = DecodeJson.ListDecodeJson[LinkValues].map(LinkList)
linkValues.widen[Link]() ||| linkList.widen[Link]()
}

implicit lazy val linkEncodeJson: EncodeJson[Link] = {
implicit val linkValues: EncodeJson[LinkValues] = jencode4L { (l: LinkValues) =>
(l.title, l.name, l.href, l.templated)
}(
"title",
"name",
"href",
"templated"
)
val linkList: EncodeJson[LinkList] = EncodeJson.ListEncodeJson[LinkValues].contramap(_.links)
EncodeJson {
case l: LinkValues =>
linkValues.encode(l)
case l: LinkList => linkList.encode(l)
}
}

implicit lazy val scalaPactDecodeJson: DecodeJson[Pact] = DecodeJson[Pact] { cur =>
for {
provider <- cur.get[PactActor]("provider")
consumer <- cur.get[PactActor]("consumer")
interactions <- cur.get[List[Interaction]]("interactions")
_links <- cur.downField("_links").downField("curies").delete.as[Option[Links]]
_links <- cur.downField("_links").as[Option[Links]]
metadata <- cur.get[Option[PactMetaData]]("metadata")
} yield Pact(provider, consumer, interactions, _links, metadata)
}
Expand All @@ -49,7 +76,7 @@ object PactImplicits {
implicit val jvmPactEncoder: EncodeJson[JvmPact] = EncodeJson { jvmPact =>
Parse.parse(jvmPact.rawContents) match {
case Right(value) => value
case Left(error) => throw new Exception(s"Generated pact is not valid json: ${error}")
case Left(error) => throw new Exception(s"Generated pact is not valid json: $error")
}
}

Expand Down Expand Up @@ -147,7 +174,7 @@ object PactImplicits {
}

implicit lazy val halIndexDecoder: DecodeJson[HALIndex] =
DecodeJson[HALIndex](_.downField("_links").downField("curies").delete.as[Links].map(HALIndex))
DecodeJson[HALIndex](_.downField("_links").as[Links].map(HALIndex))

implicit val pendingStateNoticeDecoder: DecodeJson[Notice] = DecodeJson[Notice] { cur =>
lazy val pattern = "after_verification:success_(true|false)_published_(true|false)".r
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package com.itv.scalapact.circe13

import com.itv.scalapact.shared.Notice._
import com.itv.scalapact.shared._
import io.circe.{ACursor, Codec, Decoder, DecodingFailure, Encoder, HCursor, Json, parser}
import io.circe.generic.semiauto.{deriveCodec, deriveDecoder, deriveEncoder}
import io.circe.syntax._
import io.circe._
import cats.syntax.functor._

import scala.util.{Failure, Success, Try}

Expand Down Expand Up @@ -75,24 +76,31 @@ object PactImplicits {

implicit val interactionEncoder: Encoder[Interaction] = deriveEncoder

implicit val linkValueDecoder: Codec[LinkValues] = deriveCodec
implicit val linkDecoder: Decoder[Link] = {
implicit val linkValues: Decoder[LinkValues] = deriveDecoder[LinkValues]
val linkList: Decoder[LinkList] = Decoder.decodeList[LinkValues].map(LinkList)
linkValues.widen[Link].or(linkList.widen[Link])
}

implicit val linkEncoder: Encoder[Link] = {
implicit val linkValues: Encoder[LinkValues] = deriveEncoder[LinkValues]
val linkList: Encoder[LinkList] = Encoder.encodeList[LinkValues].contramap(_.links)
Encoder.instance {
case l: LinkValues => linkValues(l)
case l: LinkList => linkList(l)
}
}

implicit val versionMetaDataDecoder: Codec[VersionMetaData] = deriveCodec

implicit val pactMetaDataDecoder: Codec[PactMetaData] = deriveCodec

private def sanitizeLinks(cursor: HCursor): ACursor = {
val links: ACursor = cursor.downField("_links").downField("curies").delete
if (links.keys.exists(_.toList.contains("pb:consumer-versions"))) links.downField("pb:consumer-versions").delete
else links
}

implicit val scalaPactDecoder: Decoder[Pact] = Decoder.instance { cur =>
for {
provider <- cur.get[PactActor]("provider")
consumer <- cur.get[PactActor]("consumer")
interactions <- cur.get[List[Interaction]]("interactions")
_links <- sanitizeLinks(cur).as[Option[Links]]
_links <- cur.downField("_links").as[Option[Links]]
metadata <- cur.get[Option[PactMetaData]]("metadata")
} yield Pact(provider, consumer, interactions, _links, metadata)
}
Expand All @@ -110,12 +118,12 @@ object PactImplicits {
implicit val jvmPactEncoder: Encoder[JvmPact] = Encoder.instance { jvmPact =>
io.circe.parser.parse(jvmPact.rawContents) match {
case Right(value) => value
case Left(error) => throw new Exception(s"Generated pact is not valid json: ${error}")
case Left(error) => throw new Exception(s"Generated pact is not valid json: $error")
}
}

implicit val halIndexDecoder: Decoder[HALIndex] = Decoder.instance { cur =>
sanitizeLinks(cur).as[Links].map(HALIndex)
cur.downField("_links").as[Links].map(HALIndex.apply)
}

implicit val embeddedPactsForVerificationDecoder: Decoder[EmbeddedPactsForVerification] = deriveDecoder
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,137 +115,6 @@ class ScalaPactReaderWriterSpec extends AnyFunSpec with Matchers with OptionValu
pactEither.toOption.value shouldEqual PactFileExamples.simpleWithLinksAndMetaData
}

it("should remove curies and pb:consumer-versions from parsed _links") {
val simpleWithCuriesAndPbConsumerVersionsAsString: String =
"""{
| "provider" : {
| "name" : "provider"
| },
| "consumer" : {
| "name" : "consumer"
| },
| "interactions" : [
| {
| "request" : {
| "method" : "GET",
| "body" : "fish",
| "path" : "/fetch-json",
| "matchingRules" : {
| "$.headers.Accept" : {
| "match" : "regex",
| "regex" : "\\w+"
| },
| "$.headers.Content-Length" : {
| "match" : "type"
| }
| },
| "query" : "fish=chips",
| "headers" : {
| "Content-Type" : "text/plain"
| }
| },
| "description" : "a simple request",
| "response" : {
| "status" : 200,
| "headers" : {
| "Content-Type" : "application/json"
| },
| "body" : {
| "fish" : [
| "cod",
| "haddock",
| "flying"
| ]
| },
| "matchingRules" : {
| "$.headers.Accept" : {
| "match" : "regex",
| "regex" : "\\w+"
| },
| "$.headers.Content-Length" : {
| "match" : "type"
| }
| }
| },
| "providerState" : "a simple state"
| },
| {
| "request" : {
| "method" : "GET",
| "body" : "fish",
| "path" : "/fetch-json2",
| "headers" : {
| "Content-Type" : "text/plain"
| }
| },
| "description" : "a simple request 2",
| "response" : {
| "status" : 200,
| "headers" : {
| "Content-Type" : "application/json"
| },
| "body" : {
| "chips" : true,
| "fish" : [
| "cod",
| "haddock"
| ]
| }
| },
| "providerState" : "a simple state 2"
| }
| ],
| "_links": {
| "self": {
| "title": "Pact",
| "name": "Pact between consumer (v1.0.0) and provider",
| "href": "http://localhost/pacts/provider/provider/consumer/consumer/version/1.0.0"
| },
| "pb:consumer": {
| "title": "Consumer",
| "name": "consumer",
| "href": "http://localhost/pacticipants/consumer"
| },
| "pb:provider": {
| "title": "Provider",
| "name": "provider",
| "href": "http://localhost/pacticipants/provider"
| },
| "pb:latest-tagged-pact-version": {
| "title": "Latest tagged version of this pact",
| "href": "http://localhost/pacts/provider/provider-service/consumer/consumer-service/latest/{tag}",
| "templated": true
| },
| "pb:consumer-versions": [
| {
| "title": "Consumer version",
| "name": "1.2.3",
| "href": "http://localhost/pacticipants/consumer/versions/1.2.3"
| }
| ],
| "curies": [
| {
| "name": "pb",
| "href": "http://localhost/doc/{rel}",
| "templated": true
| }
| ]
| },
| "metadata": {
| "pactSpecification": {
| "version": "2.0.0"
| },
| "scala-pact": {
| "version": "1.0.0"
| }
| }
|}""".stripMargin

val pactEither = pactReader.jsonStringToScalaPact(simpleWithCuriesAndPbConsumerVersionsAsString)

pactEither.toOption.value shouldEqual PactFileExamples.simpleWithLinksAndMetaData
}

it("should be able to write Pact files and add metadata when missing") {

val written = pactWriter.pactToJsonString(PactFileExamples.simple, scalaPactVersion)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
package com.itv.scalapact.circe14

import cats.syntax.functor._
import com.itv.scalapact.shared.Notice._
import com.itv.scalapact.shared._
import io.circe.{ACursor, Codec, Decoder, DecodingFailure, Encoder, HCursor, Json, parser}
import io.circe.generic.semiauto.{deriveCodec, deriveDecoder, deriveEncoder}
import io.circe.syntax._
import io.circe._

import scala.util.{Failure, Success, Try}

Expand Down Expand Up @@ -75,25 +76,32 @@ object PactImplicits {

implicit val interactionEncoder: Encoder[Interaction] = deriveEncoder

implicit val linkValueDecoder: Codec[LinkValues] = deriveCodec
implicit val linkDecoder: Decoder[Link] = {
implicit val linkValues: Decoder[LinkValues] = deriveDecoder[LinkValues]
val linkList: Decoder[LinkList] = Decoder.decodeList[LinkValues].map(LinkList)
linkValues.widen[Link].or(linkList.widen[Link])
}

implicit val linkEncoder: Encoder[Link] = {
implicit val linkValues: Encoder[LinkValues] = deriveEncoder[LinkValues]
val linkList: Encoder[LinkList] = Encoder.encodeList[LinkValues].contramap(_.links)
Encoder.instance {
case l: LinkValues => linkValues(l)
case l: LinkList => linkList(l)
}
}

implicit val versionMetaDataDecoder: Codec[VersionMetaData] =
Codec.forProduct1("version")(VersionMetaData.apply)(_.version)

implicit val pactMetaDataDecoder: Codec[PactMetaData] = deriveCodec

private def sanitizeLinks(cursor: HCursor): ACursor = {
val links: ACursor = cursor.downField("_links").downField("curies").delete
if (links.keys.exists(_.toList.contains("pb:consumer-versions"))) links.downField("pb:consumer-versions").delete
else links
}

implicit val scalaPactDecoder: Decoder[Pact] = Decoder.instance { cur =>
for {
provider <- cur.get[PactActor]("provider")
consumer <- cur.get[PactActor]("consumer")
interactions <- cur.get[List[Interaction]]("interactions")
_links <- sanitizeLinks(cur).as[Option[Links]]
_links <- cur.downField("_links").as[Option[Links]]
metadata <- cur.get[Option[PactMetaData]]("metadata")
} yield Pact(provider, consumer, interactions, _links, metadata)
}
Expand All @@ -111,12 +119,12 @@ object PactImplicits {
implicit val jvmPactEncoder: Encoder[JvmPact] = Encoder.instance { jvmPact =>
io.circe.parser.parse(jvmPact.rawContents) match {
case Right(value) => value
case Left(error) => throw new Exception(s"Generated pact is not valid json: ${error}")
case Left(error) => throw new Exception(s"Generated pact is not valid json: $error")
}
}

implicit val halIndexDecoder: Decoder[HALIndex] = Decoder.instance { cur =>
sanitizeLinks(cur).as[Links].map(HALIndex.apply)
cur.downField("_links").as[Links].map(HALIndex.apply)
}

implicit val embeddedPactForVerificationDecoder: Decoder[PactForVerification] = deriveDecoder
Expand Down
Loading

0 comments on commit e736cde

Please sign in to comment.