SkillAgentSearch skills...

Swakka

A Scala library for creating Swagger definitions in a type-safe fashion wth Akka-Http

Install / Use

/learn @jtownson/Swakka
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Swakka - Swagger for Akka-Http

Quickstart

libraryDependencies += "net.jtownson" %% "swakka" % "0.51"

Latest version

Swakka is

  1. A Scala library for creating Swagger definitions in a type-safe fashion.
  2. Swagger support for Akka Http.

Swakka is not

  1. A web runtime. Akka Http is a web runtime and Swakka is a layer above that. Swakka generates Swagger JSON and provides Akka Http Routes to (a) serve that JSON and (b) support the API in the Swagger definition. It adds to Akka Http and dovetails cleanly with Akka Http concepts.

Here's how it works...

Swakka in five key points:

// Some akka imports ...	

// Some Swakka imports
import net.jtownson.swakka.openapimodel._
import net.jtownson.swakka.openapijson._
import net.jtownson.swakka.coreroutegen._
import net.jtownson.swakka.openapiroutegen._
	
object Greeter1 extends App {

  implicit val system = ActorSystem()
  implicit val mat = ActorMaterializer()
  implicit val executionContext = system.dispatcher

  val corsHeaders = Seq(
    RawHeader("Access-Control-Allow-Origin", "*"),
    RawHeader("Access-Control-Allow-Methods", "GET"))

  // (1) - Create a swagger-like API structure using an OpenApi case class.
  // Implement each endpoint as an Akka _Route_

  val greet: String => Route =
    name =>
      complete(HttpResponse(OK, corsHeaders, s"Hello $name!"))

