---
id: endpoint
title: Endpoint
---

The `Endpoint` API in ZIO HTTP, is an alternative way to describe the endpoints but in a declarative way. It is a high-level API that allows us to describe the endpoints and their inputs, outputs, and how they should look. So we can think of it as a DSL for just describing the endpoints, and then we can implement them separately.

Using `Endpoint` API enables us to generate OpenAPI documentation, and also to generate clients for the endpoints.

## Overview

Before delving into the detailed description of the `Endpoint` API, let's begin with a simple example to demonstrate how we can define an endpoint using the `Endpoint` API:

```scala
import zio._
import zio.http._
import zio.http.codec._
import zio.http.endpoint.Endpoint
import zio.schema.DeriveSchema

case class Book(title: String, authors: List[String])
object Book {
  implicit val schema = DeriveSchema.gen[Book]
}

val endpoint =
  Endpoint(RoutePattern.GET / "books")
    .query(HttpCodec.query[String]("q") examples (("example1", "scala"), ("example2", "zio")))
    .out[List[Book]]
```

In the above example, we defined an endpoint on the path `/books` that accepts a query parameter `q` of type `String` and returns a list of `Book`.

After defining the endpoint, we are ready to implement it. We can implement it using the `Endpoint#implement` method, which takes a proper handler function that will be called when the endpoint is invoked and returns a `Route`:

```scala
val booksRoute = endpoint.implement(query => BookRepo.find(query))
```

We can also generate OpenAPI documentation for our endpoint using the `OpenAPIGen.fromEndpoints` constructor:

```scala
val openAPI       = OpenAPIGen.fromEndpoints(title = "Library API", version = "1.0", endpoint)
val swaggerRoutes = SwaggerUI.routes("docs" / "openapi", openAPI)
```

And finally we are ready to serve all the routes. Let's see the complete example:

```scala title="zio-http-example/src/main/scala/example/endpoint/BooksEndpointExample.scala" 
package example.endpoint

import zio._

import zio.schema.annotation.description
import zio.schema.{DeriveSchema, Schema}

import zio.http._
import zio.http.codec.PathCodec._
import zio.http.codec._
import zio.http.endpoint._
import zio.http.endpoint.openapi._

object BooksEndpointExample extends ZIOAppDefault {
  case class Book(
    @description("Title of the book")
    title: String,
    @description("List of the authors of the book")
    authors: List[String],
  )
  object Book {
    implicit val schema: Schema[Book] = DeriveSchema.gen
  }

  object BookRepo {
    val book1 = Book("Programming in Scala", List("Martin Odersky", "Lex Spoon", "Bill Venners", "Frank Sommers"))
    val book2 = Book("Zionomicon", List("John A. De Goes", "Adam Fraser"))
    val book3 = Book("Effect-Oriented Programming", List("Bill Frasure", "Bruce Eckel", "James Ward"))
    def find(q: String): List[Book] = {
      if (q.toLowerCase == "scala") List(book1, book2, book3)
      else if (q.toLowerCase == "zio") List(book2, book3)
      else List.empty
    }
  }

  val endpoint =
    Endpoint((RoutePattern.GET / "books") ?? Doc.p("Route for querying books"))
      .query(
        HttpCodec.query[String]("q").examples(("example1", "scala"), ("example2", "zio")) ?? Doc.p(
          "Query parameter for searching books",
        ),
      )
      .out[List[Book]](Doc.p("List of books matching the query")) ?? Doc.p(
      "Endpoint to query books based on a search query",
    )

  val booksRoute    = endpoint.implementHandler(handler((query: String) => BookRepo.find(query)))
  val openAPI       = OpenAPIGen.fromEndpoints(title = "Library API", version = "1.0", endpoint)
  val swaggerRoutes = SwaggerUI.routes("docs" / "openapi", openAPI)
  val routes        = Routes(booksRoute) ++ swaggerRoutes

  def run = Server.serve(routes).provide(Server.default)
}
```

By running the above example, other than the main `/books` route, we can also access the OpenAPI documentation using the SwaggerUI at the `/docs/openapi` route.

This was an overview of the `Endpoint` API in ZIO HTTP. Next, we will dive deeper into the `Endpoint` API and see how we can describe the endpoints in more detail.

## Describing Endpoints

Each endpoint is described by a set of properties, such as the path, query parameters, headers, and response type. The `Endpoint` API provides a set of methods to describe these properties. We can think of an endpoint as a function that takes some input and returns some output:

* **Input Properties**— They can be the HTTP method, path parameters, query parameters, request headers, and the request body.
* **Output Properties**­— They can be success or failure! Both success and failure can have a response body, media type, and status code.

Also, we can provide metadata for each property, such as documentation, examples, etc.

## Describing Input Properties

### Method and Path Parameters

We start describing an endpoint by specifying the HTTP method and the path. The default constructor of the `Endpoint` class takes a `RoutePattern` which is a combination of the HTTP method and the path:

