Skip to content

Commit

Permalink
Merge branch 'main' into update/scala-library-2.13.12
Browse files Browse the repository at this point in the history
  • Loading branch information
ybasket authored Jan 26, 2024
2 parents 3d5e99a + 8b2ae91 commit 104d7b0
Show file tree
Hide file tree
Showing 11 changed files with 241 additions and 27 deletions.
28 changes: 14 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,17 @@ jobs:
fail-fast: false
matrix:
os: [ubuntu-latest]
scala: [2.12.18, 2.13.12, 3.3.0]
scala: [2.12.18, 2.13.12, 3.3.1]
java: [temurin@8, temurin@11, temurin@17]
project: [rootJS, rootJVM, rootNative]
exclude:
- scala: 2.12.18
java: temurin@11
- scala: 2.12.18
java: temurin@17
- scala: 3.3.0
- scala: 3.3.1
java: temurin@11
- scala: 3.3.0
- scala: 3.3.1
java: temurin@17
- project: rootJS
java: temurin@11
Expand Down Expand Up @@ -152,11 +152,11 @@ jobs:

- name: Make target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: mkdir -p target .js/target cbor/.js/target site/target csv/.jvm/target xml/.jvm/target xml-scala/.js/target xml/.js/target cbor/.native/target xml-scala/.native/target cbor/.jvm/target xml-scala/.jvm/target csv/.native/target .jvm/target csv/.js/target .native/target xml/.native/target project/target
run: mkdir -p json/.native/target target .js/target cbor/.js/target site/target json/.jvm/target csv/.jvm/target xml/.jvm/target xml-scala/.js/target xml/.js/target cbor/.native/target xml-scala/.native/target cbor/.jvm/target xml-scala/.jvm/target csv/.native/target .jvm/target csv/.js/target .native/target xml/.native/target json/.js/target project/target

- name: Compress target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
run: tar cf targets.tar target .js/target cbor/.js/target site/target csv/.jvm/target xml/.jvm/target xml-scala/.js/target xml/.js/target cbor/.native/target xml-scala/.native/target cbor/.jvm/target xml-scala/.jvm/target csv/.native/target .jvm/target csv/.js/target .native/target xml/.native/target project/target
run: tar cf targets.tar json/.native/target target .js/target cbor/.js/target site/target json/.jvm/target csv/.jvm/target xml/.jvm/target xml-scala/.js/target xml/.js/target cbor/.native/target xml-scala/.native/target cbor/.jvm/target xml-scala/.jvm/target csv/.native/target .jvm/target csv/.js/target .native/target xml/.native/target json/.js/target project/target

- name: Upload target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/main')
Expand Down Expand Up @@ -300,32 +300,32 @@ jobs:
tar xf targets.tar
rm targets.tar
- name: Download target directories (3.3.0, rootJS)
- name: Download target directories (3.3.1, rootJS)
uses: actions/download-artifact@v3
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-3.3.0-rootJS
name: target-${{ matrix.os }}-${{ matrix.java }}-3.3.1-rootJS

- name: Inflate target directories (3.3.0, rootJS)
- name: Inflate target directories (3.3.1, rootJS)
run: |
tar xf targets.tar
rm targets.tar
- name: Download target directories (3.3.0, rootJVM)
- name: Download target directories (3.3.1, rootJVM)
uses: actions/download-artifact@v3
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-3.3.0-rootJVM
name: target-${{ matrix.os }}-${{ matrix.java }}-3.3.1-rootJVM

- name: Inflate target directories (3.3.0, rootJVM)
- name: Inflate target directories (3.3.1, rootJVM)
run: |
tar xf targets.tar
rm targets.tar
- name: Download target directories (3.3.0, rootNative)
- name: Download target directories (3.3.1, rootNative)
uses: actions/download-artifact@v3
with:
name: target-${{ matrix.os }}-${{ matrix.java }}-3.3.0-rootNative
name: target-${{ matrix.os }}-${{ matrix.java }}-3.3.1-rootNative

- name: Inflate target directories (3.3.0, rootNative)
- name: Inflate target directories (3.3.1, rootNative)
run: |
tar xf targets.tar
rm targets.tar
Expand Down
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = 3.7.12
version = 3.7.17

