-
Notifications
You must be signed in to change notification settings - Fork 133
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
333 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
modules/microsite/docs/scala/http4s/generating-a-server.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
--- | ||
layout: docs | ||
title: "Generating a Server - http4s - scala - guardrail" | ||
--- | ||
|
||
Generating a Server | ||
=================== | ||
|
||
guardrail-generated servers come in two parts: a `Resource` and a `Handler`. The `Resource` contains all the routing logic, accepting a `Handler` as an argument to the `route` function in order to provide an HTTP service in whichever supported HTTP framework you're hosting your service in. | ||
|
||
The following is an example from the [http4s](https://github.com/http4s/http4s) server generator: | ||
|
||
```scala mdoc:passthrough | ||
import com.twilio.guardrail.generators.Scala.Http4s | ||
import com.twilio.guardrail.docs._ | ||
DocsHelpers.renderScalaSnippet(Http4s, GeneratingAServer)(""" | ||
|// The `Handler` trait is fully abstracted from the underlying http framework. As a result, with the exception of some | ||
|// structural alterations (`F[_]` instead of `Future[_]` as the return type) the same handlers can be used with | ||
|// different `Resource` implementations from different framework generators. This permits greater compatibility between | ||
|// different frameworks without changing your business logic. | ||
""".stripMargin, | ||
"" | ||
) | ||
``` | ||
|
||
As all parameters are provided as arguments to the function stubs in the trait, there's no concern of forgetting to extract a query string parameter, introducing a typo in a form parameter name, or forgetting to close the bytestream for the streaming HTTP Request. | ||
|
||
The routes and resources generated by guardrail can be hooked up into your HTTP4s server like so: | ||
|
||
```scala | ||
val usersApi = new UsersApi() // Class which implements the generated UsersHandler from guardrail | ||
val usersService = new UsersResource[IO]().routes(usersApi) | ||
val httpApp = Router("/" -> usersService).orNotFound | ||
|
||
// Same basic server setup as in the http4s quickstart | ||
BlazeServerBuilder[IO] | ||
.bindHttp(5000, "localhost") | ||
.withHttpApp(httpApp) | ||
.resource | ||
.use(_ => IO.never) | ||
.as(ExitCode.Success) | ||
``` | ||
|
||
Separation of business logic | ||
---------------------------- | ||
|
||
Providing an implementation of a function with a well-defined set of inputs and outputs is natural for any developer. By reducing the scope of the interface a developer writes against, implementations are more clear and concise. | ||
|
||
Furthermore, by providing business logic as an implementation of an abstract class, unit tests can test the routing layer and business logic independently, by design. | ||
|
||
API structure slip is impossible | ||
-------------------------------- | ||
|
||
As parameters are explicitly provided as arguments to functions in `Handler`s, any alteration to parameters constitute a new function interface that must be implemented. As a result, if providing an implementation for an externally managed specification, the compiler informs when a previously written function is no longer sufficient. | ||
|
||
By representing different response codes and structures as members of a sealed trait, it's impossible to return a structure that violates the specification, even for less frequently used response codes. | ||
|
||
Finally, describing an endpoint in your specification without providing an implementation for it is a compiler error. This prevents reduction of functionality due to refactors, human error, or miscommunication with other teams. | ||
|
||
<span style="float: left">[Prev: Sample API specification](sample-api-specification)</span> | ||
<span style="float: right">[Next: Generating clients](generating-clients)</span> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
--- | ||
layout: docs | ||
title: "Generating Clients - http4s - scala - guardrail" | ||
--- | ||
|
||
Generating clients | ||
================== | ||
|
||
As we've seen in [Generating a Server](generating-a-server), guardrail-generated servers establish a mapping between our business logic and a cordoned off subset of HTTP. This permits us to focus on our business logic, without getting overloaded with the complexities of managing such a large protocol. The same is true with guardrail generated HTTP Clients: from a consumer's standpoint, HTTP calls should look like regular function calls, accepting domain-specific arguments and producing domain-specific results. | ||
|
||
By generating minimal clients that only have enough business knowledge to map domain types to and from HTTP, opportunities for logical errors are effectively removed. While this does not eliminate logical errors entirely, establishing a firm boundary between the underlying protocol and hand-written code drastically reduces the scope of possible bugs. | ||
|
||
The following is an example from the [http4s](https://github.com/http4s/http4s) client generator: | ||
|
||
```scala mdoc:passthrough | ||
import com.twilio.guardrail.generators.Scala.Http4s | ||
import com.twilio.guardrail.docs._ | ||
DocsHelpers.renderScalaSnippet(Http4s, GeneratingClients)( | ||
"""|// Two constructors are provided, one accepting the `httpClient` and `Async` | ||
|// implicitly, the other accepting an explicit `httpClient`, but still | ||
|// accepting the `Async` implicitly | ||
""".stripMargin, | ||
"" | ||
) | ||
``` | ||
|
||
Separation of protocol-concerns from API-level concerns | ||
------------------------------------------------------- | ||
|
||
As guardrail clients are built on top of any Http4s client type, client configuration is done the same way as you are | ||
already familiar with when using Http4s. | ||
|
||
Check out the docs for [Http4s Clients](https://http4s.org/v0.20/client/). | ||
|
||
<span style="float: left">[Prev: Generating a Server](generating-a-server)</span> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,22 @@ | ||
--- | ||
layout: docs | ||
title: "guardrail[http4s <- scala]" | ||
title: "http4s - scala - guardrail" | ||
--- | ||
|
||
These docs are under construction! | ||
Table of Contents | ||
================= | ||
|
||
In the meantime, please refer to the [akka-http](../akka-http) docs for broad context and getting started. | ||
|
||
Note that the `framework` for http4s is `"http4s"`, so using that in your SBT, Gradle, or Maven plugin configuration should get you started. | ||
1. [What is guardrail](what-is-guardrail.md) | ||
1. [Single Point of Truth](what-is-guardrail.md#single-point-of-truth) | ||
1. [Unexpected API changes are compiler errors](what-is-guardrail.md#unexpected-api-changes-are-compiler-errors) | ||
1. [Fewer binary dependencies](what-is-guardrail.md#fewer-binary-dependencies) | ||
1. [Installation](installation) | ||
1. [Sample API specification](sample-api-specification) | ||
1. [Generating a Server](generating-a-server) | ||
1. [Separation of business logic](generating-a-server#separation-of-business-logic) | ||
1. [API structure slip is impossible](generating-a-server#api-structure-slip-is-impossible) | ||
1. [Generating test-only (real) server mocks for unit tests](generating-a-server#generating-test-only-real-server-mocks-for-unit-tests) | ||
1. [A note about scalatest integration](generating-a-server#a-note-about-scalatest-integration) | ||
1. [Generating clients](generating-clients) | ||
1. [Separation of protocol-concerns from API-level concerns](generating-clients#separation-of-protocol-concerns-from-api-level-concerns) | ||
1. [guardrail Extensions](guardrail-extensions) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
--- | ||
layout: docs | ||
title: "Installation - http4s - scala - guardrail" | ||
--- | ||
|
||
Installation | ||
============ | ||
|
||
guardrail is available as a modular core, with both [sbt](https://github.com/twilio/sbt-guardrail) and [Maven](https://github.com/twilio/guardrail-maven-plugin) integration. The core can also be run as a stand-alone [CLI](https://github.com/twilio/guardrail/blob/978a92db3dd46812aa19f05050995f864cbb5bb3/build.sbt#L33-L48) application, with full support for all features. | ||
|
||
To generate servers or clients using the `http4s` framework, set `http4s` as the framework in the generation configuration in either sbt or maven. | ||
|
||
If compiling with Scala < 2.13.x, you'll need to enable `-Ypartial-unification`: | ||
|
||
```scala | ||
scalacOptions += "-Ypartial-unification" | ||
``` | ||
|
||
If compiling with Scala < 2.12.x, you'll additionally need the `-Xexperimental` flag: | ||
|
||
```scala | ||
scalacOptions += "-Xexperimental" | ||
``` | ||
|
||
Additionally, you will need to manually include dependencies in your project for the following packages: | ||
- `http4s`, dsl, server, and client dependencies | ||
- `http4s-circe` for JSON decoding and encoding support | ||
- `circe-generic` for JSON decoding and encoding support | ||
- `cats-effect` for http4s integration | ||
- `cats-core` for http4s integration | ||
|
||
Versions of these libraries should be picked by checking out the [Compatibility Matrix](https://github.com/guardrail-dev/guardrail/blob/master/COMPATIBILITY.md). | ||
|
||
<span style="float: left">[Prev: What is guardrail?](what-is-guardrail)</span> | ||
<span style="float: right">[Next: Sample API specification](sample-api-specification)</span> |
131 changes: 131 additions & 0 deletions
131
modules/microsite/docs/scala/http4s/sample-api-specification.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
--- | ||
layout: docs | ||
title: "Sample API Specification - http4s - scala - guardrail" | ||
--- | ||
|
||
Sample API specification | ||
======================== | ||
|
||
The following is a complete, annotated OpenAPI specification file: (guardrail extensions are documented in [guardrail Extensions](#guardrail-extensions)) | ||
|
||
```yaml | ||
swagger: "2.0" # Which version of the OpenAPI/Swagger specification we are following | ||
info: # Primarily for consumption by documentation generation tools | ||
title: My Service | ||
version: 0.1.0 | ||
host: localhost:1234 # Default host (and optional port) to connect to for generated clients | ||
schemes: | ||
- http | ||
paths: # All HTTP paths are direct children of the `paths` field | ||
|
||
/user/{id}: # Paths can have variable patterns in paths | ||
|
||
get: # HTTP method | ||
|
||
operationId: getUser # Friendly name, ends up as the function name (in clients and servers) | ||
|
||
x-jvm-package: users # Relative package for this client to live in. For convenience, the | ||
# last package parameter is turned into a class name for clients and | ||
# servers. In this case, `UsersClient`. | ||
|
||
parameters: # All parameters (including path parameters) are listed here. | ||
|
||
- name: id # The field name (case matters!), used to both identify the correct | ||
# field to match, as well as generate a best-guess idiomatic Scala | ||
# parameter name. | ||
|
||
in: path # Where to look for the parameter | ||
|
||
description: The ID of the user # The optional `description` parameter is not used in guardrail, | ||
# but is useful for providing a detailed explanation on what is | ||
# expected as a value for the parameter. For example: | ||
# `description: User IDs are strings comprised of the concatenation | ||
# of the two upper-case letters ID and a UUID stripped of any dashes | ||
# i.e. ID4d9b1c54e4664c9d92aba94151a7f59f` | ||
|
||
required: true # Required fields cannot be missing. `required: false` fields are | ||
# represented as `Option[T]` | ||
|
||
type: string # One of the primitive types supported in the OpenAPI specification. | ||
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#dataTypes | ||
|
||
x-scala-type: CustomString # Escape hatch to explicitly introduce a custom type. This is an | ||
# advanced technique to introduce completely custom | ||
# marshalling/unmarshalling/validation logic. Keep in mind, everyone | ||
# else will just see a plain string! | ||
|
||
responses: # All response codes that are possible are listed here | ||
|
||
200: # Each HTTP status code is mapped to the corresponding textual | ||
# representation in guardrail-generated servers. | ||
|
||
schema: # The optional `schema` parameter describes what's possible to return | ||
# as the body of a response | ||
|
||
$ref: '#/definitions/User' # In the generated `UsersHandler` `getUser` function, we can call | ||
# `respond.OK(user)`, where `user: definitions.User` | ||
|
||
404: # We must represent our failure cases as well, otherwise we can | ||
# never express failure! | ||
|
||
description: Not found # The optional `description` parameter is not used in guardrail, | ||
# but is useful here as an indicator that we don't have a response | ||
# body for `404 Not Found` responses. | ||
|
||
definitions: # All non-primitive structures are defined inside `definitions` | ||
|
||
User: # This identifies a symbolic structure name. Not all names are | ||
# translated into classes when rendered, depending on whether they | ||
# identify classes with structure, or defer to standard classes | ||
# like `Vector` for `type: array`. | ||
|
||
type: object # will generate a `User` case class in the `definitions` package | ||
|
||
required: # A list of which parameters are required. This is enforced for | ||
# clients by having non-optional parameters, and for servers by | ||
# ensuring all submitted data to the endpoint validates the schema | ||
# before getting to your `Handler` function. | ||
|
||
- id # These names must match the `properties` names exactly | ||
- user_addresses | ||
|
||
properties: # `object`s are permitted to have `properties`. These are translated | ||
# into fields in the generated case classes. | ||
|
||
id: # Case matters for `properties`! A heuristic determines whether it's | ||
# possible to translate a property name into a unique, non-reserved | ||
# camelCase identifier. | ||
|
||
type: string # One of the primitive types supported in the OpenAPI specification. | ||
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.1.md#dataTypes | ||
|
||
user_addresses: # Similar to `id`, though `user_addresses` can be safely transformed into | ||
# `userAddress`, so this is done to expose idiomatic Scala. The underlying | ||
# marshallers and unmarshallers maintain this mapping for you though, | ||
# so no chance of protocol violations. | ||
|
||
$ref: '#/definitions/UserAddresses' # Ensures that the type of `userAddress` will be `Vector[UserAddress]` | ||
|
||
UserAddresses: | ||
type: array | ||
|
||
items: # `items` is a special key for `type: array`, identifying the structure of the | ||
# sequence members | ||
|
||
$ref: '#/definitions/UserAddress' # Primitive types could be listed here, but as we're referring to another class, | ||
# we need to explicitly use a `$ref`. This may change in the future, | ||
# see https://github.com/twilio/guardrail/issues/76 | ||
|
||
UserAddress: | ||
type: object | ||
properties: | ||
line1: | ||
type: string | ||
line2: | ||
type: string | ||
line3: | ||
type: string | ||
``` | ||
<span style="float: left">[Prev: Installation](installation)</span> | ||
<span style="float: right">[Next: Generating a Server](generating-a-server)</span> |
Oops, something went wrong.