```scala
import zio._
import zio.http._
import zio.http.endpoint._
import zio.http.endpoint.AuthType._
import zio.http.codec.PathCodec

val endpoint1: Endpoint[Unit, Unit, ZNothing, ZNothing, None] =
  Endpoint(RoutePattern.GET / "users")

val endpoint2: Endpoint[String, String, ZNothing, ZNothing, None] =
  Endpoint(RoutePattern.GET / "users" / PathCodec.string("user_name"))

val endpoint3: Endpoint[(String, Int), (String, Int), ZNothing, ZNothing, None] =
  Endpoint(RoutePattern.GET / "users" / PathCodec.string("user_name") / "posts" / PathCodec.int("post_id"))
```

In the above examples, we defined three endpoints. The first one is a simple endpoint that matches the GET method on the `/users` path. The second one matches the GET method on the `/users/:user_name` path, where `:user_name` is a path parameter of type `String`. The third one matches the GET method on the `/users/:user_name/posts/:post_id` path, where `:user_name` and `:post_id` are path parameters of type `String` and `Int`, respectively.

The `Endpoint` is a type-safe way to describe the endpoints. For example, if we try to implement the `endpoint3` with a handler that takes a different input type other than `(String, Int)`, the compiler will give us an error.

### Query Parameters

Query parameters can be described using the `Endpoint#query` method which takes a `QueryCodec[A]`:


```scala
val endpoint: Endpoint[Unit, String, ZNothing, ZNothing, AuthType.None] =
  Endpoint(RoutePattern.GET / "books")
    .query(HttpCodec.query[String]("q"))
```

QueryCodecs are composable, so we can combine multiple query parameters:

```scala
val endpoint: Endpoint[Unit, (String, Int), ZNothing, ZNothing, AuthType.None] =
  Endpoint(RoutePattern.GET / "books")
    .query(HttpCodec.query[String]("q") ++ HttpCodec.query[Int]("limit"))
```

Or we can use the `query` method multiple times:

```scala
val endpoint: Endpoint[Unit, (String, Int), ZNothing, ZNothing, AuthType.None] =
  Endpoint(RoutePattern.GET / "books")
    .query(HttpCodec.query[String]("q"))
    .query(HttpCodec.query[Int]("limit"))
```

Please note that as we add more properties to the endpoint, the input and output types of the endpoint change accordingly. For example, in the following example, we have an endpoint with a path parameter of type `String` and two query parameters of type `String` and `Int`. So the input type of the endpoint is `(String, String, Int)`:

```scala
val endpoint: Endpoint[String, (String, String, Int), ZNothing, ZNothing, AuthType.None] =
  Endpoint(RoutePattern.GET / "books" / PathCodec.string("genre"))
    .query(HttpCodec.query[String]("q"))
    .query(HttpCodec.query[Int]("limit"))
```

When we implement the endpoint, the handler function should take the input type of a tuple that the first element is the "genre" path parameter, and the second and third elements are the query parameters "q" and "limit" respectively.

### Headers

Headers can be described using the `Endpoint#header` method which takes a `HeaderCodec[A]` and specifies that the given header is required, for example:

```scala
val endpoint: Endpoint[String, (String, Header.Authorization), ZNothing, ZNothing, AuthType.None] =
  Endpoint(RoutePattern.GET / "books" / PathCodec.string("genre"))
    .header(HeaderCodec.authorization)
```

The authorization type can also be encoded into the endpoint via the respective `HeaderCodec`:

```scala
val userEndpoint = Endpoint(RoutePattern.GET / "users" / "me")
    .header(HeaderCodec.bearerAuth)
```

### Request Body

The request body can be described using the `Endpoint#in` method:

```scala
import zio.schema._

case class Book(title: String, author: String)

object Book {
  implicit val schema: Schema[Book] = DeriveSchema.gen[Book]
}

val endpoint: Endpoint[Unit, Book, ZNothing, ZNothing, AuthType.None] =
  Endpoint(RoutePattern.POST / "books" )
    .in[Book]
```

The above example describes an endpoint that accepts a `Book` object as the request body.

By default, the request body is not named and its media type is determined by the `Content-Type` header. But for multipart form data, we can have multiple request bodies, called parts:

```scala
val endpoint =
  Endpoint(RoutePattern.POST / "submit-form")
    .header(HeaderCodec.contentType.expect(Header.ContentType(MediaType.multipart.`form-data`)))
    .in[String]("title")
    .in[String]("author")
```

In the above example, we have defined an endpoint that describes a multipart form data request body with two parts: `title` and `author`. Let's see what the request body might look like:

```http request
POST /submit-form HTTP/1.1
Content-Type: multipart/form-data; boundary=boundary1234567890

--boundary1234567890
Content-Disposition: form-data; name="title"

The Title of the Book
--boundary1234567890
Content-Disposition: form-data; name="author"

John Doe
--boundary1234567890--
```

The `Endpoint#in` method has multiple overloads that can be used to describe other properties of the request body, such as the media type and documentation.

## Describing Output Properties of Success Responses

The `Endpoint#out` method is used to describe the output properties of the success response:

```scala
import zio.http._
import zio.schema._

case class Book(title: String, author: String)

object Book {
  implicit val schema: Schema[Book] = DeriveSchema.gen[Book]
}

val endpoint: Endpoint[Unit, String, ZNothing, List[Book], AuthType.None] =
  Endpoint(RoutePattern.GET / "books")
    .query(HttpCodec.query[String]("q"))
    .out[List[Book]]
```