style = default

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Home of http4s integrations with [fs2-data][fs2-data]. Initially forked from [ht
* XML and Scala XML. Works as a drop-in replacement for [http4s-scala-xml][http4s-scala-xml]
* CSV
* CBOR
* JSON

Check out the [docs][docs] for examples.

Expand Down
31 changes: 23 additions & 8 deletions build.sbt
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
ThisBuild / tlBaseVersion := "0.2"
ThisBuild / tlBaseVersion := "0.3"
ThisBuild / developers := List(
tlGitHubDev("rossabaker", "Ross A. Baker"),
tlGitHubDev("ybasket", "Yannick Heiber"),
)

val Scala213 = "2.13.12"
ThisBuild / crossScalaVersions := Seq("2.12.18", Scala213, "3.3.0")
ThisBuild / crossScalaVersions := Seq("2.12.18", Scala213, "3.3.1")
ThisBuild / scalaVersion := Scala213

// ensure missing timezones don't break tests on JS
Expand All @@ -16,10 +16,10 @@ ThisBuild / jsEnv := {

lazy val root = tlCrossRootProject.aggregate(xml, xmlScala, csv, cbor)

val http4sVersion = "0.23.23"
val http4sVersion = "0.23.25"
val scalaXmlVersion = "2.2.0"
val fs2Version = "3.8.0"
val fs2DataVersion = "1.8.0"
val fs2Version = "3.9.4"
val fs2DataVersion = "1.10.0"
val munitVersion = "1.0.0-M8"
val munitCatsEffectVersion = "2.0.0-M3"

Expand Down Expand Up @@ -66,7 +66,6 @@ lazy val csv = crossProject(JVMPlatform, JSPlatform, NativePlatform)
name := "http4s-fs2-data-csv",
description := "Provides csv codecs for http4s via fs2-data",
startYear := Some(2023),
tlVersionIntroduced := Map("2.12" -> "0.2", "2.13" -> "0.2", "3" -> "0.2"),
libraryDependencies ++= Seq(
"co.fs2" %%% "fs2-core" % fs2Version,
"org.http4s" %%% "http4s-core" % http4sVersion,
Expand All @@ -85,7 +84,6 @@ lazy val cbor = crossProject(JVMPlatform, JSPlatform, NativePlatform)
name := "http4s-fs2-data-cbor",
description := "Provides CBOR codecs for http4s via fs2-data",
startYear := Some(2023),
tlVersionIntroduced := Map("2.12" -> "0.2", "2.13" -> "0.2", "3" -> "0.2"),
libraryDependencies ++= Seq(
"co.fs2" %%% "fs2-core" % fs2Version,
"org.http4s" %%% "http4s-core" % http4sVersion,
Expand All @@ -96,9 +94,26 @@ lazy val cbor = crossProject(JVMPlatform, JSPlatform, NativePlatform)
),
)

lazy val json = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("json"))
.settings(
name := "http4s-fs2-data-json",
description := "Provides JSON codecs for http4s via fs2-data",
startYear := Some(2024),
libraryDependencies ++= Seq(
"co.fs2" %%% "fs2-core" % fs2Version,
"org.http4s" %%% "http4s-core" % http4sVersion,
"org.gnieh" %%% "fs2-data-json" % fs2DataVersion,
"org.scalameta" %%% "munit-scalacheck" % munitVersion % Test,
"org.typelevel" %%% "munit-cats-effect" % munitCatsEffectVersion % Test,
"org.http4s" %%% "http4s-laws" % http4sVersion % Test,
),
)

lazy val docs = project
.in(file("site"))
.dependsOn(xml.jvm, xmlScala.jvm, csv.jvm, cbor.jvm)
.dependsOn(xml.jvm, xmlScala.jvm, csv.jvm, cbor.jvm, json.jvm)
.settings(
libraryDependencies ++= Seq(
"io.circe" %%% "circe-generic" % "0.14.5",
Expand Down
55 changes: 55 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,58 @@ curl -s -X "POST" "http://localhost:8080/csv/toCbor" \

Then copy the output to [https://cbor.me](https://cbor.me) or a similar CBOR viewer. Make sure to view as `cborseq` otherwise the output will be truncated.



## http4s-fs2-data-json

Provides basic support for parsing and encoding `fs2.data.json.Token` streams that can be handled in a streaming fashion
using the pipes and builders `fs2-data` provides.

```scala
libraryDependencies += "org.http4s" %% "http4s-fs2-data-json" % "@VERSION@"
```

### Example

This example consumes a JSON input and returns it pretty printed.

```scala mdoc
import cats.effect.Async
import org.http4s.{EntityDecoder, EntityEncoder, HttpRoutes}
import org.http4s.dsl.Http4sDsl
import fs2.Stream
import fs2.data.json.Token

class JsonHttpEndpoint[F[_]](implicit F: Async[F]) extends Http4sDsl[F] {

private implicit val payloadDecoder: EntityDecoder[F, Stream[F, Token]] =
org.http4s.fs2data.json.jsonTokensDecoder

private implicit val payloadEncoder: EntityEncoder[F, Stream[F, Token]] =
org.http4s.fs2data.json.jsonTokensEncoder(prettyPrint = true)

val service: HttpRoutes[F] = HttpRoutes.of {
case req @ POST -> Root / "prettyJson" =>
Ok(Stream.force(req.as[Stream[F, Token]]))
}
}
```

You can try yourself with this snippet:

```shell
curl -s -X "POST" "http://localhost:8080/prettyJson" \
-H 'Content-Type: text/json; charset=utf-8' \
-d '{"a":2024,"b":[true,false],"c":{"d":"e"},"d":1}'
{
"a": 2024,
"b": [
true,
false
],
"c": {
"d": "e"
},
"d": 1
}
```
66 changes: 66 additions & 0 deletions json/src/main/scala/org/http4s/fs2data/json/JsonInstances.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright 2023 http4s.org
*
* 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 org.http4s
package fs2data.json

import cats.data.NonEmptyList
import cats.effect.Concurrent
import cats.syntax.applicative._
import cats.syntax.monadError._
import fs2.data.json._
import fs2.Stream
import cats.syntax.show._
import org.http4s.Charset.`UTF-8`
import org.http4s.headers.{`Content-Type`, `Transfer-Encoding`}

trait JsonInstances {

implicit def jsonTokensDecoder[F[_]: Concurrent]: EntityDecoder[F, Stream[F, Token]] =
EntityDecoder.decodeBy(MediaType.application.json) { msg =>
DecodeResult.successT(
msg.bodyText
.through(tokens)
.adaptError { case ex: JsonException =>
MalformedMessageBodyFailure(
s"Invalid Json (${ex.context.fold("No context")(jc => jc.show)}): ${ex.msg}",
Some(ex),
)
}
)
}

def jsonTokensEncoder[F[_]](prettyPrint: Boolean)(implicit
charset: Charset = `UTF-8`
): EntityEncoder[F, Stream[F, Token]] = EntityEncoder.encodeBy(
Headers(
`Content-Type`(MediaType.application.json).withCharset(charset),
`Transfer-Encoding`(TransferCoding.chunked.pure[NonEmptyList]),
)
) { tokens =>
Entity(
tokens
.through(
if (prettyPrint) render.pretty() else render.compact
)
.through(fs2.text.encode[F](charset.nioCharset))
)
}

implicit def jsonTokensEncoder[F[_]]: EntityEncoder[F, Stream[F, Token]] =
jsonTokensEncoder(prettyPrint = false)

}
19 changes: 19 additions & 0 deletions json/src/main/scala/org/http4s/fs2data/json/json.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright 2023 http4s.org
*
* 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 org.http4s.fs2data

package object json extends JsonInstances
55 changes: 55 additions & 0 deletions json/src/test/scala/org/http4s/fs2data/json/JsonEventSuite.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package org.http4s.fs2data.json

import cats.effect.IO
import cats.syntax.all._
import fs2.Stream
import fs2.data.json._
import fs2.data.json.literals.JsonInterpolator
import munit.CatsEffectSuite
import munit.ScalaCheckEffectSuite
import org.http4s.EntityDecoder
import org.http4s.Request

class JsonEventSuite extends CatsEffectSuite with ScalaCheckEffectSuite {
test("round-trip Json") {
val in = json"""{"a": 1, "b": [true, false, null], "c": {"d": "e"}, "d": 1.2e3, "b": null}"""
Stream
.force(Request[IO]().withEntity(in.lift[IO]).as[Stream[IO, Token]])
.compile
.toList
.map(_.asRight[Throwable])
.assertEquals(in.toList)
}

test("round-trip Json string") {
val in = """{"a":1,"b":[true,false,null],"c":{"d":"e"},"d":1.2e3,"b":null}"""
Request[IO]()
.withEntity(in)
.as[Stream[IO, Token]]
.map(Request[IO]().withEntity(_))
.flatMap(EntityDecoder.text[IO].decode(_, false).value)
.assertEquals(Right(in))
}

test("round-trip Json pretty printing") {
val in = """{
| "a": 1,
| "b": [
| true,
| false,
| null
| ],
| "c": {
| "d": "e"
| },
| "d": 1.2e3,
| "b": null
|}""".stripMargin
Request[IO]()
.withEntity(in)
.as[Stream[IO, Token]]
.map(Request[IO]().withEntity(_)(jsonTokensEncoder[IO](prettyPrint = true)))
.flatMap(EntityDecoder.text[IO].decode(_, false).value)
.assertEquals(Right(in))
}
}
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.9.4
sbt.version=1.9.8
4 changes: 2 additions & 2 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
addSbtPlugin("org.http4s" % "sbt-http4s-org" % "0.14.13")

addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.13.2")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.15.0")

addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.14")
addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.4.17")

addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,7 +434,10 @@ object generators {
n <- Gen.poisson(5)
s <- Gen.stringOfN(n, Gen.oneOf(char))
// Text may not contain these two in literal form, §2.4 of XML syntax
r = s.replace("&", "&amp;").replace("<", "&lt;")
// We replace them by empty strings instead of their quoted versions because Scala XML fails the roundtrip
// Relates to https://github.com/scala/scala-xml/issues/57
// Works around https://github.com/http4s/http4s-fs2-data/issues/88
r = s.replace("&", "").replace("<", "")
} yield Text(r)

val genComment: Gen[Comment] =
Expand Down

0 comments on commit 104d7b0

Please sign in to comment.