Skip to content

Commit

Permalink
Merge branch 'more-guides'
Browse files Browse the repository at this point in the history
  • Loading branch information
julienrf committed Oct 18, 2018
2 parents a2188e0 + 0210675 commit 3422bfc
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ import io.circe.{Json, parser, Decoder => CirceDecoder, Encoder => CirceEncoder}
*/
trait JsonEntitiesFromCodec extends endpoints.algebra.JsonEntitiesFromCodec {

//#type-carrier
type JsonCodec[A] = CirceCodec[A]
//#type-carrier

implicit def jsonCodec[A](implicit codec: CirceCodec[A]): Codec[String, A] = new Codec[String, A] {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ import scala.util.control.NonFatal
*/
trait JsonEntitiesFromCodec extends endpoints.algebra.JsonEntitiesFromCodec {

//#type-carrier
type JsonCodec[A] = Format[A]
//#type-carrier

implicit def jsonCodec[A : Format]: Codec[String, A] = new Codec[String, A] {

Expand Down
4 changes: 4 additions & 0 deletions algebras/algebra/src/main/scala/endpoints/Tupler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,15 @@ package endpoints
* - A, Unit -> A
* - Unit, A -> A
*/
//#definition
trait Tupler[A, B] {
type Out
//#definition
def apply(a: A, b: B): Out
def unapply(out: Out): (A, B)
//#definition
}
//#definition

object Tupler extends Tupler4

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ import scala.language.higherKinds
*/
trait JsonEntities extends Endpoints {

//#request-response-types
/** Type class defining how to represent the `A` information as a JSON request entity */
type JsonRequest[A]

/** Defines a `RequestEntity[A]` given an implicit `JsonRequest[A]` */
def jsonRequest[A : JsonRequest](docs: Documentation = None): RequestEntity[A]

/** Type class defining how to represent the `A` information as a JSON response entity */
type JsonResponse[A]
//#request-response-types

/** Defines a `RequestEntity[A]` given an implicit `JsonRequest[A]` */
def jsonRequest[A : JsonRequest](docs: Documentation = None): RequestEntity[A]

/** Defines a `Response[A]` given an implicit `JsonResponse[A]` */
def jsonResponse[A : JsonResponse](docs: Documentation = None): Response[A]
Expand All @@ -42,8 +44,10 @@ trait JsonEntitiesFromCodec extends JsonEntities {
type JsonRequest[A] = Codec[String, A]
type JsonResponse[A] = Codec[String, A]

//#json-codec-type
/** A JSON codec type class */
type JsonCodec[A]
//#json-codec-type

/** Turns a JsonCodec[A] into a Codec[String, A] */
implicit def jsonCodec[A : JsonCodec]: Codec[String, A]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ trait JsonSchemaEntities
extends endpoints.algebra.JsonEntities
with JsonSchemas {

//#type-carrier
type JsonRequest[A] = JsonSchema[A]
type JsonResponse[A] = JsonSchema[A]
//#type-carrier

}
4 changes: 3 additions & 1 deletion documentation/manual/src/ornate/algebras/json-entities.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ a JSON decoder for `A`. Similarly, server interpreters fix the
interpreters fix these types to be JSON schemas for `A`.

The remaining sections document some concrete types given by algebras
extending `JsonEntities`.
extending `JsonEntities`. You might also be interested in looking at
the [JSON codecs guide](/guides/json-codecs.md), which explains which
families of algebras and interpreters you should use together.

## `JsonSchemaEntities`

Expand Down
15 changes: 14 additions & 1 deletion documentation/manual/src/ornate/guides.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,27 @@

This part of the documentation provides in-depth guides for specific features.

## JSON codecs

When working with JSON entities, the large variety of algebras and interpreters
might make it a bit hard to see which ones you should use. The
[JSON codecs](/guides/json-codecs.md) guide helps you to choose the solution
that fits your need.

## `Tupler`

You might have seen implicit `Tupler` parameters in the type signature
of some algebra operations. The [Tupler](/guides/tupler.md) guide explains
what it is used for.

## Extending *endpoints* with application-specific concepts

When you have application-specific aspects of your communication endpoints
that are not covered by *endpoints* you can extend the algebras and their
interpreters to include them.

Typically, each application has its own way of dealing with authentication.
The [custom authentication](guides/custom-authentication.md) guide shows how
The [custom authentication](/guides/custom-authentication.md) guide shows how
to enrich the algebras with authentication-related vocabulary and how to
extend the client and server interpreters to consistently implement the
application-specific authentication mechanism.
72 changes: 72 additions & 0 deletions documentation/manual/src/ornate/guides/json-codecs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# JSON codecs

The [JSON entities algebras](/algebras/json-entities.md) show that several options
are available to define endpoints having JSON entities. In this guide, we show
which options should be used in which case, and which interpreters are compatible
with which algebras.

## Use codecs at the algebra level

The most general algebra for defining JSON entities uses separate
types for request and response entities:

~~~ scala src=../../../../../algebras/algebra/src/main/scala/endpoints/algebra/JsonEntities.scala#request-response-types
~~~

However, in case your endpoint definitions are interpreted in multiple ways these types
should be aligned so that requests and responses are consistently encoded, decoded and
documented by client, server and documentation interpreters. A consequence of this is that an
implicit instance of such codecs has to be available for your data types at the
definition-site of your endpoints.

The next sections show how to use the two families of “codec”-based `JsonEntities` algebra
specializations: `JsonEntitiesFromCodec` and `JsonSchemaEntities`.

## `JsonEntitiesFromCodec`

### Algebra

In case you don’t need to document the JSON schemas of your request and response entities, the
`JsonEntitiesFromCodec` family of algebras is the preferred approach. These algebras fix both
the `JsonRequest` and `JsonResponse` types to a same (abstract) `JsonCodec` type:

~~~ scala src=../../../../../algebras/algebra/src/main/scala/endpoints/algebra/JsonEntities.scala#json-codec-type
~~~

Generally, you want to use a `JsonEntitiesFromCodec` algebra that fixes this `JsonCodec` type to
a concrete type. An example is `endpoints.algebra.playjson.JsonEntitiesFromCodec`, which
aligns the `JsonCodec` type with Play’s `Format` type:

~~~ scala src=../../../../../algebras/algebra-playjson/src/main/scala/endpoints/algebra/playjson/JsonEntitiesFromCodec.scala#type-carrier
~~~

The [JSON entities algebra](/algebras/json-entities.md#jsonentitiesfromcodec) documentation page
shows the list of `JsonEntitiesFromCodec` algebras that fixes the `JsonCodec` type to a concrete type.

### Interpreters

To interpret endpoints defined with such algebras, apply any interpreter named `JsonEntitiesFromCodec`
that matches your [family](/algebras-and-interpreters.md#interpreters) of interpreters. For instance,
if you use interpreters from the `endpoints.xhr` package (ie. the Scala.js web interpreters), you
should use the `endpoints.xhr.JsonEntitiesFromCodec` interpreter.

## `JsonSchemaEntities`

### Algebra

The `endpoints.algebra.JsonSchemaEntities` algebra allows you to document the JSON schema of request
and response entities (when you apply a documentation interpreter to endpoints defined with this
algebra). Both the `JsonRequest` and `JsonResponse` types are fixed to the
`JsonSchema` type provided by the [JsonSchemas](/algebras/json-schemas.md) algebra:

~~~ scala src=../../../../../algebras/algebra/src/main/scala/endpoints/algebra/JsonSchemaEntities.scala#type-carrier
~~~

### Interpreters

To interpret endpoints defined with this algebra, pick an interpreter that matches your
[family](/algebras-and-interpreters.md#interpreters) of interpreters and, if relevant,
the underlying JSON library to use. For instance, the
`endpoints.play.server.circe.JsonSchemaEntities` trait is a server interpreter based
on Play framework that uses the circe JSON library.

91 changes: 91 additions & 0 deletions documentation/manual/src/ornate/guides/tupler.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# `Tupler`

This guide explains why we use implicit [`Tupler`](api:endpoints.Tupler) parameters
in the signature of some algebra operations and how it works.

## Motivation

As explained in the [design](/design.md) page, to model a request that carries
an information of type `A`, we use the type `Request[A]`. This type `A` is
important because it represents what is needed by clients to build such
a request and what is received by servers to process such a request. For
instance, a request carrying a user id could be modeled as `Request[Long]`.
Clients would have to supply a `Long` value in order to build such a request,
and servers would decode a `Long` value. Tracking these types is important
because it guarantees that requests and responses are well-formed.

Now, suppose that we define a request that has an URL carrying a user id
(of type `Long`), and an entity carrying an `UpdateUser` value (describing
changes to apply to a user resource). Such a request would have type
`Request[(Long, UpdateUser)]`.

To support the definition of such requests, the algebra provides an
operation that, given an URL and a request entity, returns a request.
A naive definition of such an operation could be the following:

~~~ scala
def request[U, E](url: Url[U], entity: RequestEntity[E]): Request[(U, E)]
~~~

So that, given an `Url[Long]` and a `RequestEntity[UpdateUser]`, it
would return a `Request[(Long, UpdateUser)]`.

However, some requests have no entity. An empty request entity is modeled
by the `emptyEntity` constructor, which has type `RequestEntity[Unit]`.

This means that given the above definition of `request`, defining a
request whose URL carries a `Long` value, but having no entity, would
have type `Request[(Long, Unit)]`. Also, to build such requests
clients would have to supply a `(Long, Unit)` value, and to handle
such requests servers would have to process a `(Long, Unit)` value.
However, these `Unit` values are never meaningful: they can not carry
any useful information.

Things are even worse because in practice requests are formed of an
`Url[U]`, a `RequestEntity[E]` and a `RequestHeaders[H]`. So, the
“naive” return type of a request should be `Request[(U, E, H)]`,
even if several of these type parameters are instantiated to `Unit`.

The goal of the `Tupler` type is to compute more useful tuple types
by discarding the `Unit` parameters. For instance, it produces a
`Request[Long]` instead of a `Request[(Long, Unit)]`. Additionally,
nested tuples are flattened: `Request[((Long, Boolean), String)]`
becomes `Request[(Long, Boolean, String)]`.

## How it works

The `Tupler[A, B]` type takes two type parameters `A` and `B`
and defines an abstract type member `Out`. This `Out` type defines
the “useful” form of tupling `A` and `B`.

~~~ scala src=../../../../../algebras/algebra/src/main/scala/endpoints/Tupler.scala#definition
~~~

Algebra operations that want to tuple types `A` and `B` take as
parameter an implicit `tupler: Tupler[A, B]` and return a
`tupler.Out` instead of an `(A, B)`.

Several implicit instances of `Tupler` are provided. For example,
the following instance returns a pair of `(A, B)` for all types
`A` and `B`:

~~~ scala
implicit def pair[A, B]: Tupler[A, B] { type Out = (A, B) }
~~~

But we have seen that our goal is to special case tupling `Unit`
types. This is achieved by defining another `Tupler` instance
with a higher priority than this one, for the case where one
of the type parameters is `Unit`. For instance:

~~~ scala
implicit def keepFirst[A]: Tupler[A, Unit] { type Out = A }
~~~

The `keepFirst` instance computes the type of tupling `A`
and `Unit`, for all type `A`, to be `A`.

When the operation that takes an implicit `Tupler` as parameter
is called, appropriate instances of `Tupler` will compute the type
of the resulting tuple, by discarding `Unit` types and flattening
nested tuples.

0 comments on commit 3422bfc

Please sign in to comment.