In the above example, we defined an endpoint that describes a query parameter `q` as input and returns a list of `Book` as output. The `Endpoint#out` method has multiple overloads that can be used to describe other properties of the output, such as the status code, media type, and documentation.

We can also add custom headers to the output using the `Endpoint#outHeader` method:

```scala
import zio.http._
import zio.schema._

case class Book(title: String, author: String)

object Book {
  implicit val schema: Schema[Book] = DeriveSchema.gen[Book]
}

val endpoint: Endpoint[Unit, String, ZNothing, (List[Book], Header.Date), AuthType.None] =
    Endpoint(RoutePattern.GET / "books")
      .query(HttpCodec.query[String]("q"))
      .out[List[Book]]
      .outHeader(HttpCodec.date)
```

Sometimes based on the condition, we might want to return different types of responses. We can use the `Endpoint#out` method multiple times to describe different output types:

```scala
import zio._
import zio.http.{RoutePattern, _}
import zio.http.endpoint.Endpoint
import zio.schema.DeriveSchema.gen
import zio.schema._

case class Book(title: String, author: String)

object Book {
  implicit val schema: Schema[Book] = DeriveSchema.gen
}

case class Article(title: String, author: String)

object Article {
  implicit val schema: Schema[Article] = DeriveSchema.gen
}

case class Course(title: String, price: Double)
object Course {
  implicit val schema = DeriveSchema.gen[Course]
}

case class Quiz(question: String, level: Int)
object Quiz {
  implicit val schema = DeriveSchema.gen[Quiz]
}

object EndpointWithMultipleOutputTypes extends ZIOAppDefault {
  val endpoint: Endpoint[Unit, Unit, ZNothing, Either[Quiz, Course], AuthType.None] =
    Endpoint(RoutePattern.GET / "resources")
      .out[Course]
      .out[Quiz]

  def run = Server.serve(
    endpoint.implement(_ =>
      ZIO.randomWith(_.nextBoolean)
        .map(r =>
          if (r) Right(Course("Introduction to Programming", 49.99))
          else Left(Quiz("What is the boiling point of water in Celsius?", 2)),
        )
    )
    .toRoutes).provide(Server.default)
}
```

For Scala 3, we can use a union type instead of an `Either` by calling `Endpoint#orOut` for more than one output:

```scala
  val endpoint: Endpoint[Unit, Unit, ZNothing, Course | Quiz, AuthType.None] = 
  Endpoint(RoutePattern.GET / "resources")
    .out[Course]
    .orOut[Quiz]
```

In the above example, we defined an endpoint that describes a path parameter `id` as input and returns either a `Book` or an `Article` as output.

With multiple outputs, we can define if all of them or just some should add an output header, by the order of calling `out` and `outHeader` methods:

```scala
import zio._
import zio.http._
import zio.http.endpoint._
import zio.schema._

case class Book(title: String, author: String)

object Book {
  implicit val schema: Schema[Book] = DeriveSchema.gen
}

case class Article(title: String, author: String)

object Article {
  implicit val schema: Schema[Article] = DeriveSchema.gen
}
// header will be added to the first output
val endpoint: Endpoint[Unit, Unit, ZNothing, Either[Article, (Book, Header.Date)], AuthType.None] =
  Endpoint(RoutePattern.GET / "resources")
    .out[Book]
    .outHeader(HttpCodec.date)
    .out[Article]

// header will be added to all outputs
val endpoint2: Endpoint[Unit, Unit, ZNothing, (Either[Article, Book], Header.Date), AuthType.None] =
  Endpoint(RoutePattern.GET / "resources")
    .out[Book]
    .out[Article]
    .outHeader(HttpCodec.date)
```

A call to `outHeder` will require to provide the header together with all outputs defined before it.

Sometimes we might want more control over the output properties, in such cases, we can provide a custom `HttpCodec` that describes the output properties using the `Endpoint#outCodec` method.
This can be very useful when we only want to add headers to a subset of outputs for example:

```scala
import zio._
import zio.http._
import zio.http.endpoint._
import zio.schema._

case class Book(title: String, author: String)

object Book {
  implicit val schema: Schema[Book] = DeriveSchema.gen
}

case class Article(title: String, author: String)

object Article {
  implicit val schema: Schema[Article] = DeriveSchema.gen
}
val endpoint: Endpoint[Unit, Unit, ZNothing, Either[(Article, Header.Date), Book], AuthType.None] =
  Endpoint(RoutePattern.GET / "resources")
    .out[Book]
    .outCodec(HttpCodec.content[Article] ++ HttpCodec.date)
```
Or when we want to reuse the same codec for multiple endpoints:

