Skip to content

Issue #64 merge master #85

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 27 commits into from
Feb 8, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2af8372
Update sbt, scoverage, coveralls, circe, akka, scalatest versions to …
jmhofer Aug 5, 2016
0410c2b
Add `coverageReport` target to the Travis `after_success` step as des…
jmhofer Aug 5, 2016
076b2f7
Merge pull request #68 from jmhofer/issue/67
zmeda Aug 5, 2016
429102d
implicit conversions for attributes and values
Sep 8, 2016
65a489c
Merge pull request #71 from RavelLaw/issue/70
zmeda Sep 16, 2016
0f4c231
add ensime files to gitignore
Sep 16, 2016
f22c681
implicit conversions for all jsonapiobject values
Sep 16, 2016
8185900
Merge pull request #73 from RavelLaw/issue/72
zmeda Sep 17, 2016
d2333b9
Bumps the version and updates the contributing guidelines.
Sep 24, 2016
b43a751
get on actual release of circe
Sep 24, 2016
b83421b
v0.5.4
Sep 25, 2016
6c9316f
Merge pull request #75 from emanresusername/issue/74
zmeda Sep 25, 2016
9d06f22
add maven central version badge to readme
Sep 28, 2016
1709824
Merge pull request #77 from emanresusername/issue/76
zmeda Sep 28, 2016
a712f4f
circe 0.6.0
Nov 11, 2016
88d1be7
Merge pull request #79 from RavelLaw/issue/78
zmeda Nov 15, 2016
115fc50
update readme for 0.6.0
Nov 16, 2016
d793788
Merge pull request #80 from RavelLaw/master
zmeda Nov 16, 2016
f905eda
Adding Object Links.
palhash Nov 23, 2016
07936bc
Modifying function names.
palhash Nov 23, 2016
56a0663
Adding more code coverage and removing redundant case classes.
palhash Nov 30, 2016
41f13a5
Updating version and readme.
palhash Dec 5, 2016
5e588a0
Merge pull request #81 from prahim/master
zmeda Dec 7, 2016
d2148a2
circe 0.7.0
Jan 17, 2017
fc98b3c
Merge pull request #83 from emanresusername/issue/82/circe-0.7.0
Jan 30, 2017
5e09d47
Merge remote-tracking branch 'origin/master' into issue/64-merge-curr…
oporkka Feb 2, 2017
2bd6f42
Merge branch 'issue/64' into issue/64-merge-current-master
oporkka Feb 3, 2017
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
target/
.idea/
.ensime*
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ notifications:
on_start: never
script: "sbt clean coverage test"
after_success:
- "sbt coveralls"
- "sbt coverageReport coveralls"
- bash <(curl -s https://codecov.io/bash)
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ We are happy to accept your contributions to make scala-jsonapi better and more

Please do not push to the master branch directly. Always use pull requests and let people discuss changes in the pull request.
Pull requests should only be merged after all discussions have been concluded and at least one reviewer has offered **approval**.
If you expect new version to be released please update `version.sbt` and `README.md` accordingly in the same pull request as changes were made.

## Guidelines

Expand Down
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
[![Coverage Status](https://coveralls.io/repos/zalando/scala-jsonapi/badge.svg?branch=master&service=github)](https://coveralls.io/github/zalando/scala-jsonapi?branch=master)
[![codecov.io](https://codecov.io/github/zalando/scala-jsonapi/coverage.svg?branch=master)](https://codecov.io/github/zalando/scala-jsonapi?branch=master)
[![Join the chat at https://gitter.im/zalando/scala-jsonapi](https://badges.gitter.im/zalando/scala-jsonapi.svg)](https://gitter.im/zalando/scala-jsonapi?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.zalando/scala-jsonapi_2.11/badge.svg)](https://maven-badges.herokuapp.com/maven-central/org.zalando/scala-jsonapi_2.11)

scala-jsonapi is a Scala library that aims to help you produce JSON output based on the [JSON API specification][jsonapi] easily and painlessly. The library is compatible with Scala version `2.11`. It supports read and write for the following backends:

Expand All @@ -23,7 +24,7 @@ This library is very much a work in progress, so expect its API to change.

To use scala-jsonapi, first add a library dependency—assuming that you have [sonatype resolvers] set up.

libraryDependencies += "org.zalando" %% "scala-jsonapi" % "0.5.2"
libraryDependencies += "org.zalando" %% "scala-jsonapi" % "0.6.2"

You also have to provide the used backend (e.g. spray-json).

Expand Down Expand Up @@ -101,6 +102,18 @@ In contrast there is a type class called `JsonapiRootObjectReader` that supports

For complete usage, see [the specs example].

## JSON API Links Support

There is support for string and object links.

To create a string "self" link:

Links.self("href", None)

To create an object "self" link:

Links.self("href", Some(meta))

# Publishing and Releasing

Publishing and releasing is made with help of the [sbt-sonatype plugin].
Expand Down
4 changes: 2 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,9 @@ lazy val `akka-http` = project.in(file("akka-http")).
settings(commonSettings: _*).
settings(libraryDependencies ++= akkaHttpDeps)

ScoverageSbtPlugin.ScoverageKeys.coverageMinimum := 80
coverageMinimum := 80

ScoverageSbtPlugin.ScoverageKeys.coverageFailOnMinimum := true
coverageFailOnMinimum := true

publishMavenStyle := true

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.zalando.jsonapi.circe

import cats.data.Xor
import io.circe._
import org.zalando.jsonapi.json.FieldNames
import org.zalando.jsonapi.model.JsonApiObject._
Expand All @@ -20,51 +19,95 @@ trait CirceJsonapiDecoders {
}.toList)
)

implicit val valueDecoder = Decoder.instance[Value](_.as[Json].map(jsonToValue))
implicit val valueDecoder = Decoder.instance[Value](_.as[Json].right.map(jsonToValue))

implicit val attributesDecoder = Decoder.instance[Attributes](hcursor ⇒ {
hcursor.as[Value].flatMap {
hcursor.as[Value].right.flatMap {
case JsObjectValue(value) ⇒
Xor.Right(value)
Right(value)
case _ ⇒
Xor.Left(DecodingFailure("only an object can be decoded to Attributes", hcursor.history))
Left(DecodingFailure("only an object can be decoded to Attributes", hcursor.history))
}
})

implicit val attributeDecoder = Decoder.instance[Attribute](_.as[Attributes].map(_.head))
implicit val attributeDecoder = Decoder.instance[Attribute](_.as[Attributes].right.map(_.head))

implicit val linksDecoder = Decoder.instance[Links](hcursor ⇒ {
hcursor.as[Value].flatMap {
hcursor.as[Value].right.flatMap {
case JsObjectValue(attributes) ⇒
Xor.Right(attributes.map {
case Attribute(FieldNames.`self`, StringValue(url)) ⇒ Links.Self(url)
Right(attributes.map {
case Attribute(FieldNames.`self`, StringValue(url)) ⇒ Links.Self(url, None)
case Attribute(FieldNames.`self`, JsObjectValue(linkAttributes)) =>
val linkValues = attributesToLinkValues(linkAttributes)
Links.Self(linkValues._1, linkValues._2)

case Attribute(FieldNames.`about`, StringValue(url)) ⇒
Links.About(url)
Links.About(url, None)
case Attribute(FieldNames.`about`, JsObjectValue(linkAttributes)) =>
val linkValues = attributesToLinkValues(linkAttributes)
Links.About(linkValues._1, linkValues._2)

case Attribute(FieldNames.`first`, StringValue(url)) ⇒
Links.First(url)
case Attribute(FieldNames.`last`, StringValue(url)) ⇒ Links.Last(url)
case Attribute(FieldNames.`next`, StringValue(url)) ⇒ Links.Next(url)
case Attribute(FieldNames.`prev`, StringValue(url)) ⇒ Links.Prev(url)
Links.First(url, None)
case Attribute(FieldNames.`first`, JsObjectValue(linkAttributes)) =>
val linkValues = attributesToLinkValues(linkAttributes)
Links.First(linkValues._1, linkValues._2)

case Attribute(FieldNames.`last`, StringValue(url)) ⇒ Links.Last(url, None)
case Attribute(FieldNames.`last`, JsObjectValue(linkAttributes)) =>
val linkValues = attributesToLinkValues(linkAttributes)
Links.Last(linkValues._1, linkValues._2)

case Attribute(FieldNames.`next`, StringValue(url)) ⇒ Links.Next(url, None)
case Attribute(FieldNames.`next`, JsObjectValue(linkAttributes)) =>
val linkValues = attributesToLinkValues(linkAttributes)
Links.Next(linkValues._1, linkValues._2)

case Attribute(FieldNames.`prev`, StringValue(url)) ⇒ Links.Prev(url, None)
case Attribute(FieldNames.`prev`, JsObjectValue(linkAttributes)) =>
val linkValues = attributesToLinkValues(linkAttributes)
Links.Prev(linkValues._1, linkValues._2)

case Attribute(FieldNames.`related`, StringValue(url)) ⇒
Links.Related(url)
Links.Related(url, None)
case Attribute(FieldNames.`related`, JsObjectValue(linkAttributes)) =>
val linkValues = attributesToLinkValues(linkAttributes)
Links.Related(linkValues._1, linkValues._2)
})
case _ ⇒
Xor.Left(DecodingFailure("only an object can be decoded to Links", hcursor.history))
Left(DecodingFailure("only an object can be decoded to Links", hcursor.history))
}
})

def jsonToData(json: Json): Xor[DecodingFailure, Data] = json match {
def attributesToLinkValues(linkObjectAttributes: Attributes): (String, Option[Meta]) = {
(linkObjectAttributes.find(_.name == "href"), linkObjectAttributes.find(_.name == "meta")) match {
case (Some(hrefAttribute), Some(metaAttribute)) =>
val href = hrefAttribute match {
case Attribute("href", StringValue(url)) => url
}
val meta: Map[String, JsonApiObject.Value] = metaAttribute match {
case Attribute("meta", JsObjectValue(metaAttributes)) =>
metaAttributes.map {
case Attribute(name, value) ⇒ name -> value
}.toMap
}
(href, Some(meta))
}
}

def jsonToData(json: Json): Either[DecodingFailure, Data] = json match {
case json: Json if json.isArray ⇒
json.as[ResourceObjects]
case json: Json if json.isObject ⇒
json.as[ResourceObject]
}

implicit val dataDecoder = Decoder.instance[Data](_.as[Json].right.flatMap(jsonToData))

implicit val relationshipDecoder = Decoder.instance[Relationship](hcursor ⇒ {
for {
links ← hcursor.downField(FieldNames.`links`).as[Option[Links]]
// TODO: there's prolly a cleaner way here. there's a circular dependency Data -> ResourceObject(s) -> Relationship(s) -> Data that's giving circe problems
data ← hcursor.downField(FieldNames.`data`).as[Option[Json]].map(_.flatMap(jsonToData(_).toOption))
links ← hcursor.downField(FieldNames.`links`).as[Option[Links]].right
data ← hcursor.downField(FieldNames.`data`).as[Option[Data]].right
} yield
Relationship(
links = links,
Expand All @@ -75,31 +118,31 @@ trait CirceJsonapiDecoders {
implicit val relationshipsDecoder = Decoder.instance[Relationships](_.as[Map[String, Relationship]])

implicit val jsonApiDecoder = Decoder.instance[JsonApi](hcursor ⇒ {
hcursor.as[Value].flatMap {
hcursor.as[Value].right.flatMap {
case JsObjectValue(attributes) ⇒
Xor.Right(attributes.map {
Right(attributes.map {
case Attribute(name, value) ⇒ JsonApiProperty(name, value)
})
case _ ⇒
Xor.Left(DecodingFailure("only an object can be decoded to JsonApi", hcursor.history))
Left(DecodingFailure("only an object can be decoded to JsonApi", hcursor.history))
}
})

implicit val metaDecoder = Decoder.instance[Meta](hcursor ⇒ {
hcursor.as[Value].flatMap {
hcursor.as[Value].right.flatMap {
case JsObjectValue(attributes) ⇒
Xor.Right(attributes.map {
Right(attributes.map {
case Attribute(name, value) ⇒ name -> value
}.toMap)
case _ ⇒
Xor.Left(DecodingFailure("only an object can be decoded to Meta", hcursor.history))
Left(DecodingFailure("only an object can be decoded to Meta", hcursor.history))
}
})

implicit val errorSourceDecoder = Decoder.instance[ErrorSource](hcursor ⇒ {
for {
pointer ← hcursor.downField(FieldNames.`pointer`).as[Option[String]]
parameter ← hcursor.downField(FieldNames.`parameter`).as[Option[String]]
pointer ← hcursor.downField(FieldNames.`pointer`).as[Option[String]].right
parameter ← hcursor.downField(FieldNames.`parameter`).as[Option[String]].right
} yield
ErrorSource(
pointer = pointer,
Expand All @@ -109,14 +152,14 @@ trait CirceJsonapiDecoders {

implicit val errorDecoder = Decoder.instance[Error](hcursor ⇒ {
for {
id ← hcursor.downField(FieldNames.`id`).as[Option[String]]
status ← hcursor.downField(FieldNames.`status`).as[Option[String]]
code ← hcursor.downField(FieldNames.`code`).as[Option[String]]
title ← hcursor.downField(FieldNames.`title`).as[Option[String]]
detail ← hcursor.downField(FieldNames.`detail`).as[Option[String]]
links ← hcursor.downField(FieldNames.`links`).as[Option[Links]]
meta ← hcursor.downField(FieldNames.`meta`).as[Option[Meta]]
source ← hcursor.downField(FieldNames.`source`).as[Option[ErrorSource]]
id ← hcursor.downField(FieldNames.`id`).as[Option[String]].right
status ← hcursor.downField(FieldNames.`status`).as[Option[String]].right
code ← hcursor.downField(FieldNames.`code`).as[Option[String]].right
title ← hcursor.downField(FieldNames.`title`).as[Option[String]].right
detail ← hcursor.downField(FieldNames.`detail`).as[Option[String]].right
links ← hcursor.downField(FieldNames.`links`).as[Option[Links]].right
meta ← hcursor.downField(FieldNames.`meta`).as[Option[Meta]].right
source ← hcursor.downField(FieldNames.`source`).as[Option[ErrorSource]].right
} yield
Error(
id = id,
Expand All @@ -132,12 +175,12 @@ trait CirceJsonapiDecoders {

implicit val resourceObjectDecoder = Decoder.instance[ResourceObject](hcursor ⇒ {
for {
id ← hcursor.downField(FieldNames.`id`).as[Option[String]]
`type` ← hcursor.downField(FieldNames.`type`).as[String]
attributes ← hcursor.downField(FieldNames.`attributes`).as[Option[Attributes]]
relationships ← hcursor.downField(FieldNames.`relationships`).as[Option[Relationships]]
links ← hcursor.downField(FieldNames.`links`).as[Option[Links]]
meta ← hcursor.downField(FieldNames.`meta`).as[Option[Meta]]
id ← hcursor.downField(FieldNames.`id`).as[Option[String]].right
`type` ← hcursor.downField(FieldNames.`type`).as[String].right
attributes ← hcursor.downField(FieldNames.`attributes`).as[Option[Attributes]].right
relationships ← hcursor.downField(FieldNames.`relationships`).as[Option[Relationships]].right
links ← hcursor.downField(FieldNames.`links`).as[Option[Links]].right
meta ← hcursor.downField(FieldNames.`meta`).as[Option[Meta]].right
} yield
ResourceObject(
id = id,
Expand All @@ -150,20 +193,18 @@ trait CirceJsonapiDecoders {
})

implicit val resourceObjectsDecoder =
Decoder.instance[ResourceObjects](_.as[List[ResourceObject]].map(ResourceObjects))

implicit val dataDecoder = Decoder.instance[Data](_.as[Json].flatMap(jsonToData))
Decoder.instance[ResourceObjects](_.as[List[ResourceObject]].right.map(ResourceObjects))

implicit val includedDecoder = Decoder.instance[Included](_.as[ResourceObjects].map(Included.apply))
implicit val includedDecoder = Decoder.instance[Included](_.as[ResourceObjects].right.map(Included.apply))

implicit val rootObjectDecoder = Decoder.instance[RootObject](hcursor ⇒ {
for {
data ← hcursor.downField(FieldNames.`data`).as[Option[Data]]
links ← hcursor.downField(FieldNames.`links`).as[Option[Links]]
errors ← hcursor.downField(FieldNames.`errors`).as[Option[Errors]]
meta ← hcursor.downField(FieldNames.`meta`).as[Option[Meta]]
included ← hcursor.downField(FieldNames.`included`).as[Option[Included]]
jsonapi ← hcursor.downField(FieldNames.`jsonapi`).as[Option[JsonApi]]
data ← hcursor.downField(FieldNames.`data`).as[Option[Data]].right
links ← hcursor.downField(FieldNames.`links`).as[Option[Links]].right
errors ← hcursor.downField(FieldNames.`errors`).as[Option[Errors]].right
meta ← hcursor.downField(FieldNames.`meta`).as[Option[Meta]].right
included ← hcursor.downField(FieldNames.`included`).as[Option[Included]].right
jsonapi ← hcursor.downField(FieldNames.`jsonapi`).as[Option[JsonApi]].right
} yield
RootObject(
data = data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,34 @@ trait CirceJsonapiEncoders {
}

implicit val linkEncoder = Encoder.instance[Link] { link =>
val (name: String, value: String) = link match {
case Links.Self(url) => FieldNames.`self` -> url
case Links.About(url) => FieldNames.`about` -> url
case Links.First(url) => FieldNames.`first` -> url
case Links.Last(url) => FieldNames.`last` -> url
case Links.Next(url) => FieldNames.`next` -> url
case Links.Prev(url) => FieldNames.`prev` -> url
case Links.Related(url) => FieldNames.`related` -> url
val (name: String, href: String, metaOpt: Option[Meta]) = link match {
case Links.Self(url, None) => (FieldNames.`self`, url, None)
case Links.Self(url, Some(meta)) => (FieldNames.`self`, url, Some(meta))

case Links.About(url, None) => (FieldNames.`about`, url, None)
case Links.About(url, Some(meta)) => (FieldNames.`about`, url, Some(meta))

case Links.First(url, None) => (FieldNames.`first`, url, None)
case Links.First(url, Some(meta)) => (FieldNames.`first`, url, Some(meta))

case Links.Last(url, None) => (FieldNames.`last`, url, None)
case Links.Last(url, Some(meta)) => (FieldNames.`last`, url, Some(meta))

case Links.Next(url, None) => (FieldNames.`next`, url, None)
case Links.Next(url, Some(meta)) => (FieldNames.`next`, url, Some(meta))

case Links.Prev(url, None) => (FieldNames.`prev`, url, None)
case Links.Prev(url, Some(meta)) => (FieldNames.`prev`, url, Some(meta))

case Links.Related(url, None) => (FieldNames.`related`, url, None)
case Links.Related(url, Some(meta)) => (FieldNames.`related`, url, Some(meta))
}
metaOpt match {
case None => Json.fromFields(Seq(name -> Json.fromString(href)))
case Some(meta) =>
val linkObjectJson = Json.fromFields(Seq("href" -> Json.fromString(href), "meta" -> meta.asJson))
Json.fromFields(Seq(name -> linkObjectJson))
}
Json.fromFields(Seq(name -> Json.fromString(value)))
}

implicit val linksEncoder = Encoder.instance[Links](_.map(_.asJson).reduce(_.deepMerge(_)))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ trait CirceJsonapiSupport extends CirceJsonapiEncoders with CirceJsonapiDecoders
implicit val circeJsonapiUnmarshaller = Unmarshaller.delegate[String, RootObject](
`application/vnd.api+json`,
`application/json`
)(decode[RootObject](_).toOption.get)
)(decode[RootObject](_).right.get)
}

object CirceJsonapiSupport extends CirceJsonapiSupport
Loading