  val api =
    OpenApi(
      produces = Some(Seq("text/plain")),
      paths =
      PathItem(
        path = "/greet",
        method = GET,
        operation = Operation(
          parameters = Tuple1(QueryParameter[String]('name)),
          responses = ResponseValue[String]("200", "ok"),
          endpointImplementation = greet
        )
      )
    )

  // (2) - Swakka will generate 
  //       a) a Route for the API. 
  //          This extracts the paths, parameters, headers, etc in your swagger definition 
  //          and passes them to your implementation.
  //       b) a swagger.json. This is added to the API route above.
  val route: Route = openApiRoute(
    api,
    docRouteSettings = Some(SwaggerRouteSettings()))

  val bindingFuture = Http().bindAndHandle(
    route,
    "localhost",
    8080)
}
jtownson@munch ~$ # (3) Your callers can then get the swagger file
jtownson@munch ~$ curl -i localhost:8080/swagger.json
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Server: akka-http/10.0.5
Date: Thu, 16 Nov 2017 21:02:53 GMT
Content-Type: application/json
Content-Length: 476

{
  "swagger": "2.0",
  "info": {
    "title": "",
    "version": ""
  },
  "produces": ["text/plain"],
  "paths": {
    "/greet": {
      "get": {
        "parameters": [{
          "name": "name",
          "in": "query",
          "required": true,
          "type": "string"
        }],
        "responses": {
          "200": {
            "description": "ok",
            "schema": {
              "type": "string"
            }
          }
        }
      }
    }
  }
}

jtownson@munch ~$ # (4) and call the API
jtownson@munch ~$ curl -i localhost:8080/greet?name=you
HTTP/1.1 200 OK
Server: akka-http/10.0.5
Date: Thu, 16 Nov 2017 21:04:59 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 10

Hello you!

jtownson@munch ~$ # (5) With the generated route directives matching the
jtownson@munch ~$ #     host, paths, parameters, etc of your swagger API definition,
jtownson@munch ~$ #     you can be sure that requests reaching your endpoing are valid.
jtownson@munch ~$ curl -i localhost:8080/greet
HTTP/1.1 404 Not Found
Server: akka-http/10.0.5
Date: Thu, 16 Nov 2017 21:06:43 GMT
Content-Type: text/plain; charset=UTF-8
Content-Length: 50

Request is missing required query parameter 'name'

Parameters:

The example above took a single QueryParameter[String]. There are in fact

  • QueryParameter[T]
  • PathParameter[T]
  • HeaderParameter[T]
  • BodyParameter[T]
  • MultiValued[T, Parameter[T]] this is a wrapper for handling multiple valued query params.

In OpenApi, the set of parameters for an endpoint is given as a JSON array. In Swakka, you use any Product type. Specifically, you can use

  • a scala tuple
parameters =
 (
      PathParameter[Long](
        name = 'petId,
        description = Some("ID of pet to update")
      ),
      FormFieldParameter[Option[String]](
        name = 'additionalMetadata,
        description = Some("Additional data to pass to server")
      ),
      FormFieldParameter[Option[(FileInfo,
                                 Source[ByteString, Any])]](
        name = 'file,
        description = Some("file to upload")
      )
  )
  • a case class
case class Parameters(
    petId: PathParameter[Long], 
    additionalMetadata: FormFieldParameter[Option[String]],
    file: FormFieldParameter[Option[(FileInfo, Source[ByteString, Any])]])

// ...

parameters = Parameters(
  PathParameter[Long](
    name = 'petId,
    description = Some("ID of pet to update")
  ),
  FormFieldParameter[Option[String]](
    name = 'additionalMetadata,
    description = Some("Additional data to pass to server")
  ),
  FormFieldParameter[Option[(FileInfo,
                             Source[ByteString, Any])]](
    name = 'file,
    description = Some("file to upload")
  ))
  • a shapeless HList
import shapeless.{HNil, ::}
// ...
parameters = 
  PathParameter[Long](
    name = 'petId,
    description = Some("ID of pet to update")
        )
  ::
  FormFieldParameter[Option[String]](
    name = 'additionalMetadata,
    description = Some("Additional data to pass to server")
  )
  ::
  FormFieldParameter[Option[(FileInfo,
                             Source[ByteString, Any])]](
    name = 'file,
    description = Some("file to upload")
  )
  ::
        HNil

The parameters Product type of each endpoint in your API defines a Params type parameter. See net.jtownson.swakka.openapimodel.Operation.

For each of your swagger endpoints, you provide an endpoint implementation function to handle the request. The function type of this endpoint implementation is dependent on the Params type definition. If for instance,

Params = (QueryParameter[Boolean], PathParameter[String], HeaderParameter[Long])

then the endpoint implementation will have a dependent function type of (Boolean, String, Long) => Route

Swakka reads the Params defined in your API and generates Akka-Http Routes that extract those Params. It passes those params to your endpoint implementation. The Route returned from your endpoint implementation is then a nested, inner Route which completes the response.

Optional Parameters

If a parameter in your API is optional then declare it using scala's Option, a la:

  "optional query parameters when missing" should "not cause request rejections" in {

    // Our query parameter is Option[Int].
    val f: Option[Int] => Route =
      iOption => complete(iOption map (_ => "Something") getOrElse "None")

    val api = OpenApi(paths =
      PathItem(
        path = "/app/e1",
        method = GET,
        operation = Operation(
          parameters = QueryParameter[Option[Int]]('q) :: HNil,
          responses = Response[String]("200", "ok"),
          endpointImplementation = f)))

    val route = openApiRoute(api)

    // The q parameter is missing from the request
    // Our endpoint implementation will be called with q=None
    Get("http://localhost:8080/app/e1") ~> seal(route) ~> check {
      status shouldBe OK
      responseAs[String] shouldBe "None"
    }
    
    // The caller provides the value "Something" for q
    // The endpoint implementation will get Some("Something")
    Get("http://localhost:8080/app/e1?q=Something") ~> seal(route) ~> check {
      status shouldBe OK
      responseAs[String] shouldBe "Something"
    }
  }

For a mandatory parameter such as QueryParameter[Int]('q)

the generated swagger will list that parameter as required=true:

        "parameters": [{
          "name": "q",
          "in": "query",
          "required": true,
          "type": "integer"
          "format": "int32"
        }],

For an optional parameter such as QueryParameter[Option[Int]]('q)

the generated swagger will be identical except that the parameter will have required=false:

        "parameters": [{
          "name": "q",
          "in": "query",
          "required": false,
          "type": "integer"
          "format": "int32"
        }],

Note, PathParameter does not support Optional values since Swagger/OpenAPI does not. If you have a case where a URL makes sense both with and without some part of the path, you should define two endpoints.

Constrained parameters

JsonSchema provides a fairly wide array of validation constraints. For example, it allows you to specify that an int parameter must be >0 or that a string parameter must match a regex. (All the options are here http://json-schema.org/latest/json-schema-validation.html#rfc.section.6.2.3).

By extension OpenApi allows such constraints on parameter definitions.

To support this, Swakka provides an additional set of types called

  • QueryParameterConstrained[T, U]
  • PathParameterConstrained[T, U]
  • FormFieldParameterConstrained[T, U]
  • HeaderParameterConstrained[T, U]

Here, T is the type of the parameter itself. U refers to the type of the constraint. So, for example

QueryParameterConstrained[Option[String], String](
    name = 'state,
    default = Some("open"),
    constraints = Constraints(enum = Some(Set("open", "closed"))))

This code can be read as:

  • This is an optional, string query parameter, called state. Type T = Option[String]
  • If the state param is missing from the request, there is a default value: open

Related Skills

View on GitHub
GitHub Stars72
CategoryDevelopment
Updated8d ago
Forks5

Languages

Scala

Security Score

85/100

Audited on Mar 27, 2026

No findings