```scala
import zio._
import zio.http._
import zio.http.endpoint._
import zio.schema._

case class Book(title: String, author: String)

object Book {
  implicit val schema: Schema[Book] = DeriveSchema.gen
}

case class Article(title: String, author: String)

object Article {
  implicit val schema: Schema[Article] = DeriveSchema.gen
}
val bookCodec = HttpCodec.content[Book] ++ HttpCodec.date
val articleCodec = HttpCodec.content[Article] ++ HttpCodec.date

val endpoint1: Endpoint[Unit, Unit, ZNothing, (Book, Header.Date), AuthType.None] =
  Endpoint(RoutePattern.GET / "books")
    .outCodec(bookCodec)

val endpoint2: Endpoint[Unit, Unit, ZNothing, (Article, Header.Date), AuthType.None] =
    Endpoint(RoutePattern.GET / "articles")
        .outCodec(articleCodec)

val endpoint3: Endpoint[Unit, Unit, ZNothing, Either[(Article, Header.Date), (Book, Header.Date)], AuthType.None] =
    Endpoint(RoutePattern.GET / "resources")
        .outCodec(articleCodec | bookCodec)
```
## Describing Failures

For failure outputs, we can describe the output properties using the `Endpoint#outError*` methods. Let's see an example:

```scala title="zio-http-example/src/main/scala/example/endpoint/style/DeclarativeProgrammingExample.scala" 
package example.endpoint.style

import zio._

import zio.schema.{DeriveSchema, Schema}

import zio.http._
import zio.http.codec._
import zio.http.endpoint.{AuthType, Endpoint}

object DeclarativeProgrammingExample extends ZIOAppDefault {

  case class Book(title: String, authors: List[String])

  object Book {
    implicit val schema: Schema[Book] = DeriveSchema.gen
  }
  case class NotFoundError(message: String)

  object NotFoundError {
    implicit val schema: Schema[NotFoundError] = DeriveSchema.gen
  }

  object BookRepo {
    def find(id: String): ZIO[Any, NotFoundError, Book] = {
      if (id == "1")
        ZIO.succeed(Book("Zionomicon", List("John A. De Goes", "Adam Fraser")))
      else
        ZIO.fail(NotFoundError("The requested book was not found!"))
    }
  }

  val endpoint: Endpoint[Unit, String, NotFoundError, Book, AuthType.None] =
    Endpoint(RoutePattern.GET / "books")
      .query(HttpCodec.query[String]("id"))
      .out[Book]
      .outError[NotFoundError](Status.NotFound)

  val getBookHandler: Handler[Any, NotFoundError, String, Book] =
    handler(BookRepo.find(_))

  val routes = endpoint.implementHandler(getBookHandler).toRoutes @@ Middleware.debug

  def run = Server.serve(routes).provide(Server.default)
}
```

In the above example, we defined an endpoint that describes a query parameter `id` as input and returns a `Book` as output. If the book is not found, the endpoint returns a `NotFound` status code with a custom error message.

### Multiple Failure Outputs Using `Endpoint#outError`

If we have multiple failure outputs, we can use the `Endpoint#outError` method multiple times to describe different error types. By specifying more error types, the type of the endpoint will be the union of all the error types (e.g., `Either[Error1, Error2]`):

```scala
import zio._
import zio.http._
import zio.schema._
import zio.schema.DeriveSchema

case class Book(title: String, authors: List[String])
case class BookNotFound(message: String, bookId: Int)
case class AuthenticationError(message: String, userId: Int)

implicit val bookSchema     = DeriveSchema.gen[Book]
implicit val notFoundSchema = DeriveSchema.gen[BookNotFound]
implicit val authSchema     = DeriveSchema.gen[AuthenticationError]

val endpoint: Endpoint[Int, (Int, Header.Authorization), Either[AuthenticationError, BookNotFound], Book, AuthType.None] =
  Endpoint(RoutePattern.GET / "books" / PathCodec.int("id"))
    .header(HeaderCodec.authorization)
    .out[Book]
    .outError[BookNotFound](Status.NotFound)
    .outError[AuthenticationError](Status.Unauthorized)
```

<details>
<summary><b>Full Implementation Showcase</b></summary>

```scala title="zio-http-example/src/main/scala/example/endpoint/EndpointWithMultipleErrorsUsingEither.scala" 
package example.endpoint

import zio._

import zio.schema.{DeriveSchema, Schema}

import zio.http._
import zio.http.codec._
import zio.http.endpoint.{AuthType, Endpoint}

object EndpointWithMultipleErrorsUsingEither extends ZIOAppDefault {

  case class Book(title: String, authors: List[String])

  object Book {
    implicit val schema: Schema[Book] = DeriveSchema.gen
  }

  case class BookNotFound(message: String, bookId: Int)

  object BookNotFound {
    implicit val schema: Schema[BookNotFound] = DeriveSchema.gen
  }

  case class AuthenticationError(message: String, userId: Int)

  object AuthenticationError {
    implicit val schema: Schema[AuthenticationError] = DeriveSchema.gen
  }

  object BookRepo {
    def find(id: Int): ZIO[Any, BookNotFound, Book] = {
      if (id == 1)
        ZIO.succeed(Book("Zionomicon", List("John A. De Goes", "Adam Fraser")))
      else
        ZIO.fail(BookNotFound("The requested book was not found.", id))
    }
  }

  val endpoint
    : Endpoint[Int, (Int, Header.Authorization), Either[AuthenticationError, BookNotFound], Book, AuthType.None] =
    Endpoint(RoutePattern.GET / "books" / PathCodec.int("id"))
      .header(HeaderCodec.authorization)
      .out[Book]
      .outError[BookNotFound](Status.NotFound)
      .outError[AuthenticationError](Status.Unauthorized)

  def isUserAuthorized(authHeader: Header.Authorization) = false

  val getBookHandler
    : Handler[Any, Either[AuthenticationError, BookNotFound], (RuntimeFlags, Header.Authorization), Book] =
    handler { (id: Int, authHeader: Header.Authorization) =>
      if (isUserAuthorized(authHeader))
        BookRepo.find(id).mapError(Right(_))
      else
        ZIO.fail(Left(AuthenticationError("User is not authenticated", 123)))
    }

  val routes = endpoint.implementHandler(getBookHandler).toRoutes @@ Middleware.debug

  def run = Server.serve(routes).provide(Server.default)
}
```

</details>

### Multiple Failure Outputs `Endpoint#outErrors`

Alternatively, the idiomatic way to describe multiple failure outputs is to unify all the error types into a single error type using a sealed trait or an enum, and then describe the output properties using the `Endpoint#outErrors` method:

```scala
import zio.schema.DeriveSchema

case class Book(title: String, authors: List[String])
implicit val bookSchema = DeriveSchema.gen[Book]

abstract class AppError(message: String)
case class BookNotFound(message: String, bookId: Int)        extends AppError(message)
case class AuthenticationError(message: String, userId: Int) extends AppError(message)

implicit val notFoundSchema = DeriveSchema.gen[BookNotFound]
implicit val authSchema     = DeriveSchema.gen[AuthenticationError]

val endpoint: Endpoint[Int, (Int, Header.Authorization), AppError, Book, AuthType.None] =
  Endpoint(RoutePattern.GET / "books" / PathCodec.int("id"))
    .header(HeaderCodec.authorization)
    .out[Book]
    .outErrors[AppError](
      HttpCodec.error[BookNotFound](Status.NotFound),
      HttpCodec.error[AuthenticationError](Status.Unauthorized),
    )
```

The `Endpoint#outErrors` method takes a list of `HttpCodec` that describes the error types and their corresponding status codes.

<details>
<summary><b>Full Implementation Showcase</b></summary>

```scala title="zio-http-example/src/main/scala/example/endpoint/EndpointWithMultipleUnifiedErrors.scala" 
package example.endpoint

import scala.annotation.nowarn

import zio._

import zio.schema.{DeriveSchema, Schema}

import zio.http._
import zio.http.codec._
import zio.http.endpoint.{AuthType, Endpoint}

object EndpointWithMultipleUnifiedErrors extends ZIOAppDefault {

  case class Book(title: String, authors: List[String])

  object Book {
    implicit val schema: Schema[Book] = DeriveSchema.gen
  }

  @nowarn("msg=parameter .* never used")
  abstract class AppError(message: String)

  case class BookNotFound(message: String, bookId: Int) extends AppError(message)

  object BookNotFound {
    implicit val schema: Schema[BookNotFound] = DeriveSchema.gen
  }

  case class AuthenticationError(message: String, userId: Int) extends AppError(message)

  object AuthenticationError {
    implicit val schema: Schema[AuthenticationError] = DeriveSchema.gen
  }

  object BookRepo {
    def find(id: Int): ZIO[Any, BookNotFound, Book] = {
      if (id == 1)
        ZIO.succeed(Book("Zionomicon", List("John A. De Goes", "Adam Fraser")))
      else
        ZIO.fail(BookNotFound("The requested book was not found.", id))
    }
  }

  val endpoint: Endpoint[Int, (Int, Header.Authorization), AppError, Book, AuthType.None] =
    Endpoint(RoutePattern.GET / "books" / PathCodec.int("id"))
      .header(HeaderCodec.authorization)
      .out[Book]
      .outErrors[AppError](
        HttpCodec.error[BookNotFound](Status.NotFound),
        HttpCodec.error[AuthenticationError](Status.Unauthorized),
      )

  def isUserAuthorized(authHeader: Header.Authorization) = false

  val getBookHandler: Handler[Any, AppError, (Int, Header.Authorization), Book] =
    handler { (id: Int, authHeader: Header.Authorization) =>
      if (isUserAuthorized(authHeader))
        BookRepo.find(id)
      else
        ZIO.fail(AuthenticationError("User is not authenticated", 123))
    }

  val routes = endpoint.implementHandler(getBookHandler).toRoutes @@ Middleware.debug

  def run = Server.serve(routes).provide(Server.default)
}
```
</details>

### Multiple Failure Outputs Using Union Types

The `Endpoint#orOutError` method can be used to describe multiple failure outputs using union types:

```scala
import zio.schema.DeriveSchema

case class Book(title: String, authors: List[String])
implicit val bookSchema = DeriveSchema.gen[Book]

case class BookNotFound(message: String, bookId: Int)
case class AuthenticationError(message: String, userId: Int)

implicit val notFoundSchema = DeriveSchema.gen[BookNotFound]
implicit val authSchema     = DeriveSchema.gen[AuthenticationError]

val endpoint: Endpoint[Int, (Int, Header.Authorization), BookNotFound | AuthenticationError, Book, AuthType.None] =
  Endpoint(RoutePattern.GET / "books" / PathCodec.int("id"))
    .header(HeaderCodec.authorization)
    .out[Book]
    .outError[BookNotFound](Status.NotFound)
    .orOutError[AuthenticationError](Status.Unauthorized)
```

## Transforming Endpoint Input/Output and Error Types

To transform the input, output, and error types of an endpoint, we can use the `Endpoint#transformIn`, `Endpoint#transformOut`, and `Endpoint#transformError` methods, respectively. Let's see an example:

```scala
case class BookQuery(query: String, genre: String, title: String)

val endpoint: Endpoint[String, (String, String, String), ZNothing, ZNothing, AuthType.None] =
  Endpoint(RoutePattern.POST / "books" / PathCodec.string("genre"))
    .query(HttpCodec.query[String]("q"))
    .query(HttpCodec.query[String]("title"))

val mappedEndpoint: Endpoint[String, BookQuery, ZNothing, ZNothing, AuthType.None] =
  endpoint.transformIn[BookQuery] { case (genre, q, title) => BookQuery(q, genre, title) } { i =>
    (i.genre, i.query, i.title)
  }
```

In the above example, we mapped over the input type of the `endpoint` and transformed it into a single `BookQuery` object. The `Endpoint#transformIn` method takes two functions, the first one is used to map the input type to the new input type, and the second one is responsible for mapping the new input type back to the original input type.

The `transformOut` and `transformError` methods work similarly to the `transformIn` method.

## CodecConfig
The `CodecConfig` is injected when building any `Endpoint` API codecs. You can see this in the definition of `BinaryCodecWithSchema`:

```scala
import zio.http.codec._
import zio.schema._
import zio.schema.codec._

case class BinaryCodecWithSchema[A](codecFn: CodecConfig => BinaryCodec[A], schema: Schema[A])
```

By default, it effects only the JSON codecs, but you may use it to configure custom codecs as well.

The configuration options can be changed globally or per set of `Routes`.

To change the config globally, we set a config in the bootstrap of the application:

```scala
import zio.http._
import zio.http.codec._

object MyApp extends ZIOAppDefault {
  override val bootstrap: ZLayer[ZIOAppArgs, Any, Any] =
    CodecConfig.configLayer(CodecConfig(rejectExtraFields = true))

  override def run: ZIO[Any, Throwable, Unit] = ???
}
```

To change the config per set of `Routes`, we can use middleware:

```scala
import zio.http._
import zio.http.codec._

object MyApp extends ZIOAppDefault {
  override def run: ZIO[Any, Throwable, Unit] = {
    val routes: Routes[Any, Nothing] = ???

    val customConfig = CodecConfig(rejectExtraFields = true)
    val customRoutes = routes @@ CodecConfig.withConfig(customConfig)

    Server.serve(customRoutes).provide(Server.default)
  }
}
```
Here is a table of the available options and their default values:

| Config                     | Default Value                                                                                                                                                                                                                                            |
|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `rejectExtraFields`       | `false`<br/><br/>Rejects requests that contain fields that are not part of the data structure that should be encoded.                                                                                                                                    |
| `explicitEmptyCollections` | `ExplicitConfig(encoding = true, decoding = false)`<br/><br/>Collecttions will be encoded as `"fieldName": []` and missing fields will be decoded as empty collections                                                                                        |
| `explicitNulls`         | `ExplicitConfig(encoding = true, decoding = false)`<br/><br/>Null values/Option.None will be encoded as `"fieldName": null` and missing fields will be decoded as `Option.None`                                                                               |
| `discriminatorSettings` | `DiscriminatorSetting.ClassName(NameFormat.Identity)`<br/><br/>Discriminator will be the class name of the sealed trait or enum case. It will be the field name of a singe field object. The value of this field will be the JSON representation of the case. |
| `fieldNameFormat`       | `NameFormat.Identity`<br/><br/>The field name will be the same as the case class field name. There are predefined cases for `SnakeCase`, `CamelCase`, `KebabCase`, and `PascalCase`.                                                                          |
| `treatStreamsAsArrays` | `false`<br/><br/>If set to true, streams will be read into arrays before being encoded.                                                                                                                                                                        |

## OpenAPI Documentation

Every property of an `Endpoint` API can be annotated with documentation, may be examples using methods like `??` and `example*`. We can use these metadata to generate OpenAPI documentation:

```scala
val endpoint =
  Endpoint((RoutePattern.GET / "books") ?? Doc.p("Route for querying books"))
    .query(
      HttpCodec.query[String]("q").examples(("example1", "scala"), ("example2", "zio")) ?? Doc.p(
        "Query parameter for searching books",
      ),
    )
    .out[List[Book]](Doc.p("List of books matching the query")) ?? Doc.p(
    "Endpoint to query books based on a search query",
  )
```

Also, we can use the `@description` annotation from the `zio.schema.annotation` package to annotate data models, which will enrich the OpenAPI documentation:

```scala
import zio.schema.annotation.description

case class Book(
  @description("Title of the book")
  title: String,
  @description("List of the authors of the book")
  authors: List[String],
)
```

The `OpenAPIGen.fromEndpoints` constructor generates OpenAPI documentation from the endpoints. By having the OpenAPI documentation, we can easily generate Swagger UI routes using the `SwaggerUI.routes` constructor:

```scala
val booksRoute = endpoint.implement(query => BookRepo.find(query))
val openAPI    = OpenAPIGen.fromEndpoints(title = "Library API", version = "1.0", endpoint)
val swaggerRoutes = SwaggerUI.routes("docs" / "openapi", openAPI)
val routes     = Routes(booksRoute) ++ swaggerRoutes
```

<details>
<summary><b>Full Implementation Showcase</b></summary>

```scala title="zio-http-example/src/main/scala/example/endpoint/BooksEndpointExample.scala" 
package example.endpoint

import zio._

import zio.schema.annotation.description
import zio.schema.{DeriveSchema, Schema}

import zio.http._
import zio.http.codec.PathCodec._
import zio.http.codec._
import zio.http.endpoint._
import zio.http.endpoint.openapi._

object BooksEndpointExample extends ZIOAppDefault {
  case class Book(
    @description("Title of the book")
    title: String,
    @description("List of the authors of the book")
    authors: List[String],
  )
  object Book {
    implicit val schema: Schema[Book] = DeriveSchema.gen
  }

  object BookRepo {
    val book1 = Book("Programming in Scala", List("Martin Odersky", "Lex Spoon", "Bill Venners", "Frank Sommers"))
    val book2 = Book("Zionomicon", List("John A. De Goes", "Adam Fraser"))
    val book3 = Book("Effect-Oriented Programming", List("Bill Frasure", "Bruce Eckel", "James Ward"))
    def find(q: String): List[Book] = {
      if (q.toLowerCase == "scala") List(book1, book2, book3)
      else if (q.toLowerCase == "zio") List(book2, book3)
      else List.empty
    }
  }

  val endpoint =
    Endpoint((RoutePattern.GET / "books") ?? Doc.p("Route for querying books"))
      .query(
        HttpCodec.query[String]("q").examples(("example1", "scala"), ("example2", "zio")) ?? Doc.p(
          "Query parameter for searching books",
        ),
      )
      .out[List[Book]](Doc.p("List of books matching the query")) ?? Doc.p(
      "Endpoint to query books based on a search query",
    )

  val booksRoute    = endpoint.implementHandler(handler((query: String) => BookRepo.find(query)))
  val openAPI       = OpenAPIGen.fromEndpoints(title = "Library API", version = "1.0", endpoint)
  val swaggerRoutes = SwaggerUI.routes("docs" / "openapi", openAPI)
  val routes        = Routes(booksRoute) ++ swaggerRoutes

  def run = Server.serve(routes).provide(Server.default)
}
```
</details>

## Generating Endpoint from OpenAPI Spec

With ZIO HTTP, we can generate endpoints from an OpenAPI specification. To do this, first, we need to add the following line to the `build.sbt` file:

```scala
libraryDependencies += "dev.zio" %% "zio-http-gen" % "3.3.3"
```

Then we can generate the endpoints from the OpenAPI specification using the `EndpointGen.fromOpenAPI` constructor:

```scala title="zio-http-example/src/main/scala/example/endpoint/GenerateEndpointFromOpenAPIExample.scala" 
package example.endpoint

import java.nio.file._

import zio.http.endpoint.openapi.OpenAPI
import zio.http.gen.openapi.EndpointGen
import zio.http.gen.scala.CodeGen

object GenerateEndpointFromOpenAPIExample extends App {
  val userOpenAPI = OpenAPI.fromJson(
    """|{
       |  "openapi": "3.0.0",
       |  "info": {
       |    "title": "User API",
       |    "version": "1.0.0"
       |  },
       |  "paths": {
       |    "/users/{userId}": {
       |      "get": {
       |        "parameters": [
       |          {
       |            "name": "userId",
       |            "in": "path",
       |            "required": true,
       |            "schema": {
       |              "type": "integer"
       |            }
       |          }
       |        ],
       |        "responses": {
       |          "200": {
       |            "content": {
       |              "application/json": {
       |                "schema": {
       |                  "type": "object",
       |                  "properties": {
       |                    "userId": {
       |                      "type": "integer"
       |                    },
       |                    "username": {
       |                      "type": "string"
       |                    }
       |                  }
       |                }
       |              }
       |            }
       |          },
       |          "404": {
       |            "description": "User not found"
       |          }
       |        }
       |      }
       |    }
       |  }
       |}
       |""".stripMargin,
  )

  CodeGen.writeFiles(
    EndpointGen.fromOpenAPI(userOpenAPI.toOption.get),
    basePath = Paths.get("./users/src/main/scala"),
    basePackage = "org.example",
    scalafmtPath = None,
  )
}
```

## Generating ZIO CLI App from Endpoint API

The ZIO CLI is a ZIO library that provides a way to build command-line applications using ZIO facilities. With ZIO HTTP, we can generate a ZIO CLI client from the `Endpoint` API.

To do this, first, we need to add the following line to the `build.sbt` file:

```scala
libraryDependencies += "dev.zio" %% "zio-http-cli" % "3.3.3"
```

Then we can generate the ZIO CLI client from the `Endpoint` API using the `HttpCliApp.fromEndpoints` constructor:

```scala
object TestCliApp extends zio.cli.ZIOCliDefault with TestCliEndpoints {
  val cliApp =
    HttpCliApp
      .fromEndpoints(
        name = "users-mgmt",
        version = "0.0.1",
        summary = HelpDoc.Span.text("Users management CLI"),
        footer = HelpDoc.p("Copyright 2023"),
        host = "localhost",
        port = 8080,
        endpoints = Chunk(getUser, getUserPosts, createUser),
        cliStyle = true,
      )
      .cliApp
}
```

Using the above code, we can create the `users-mgmt` CLI application that can be used to interact with the `getUser`, `getUserPosts`, and `createUser` endpoints:

```shell
                                                             __ 
  __  __________  __________      ____ ___  ____ _____ ___  / /_
 / / / / ___/ _ \/ ___/ ___/_____/ __ `__ \/ __ `/ __ `__ \/ __/
/ /_/ (__  )  __/ /  (__  )_____/ / / / / / /_/ / / / / / / /_  
\__,_/____/\___/_/  /____/     /_/ /_/ /_/\__, /_/ /_/ /_/\__/  
                                         /____/                 

users-mgmt v0.0.1 -- Users management CLI

USAGE

  $ users-mgmt <command>

COMMANDS

  - get-users --userId integer --location text                               Get a user by ID

  - get-users-posts --postId integer --userId integer --user-name text       Get a user's posts by userId and postId

  - create-users -f file|-u text|--.id integer --.name text [--.email text]  Create a new user

Copyright 2023
```

<details>
<summary><b>Full Implementation Showcase</b></summary>

```scala title="zio-http-example/src/main/scala/example/endpoint/CliExamples.scala" 
package example.endpoint

import zio._
import zio.cli._

import zio.schema._
import zio.schema.annotation.description

import zio.http.Header.Location
import zio.http._
import zio.http.codec._
import zio.http.endpoint.cli._
import zio.http.endpoint.{Endpoint, EndpointExecutor}

final case class User(
  @description("The unique identifier of the User")
  id: Int,
  @description("The user's name")
  name: String,
  @description("The user's email")
  email: Option[String],
)
object User {
  implicit val schema: Schema[User] = DeriveSchema.gen[User]
}
final case class Post(
  @description("The unique identifier of the User")
  userId: Int,
  @description("The unique identifier of the Post")
  postId: Int,
  @description("The post's contents")
  contents: String,
)
object Post {
  implicit val schema: Schema[Post] = DeriveSchema.gen[Post]
}

trait TestCliEndpoints {

  val getUser =
    Endpoint(Method.GET / "users" / int("userId") ?? Doc.p("The unique identifier of the user"))
      .header(HeaderCodec.location ?? Doc.p("The user's location"))
      .out[User] ?? Doc.p("Get a user by ID")

  val getUserPosts =
    Endpoint(
      Method.GET /
        "users" / int("userId") ?? Doc.p("The unique identifier of the user") /
        "posts" / int("postId") ?? Doc.p("The unique identifier of the post"),
    )
      .query(
        HttpCodec.query[String]("user-name") ?? Doc.p(
          "The user's name",
        ),
      )
      .out[List[Post]] ?? Doc.p("Get a user's posts by userId and postId")

  val createUser =
    Endpoint(Method.POST / "users")
      .in[User]
      .out[String] ?? Doc.p("Create a new user")
}

object TestCliApp extends zio.cli.ZIOCliDefault with TestCliEndpoints {
  val cliApp =
    HttpCliApp
      .fromEndpoints(
        name = "users-mgmt",
        version = "0.0.1",
        summary = HelpDoc.Span.text("Users management CLI"),
        footer = HelpDoc.p("Copyright 2023"),
        host = "localhost",
        port = 8080,
        endpoints = Chunk(getUser, getUserPosts, createUser),
        cliStyle = true,
      )
      .cliApp
}

object TestCliServer extends zio.ZIOAppDefault with TestCliEndpoints {
  val getUserRoute =
    getUser.implementHandler {
      Handler.fromFunctionZIO { case (id, _) =>
        ZIO.succeed(User(id, "Juanito", Some("juanito@test.com"))).debug("Hello")
      }
    }

  val getUserPostsRoute =
    getUserPosts.implementHandler {
      Handler.fromFunction { case (userId, postId, name) =>
        List(Post(userId, postId, name))
      }
    }

  val createUserRoute =
    createUser.implementHandler {
      Handler.fromFunction { user =>
        user.name
      }
    }

  val routes = Routes(getUserRoute, getUserPostsRoute, createUserRoute) @@ Middleware.debug

  val run = Server.serve(routes).provide(Server.default)
}

object TestCliClient extends zio.ZIOAppDefault with TestCliEndpoints {
  val run =
    clientExample
      .provide(
        EndpointExecutor.make(serviceName = "test"),
        Client.default,
      )

  def clientExample: URIO[EndpointExecutor[Any, Unit, Scope], Unit] =
    for {
      executor <- ZIO.service[EndpointExecutor[Any, Unit, Scope]]
      _        <- ZIO.scoped(executor(getUser(42, Location.parse("some-location").toOption.get))).debug("result1")
      _        <- ZIO.scoped(executor(getUserPosts(42, 200, "adam")).debug("result2"))
      _        <- ZIO.scoped(executor(createUser(User(2, "john", Some("john@test.com"))))).debug("result3")
    } yield ()

}
```

</details>
