Skip to main content

DSL Reference

The Baklava DSL provides a comprehensive, tree-structured API for documenting and testing HTTP endpoints. This reference covers all DSL elements and their attributes.

DSL Tree Structure

The Baklava DSL follows a hierarchical tree structure:

path (root)
└── supports (HTTP method)
└── onRequest (test case)
├── respondsWith (expected response)
└── assert (test assertions)

Each level in the tree defines specific aspects of your API endpoint and its behavior.

Complete Example

Here's a comprehensive example demonstrating all major DSL features:

path(
path = "/users/{userId}",
description = "User management endpoints",
summary = "Manage user resources"
)(
supports(
method = GET,
pathParameters = p[Long]("userId", "The unique user identifier"),
queryParameters = (
q[Option[String]]("fields", "Comma-separated list of fields to include"),
q[Option[Int]]("version", "API version number")
),
headers = h[String]("X-Request-ID", "Unique request identifier"),
securitySchemes = Seq(
SecurityScheme("bearerAuth", HttpBearer())
),
description = "Retrieve a user by ID",
summary = "Get user",
operationId = "getUser",
tags = List("Users", "Read Operations")
)(
onRequest(
pathParameters = (1L),
queryParameters = (Some("name,email"), None),
headers = ("req-123"),
security = HttpBearer()("my-token")
)
.respondsWith[User](
statusCode = OK,
headers = Seq(
h[String]("X-Response-ID", "Response identifier"),
h[String]("X-Rate-Limit", "Rate limit information")
),
description = "Successfully retrieved user",
strictHeaderCheck = false
)
.assert { ctx =>
val response = ctx.performRequest(allRoutes)

response.body.id shouldBe 1L
response.body.name should not be empty
},

onRequest(
pathParameters = (999L)
)
.respondsWith[ErrorResponse](
statusCode = NotFound,
description = "User not found"
)
.assert { ctx =>
val response = ctx.performRequest(allRoutes)

response.body.code shouldBe "USER_NOT_FOUND"
}
),

supports(
method = POST,
description = "Create a new user",
summary = "Create user",
tags = List("Users", "Write Operations")
)(
onRequest(
body = CreateUserRequest("John Doe", "john@example.com")
)
.respondsWith[User](
statusCode = Created,
description = "User created successfully"
)
.assert { ctx =>
val response = ctx.performRequest(allRoutes)

response.body.name shouldBe "John Doe"
response.body.email shouldBe "john@example.com"
}
)
)

Root Element: path

The path() function is the root element of the DSL tree. It defines an API endpoint path and contains one or more HTTP method definitions.

Signature

def path(
path: String,
description: String = "",
summary: String = ""
)(
steps: BaklavaMethodDefinition*
): TestFrameworkFragmentsType

Parameters

ParameterTypeRequiredDescription
pathStringYesThe URL path pattern, supporting path parameters in {paramName} format (e.g., /users/{userId})
descriptionStringNoDetailed description of the path and its purpose
summaryStringNoBrief summary of the path
stepsBaklavaMethodDefinition*YesOne or more supports() definitions for HTTP methods

Example

path(
path = "/api/v1/products/{productId}",
description = "Product resource endpoints for CRUD operations",
summary = "Product management"
)(
supports(GET, ...),
supports(PUT, ...),
supports(DELETE, ...)
)

HTTP Method Element: supports

The supports() function defines an HTTP method operation on a path. It specifies the method, parameters, security, and contains test cases.

Signature

def supports[PathParameters, QueryParameters, Headers](
method: BaklavaHttpMethod,
securitySchemes: Seq[SecurityScheme] = Seq.empty,
pathParameters: PathParameters = (),
queryParameters: QueryParameters = (),
headers: Headers = (),
description: String = "",
summary: String = "",
operationId: String = "",
tags: Seq[String] = Seq.empty
)(
steps: BaklavaIntermediateTestCase[PathParameters, QueryParameters, Headers]*
): BaklavaMethodDefinition

Parameters

ParameterTypeRequiredDescription
methodBaklavaHttpMethodYesHTTP method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS)
securitySchemesSeq[SecurityScheme]NoSecurity schemes available for this operation (see Security)
pathParametersPathParametersNoPath parameter definitions using p[T]()
queryParametersQueryParametersNoQuery parameter definitions using q[T]()
headersHeadersNoHeader definitions using h[T]()
descriptionStringNoDetailed operation description
summaryStringNoBrief operation summary
operationIdStringNoUnique identifier for the operation
tagsSeq[String]NoTags for grouping operations in documentation
stepsBaklavaIntermediateTestCase*YesOne or more test cases using onRequest()

Example

supports(
method = GET,
pathParameters = p[String]("userId", "User identifier"),
queryParameters = (
q[Option[Int]]("limit", "Maximum number of results"),
q[Option[Int]]("offset", "Pagination offset")
),
headers = h[String]("X-API-Key", "API authentication key"),
securitySchemes = Seq(
SecurityScheme("apiKey", ApiKeyInHeader("X-API-Key"))
),
description = "Retrieve user details with optional pagination",
summary = "Get user",
operationId = "getUserById",
tags = List("Users", "Public API")
)(
// test cases...
)

Test Case Element: onRequest

The onRequest() function defines a specific test case for an HTTP operation. It specifies the request parameters and must be followed by respondsWith() and assert().

Signature

def onRequest[RequestBody, PathParametersProvided, QueryParametersProvided, HeadersProvided](
body: RequestBody = EmptyBodyInstance,
security: AppliedSecurity = AppliedSecurity(NoopSecurity, Map.empty),
pathParameters: PathParametersProvided = (),
queryParameters: QueryParametersProvided = (),
headers: HeadersProvided = ()
): OnRequest[RequestBody, PathParametersProvided, QueryParametersProvided, HeadersProvided]

Parameters

ParameterTypeRequiredDescription
bodyRequestBodyNoRequest body (defaults to empty). Can be a case class, FormOf[T] for form data, or EmptyBodyInstance
securityAppliedSecurityNoApplied security credentials (see Security)
pathParametersPathParametersProvidedNoActual path parameter values matching the definition
queryParametersQueryParametersProvidedNoActual query parameter values matching the definition
headersHeadersProvidedNoActual header values matching the definition

Example

onRequest(
body = CreateUserRequest("Alice", "alice@example.com"),
pathParameters = (123L),
queryParameters = (Some(10), Some(0)),
headers = ("api-key-value"),
security = HttpBearer()("my-jwt-token")
)

Response Definition: respondsWith

The respondsWith() method defines the expected HTTP response for a test case.

Signature

def respondsWith[ResponseBody](
statusCode: BaklavaHttpStatus,
headers: Seq[Header[?]] = Seq.empty,
description: String = "",
strictHeaderCheck: Boolean = strictHeaderCheckDefault
): BaklavaTestCase[RequestBody, ResponseBody, PathParametersProvided, QueryParametersProvided, HeadersProvided]

Parameters

ParameterTypeRequiredDescription
statusCodeBaklavaHttpStatusYesExpected HTTP status code (OK, Created, NotFound, etc.)
headersSeq[Header[?]]NoExpected response headers using h[T]()
descriptionStringNoDescription of this response scenario
strictHeaderCheckBooleanNoIf true, response must contain exactly the specified headers (no more, no less)

Type Parameter

  • ResponseBody: The expected response body type. Use EmptyBody for responses without a body.

Example

.respondsWith[User](
statusCode = OK,
headers = Seq(
h[String]("X-Request-ID"),
h[Int]("X-Rate-Limit-Remaining")
),
description = "User found and returned successfully",
strictHeaderCheck = false
)

Assertion Block: assert

The assert() method contains the test framework-specific assertions. It receives a context object that provides access to the request/response.

Signature

def assert[R](
r: BaklavaCaseContext[...] => R
): BaklavaIntermediateTestCase[PathParameters, QueryParameters, Headers]

Context Object

The assertion block receives a BaklavaCaseContext with:

  • ctx: The request context containing all request parameters
  • performRequest(route): Method to execute the HTTP request against your routes

Example

.assert { ctx =>
// Execute the request
val response = ctx.performRequest(allRoutes)

// Test framework-specific assertions
response.body.id shouldBe 1L
response.body.name shouldBe "Alice"
response.body.email should include("@example.com")

// Access response metadata
response.status.status shouldBe 200
response.headers.headers should contain key "X-Request-ID"
}

Parameters

Path Parameters

Path parameters are defined using the p[T]() function and represent dynamic segments in the URL path.

Signature

def p[T](
name: String,
description: String = ""
)(implicit tsm: ToPathParam[T], schema: Schema[T]): PathParam[T]

Supported Types

  • Primitives: String, Int, Long, Double, Float, Boolean, Byte, Short, Char
  • BigDecimal
  • java.util.UUID

Examples

// Single path parameter
pathParameters = p[Long]("userId", "The user's unique identifier")

// Multiple path parameters (as tuple)
pathParameters = (
p[String]("organizationId", "Organization identifier"),
p[Long]("userId", "User identifier")
)

// Providing values in onRequest
onRequest(pathParameters = (123L))
onRequest(pathParameters = ("org-123", 456L))

Query Parameters

Query parameters are defined using the q[T]() function and represent URL query string parameters.

Signature

def q[T](
name: String,
description: String = ""
)(implicit tsm: ToQueryParam[T], schema: Schema[T]): QueryParam[T]

Supported Types

  • All path parameter types
  • Option[T] for optional parameters
  • Seq[T] for multi-value parameters

Examples

// Single query parameter
queryParameters = q[String]("search", "Search term")

// Multiple query parameters
queryParameters = (
q[Option[Int]]("limit", "Maximum results"),
q[Option[Int]]("offset", "Pagination offset"),
q[Option[String]]("sort", "Sort field")
)

// Multi-value parameter
queryParameters = q[Seq[String]]("tags", "Filter by tags")

// Providing values in onRequest
onRequest(queryParameters = ("search term"))
onRequest(queryParameters = (Some(10), Some(0), None))
onRequest(queryParameters = (Seq("scala", "api")))

Headers

Headers are defined using the h[T]() function and represent HTTP headers.

Signature

def h[T](
name: String,
description: String = ""
)(implicit tsm: ToHeader[T], schema: Schema[T]): Header[T]

Supported Types

  • All query parameter types
  • Option[T] for optional headers

Examples

// Single header
headers = h[String]("X-API-Key", "API authentication key")

// Multiple headers
headers = (
h[String]("X-Request-ID", "Request tracking ID"),
h[Option[String]]("X-Correlation-ID", "Optional correlation ID"),
h[Int]("X-API-Version", "API version number")
)

// Providing values in onRequest
onRequest(headers = ("my-api-key"))
onRequest(headers = ("req-123", Some("corr-456"), 2))

Request Body

Request bodies can be specified in several ways:

Case Class Body

case class CreateUserRequest(name: String, email: String)

onRequest(
body = CreateUserRequest("Alice", "alice@example.com")
)

Form Data

Use FormOf[T] for application/x-www-form-urlencoded data:

case class LoginForm(username: String, password: String)

onRequest(
body = FormOf[LoginForm](
"username" -> "alice",
"password" -> "secret123"
)
)

Empty Body

For requests without a body (like GET requests):

onRequest()  // body defaults to EmptyBodyInstance

Security

Baklava supports various security schemes defined in the OpenAPI specification.

Defining Security Schemes

Security schemes are defined in the supports() method:

supports(
method = GET,
securitySchemes = Seq(
SecurityScheme("bearerAuth", HttpBearer()),
SecurityScheme("apiKey", ApiKeyInHeader("X-API-Key"))
),
// ...
)

Applying Security in Tests

Security credentials are provided in onRequest():

// HTTP Bearer
onRequest(
security = HttpBearer()("my-jwt-token")
)

// HTTP Basic
onRequest(
security = HttpBasic()("username", "password")
)

// API Key in Header
onRequest(
security = ApiKeyInHeader("X-API-Key")("my-api-key")
)

// API Key in Query
onRequest(
security = ApiKeyInQuery("api_key")("my-api-key")
)

// API Key in Cookie
onRequest(
security = ApiKeyInCookie("session")("session-token")
)

// OAuth2 Bearer
onRequest(
security = OAuth2InBearer(flows)("access-token")
)

// OpenID Connect
onRequest(
security = OpenIdConnectInBearer(url)("id-token")
)

// Mutual TLS
onRequest(
security = MutualTls()()
)

Available Security Types

All security types are defined in Security.scala:

OAuth2 Flows

When using OAuth2InBearer or OAuth2InCookie, you can specify OAuth2 flows via OAuthFlows:

val oauth2 = OAuth2InBearer(
flows = OAuthFlows(
implicitFlow = Some(OAuthImplicitFlow(
authorizationUrl = "https://example.com/oauth/authorize",
scopes = Map("read:users" -> "Read user data", "write:users" -> "Modify users")
)),
authorizationCodeFlow = Some(OAuthAuthorizationCodeFlow(
authorizationUrl = "https://example.com/oauth/authorize",
tokenUrl = "https://example.com/oauth/token",
scopes = Map("read:users" -> "Read user data")
))
)
)

Available flow types: OAuthImplicitFlow, OAuthPasswordFlow, OAuthClientCredentialsFlow, OAuthAuthorizationCodeFlow.

Cookie-based variants (ApiKeyInCookie, OpenIdConnectInCookie, OAuth2InCookie) take two parameters — the cookie name and its value:

security = ApiKeyInCookie(name = "api_key")("session-cookie-name", "cookie-value")
security = OAuth2InCookie(flows)("session-cookie-name", "oauth-token")

HTTP Methods

Baklava supports all standard HTTP methods:

  • GET: Retrieve resources
  • POST: Create resources
  • PUT: Update/replace resources
  • PATCH: Partially update resources
  • DELETE: Delete resources
  • HEAD: Retrieve headers only
  • OPTIONS: Retrieve supported methods
  • TRACE: Diagnostic trace
  • CONNECT: Establish tunnel

HTTP Status Codes

Common status codes available:

  • 2xx Success: OK (200), Created (201), Accepted (202), NoContent (204)
  • 3xx Redirection: MovedPermanently (301), Found (302), NotModified (304)
  • 4xx Client Errors: BadRequest (400), Unauthorized (401), Forbidden (403), NotFound (404), Conflict (409), UnprocessableEntity (422)
  • 5xx Server Errors: InternalServerError (500), NotImplemented (501), ServiceUnavailable (503)

Schema System

Baklava uses a Schema[T] type class to describe data types for documentation generation. Schemas are automatically derived via Magnolia for:

  • Primitive types (String, Int, Long, Double, Boolean, UUID, BigDecimal, etc.)
  • Collections (Seq, List, Set, Map)
  • Option types
  • Array[Byte] (mapped to binary format in OpenAPI)
  • Case classes (automatic derivation via Magnolia — works on both Scala 2.13 and Scala 3)
  • Sealed traits / enums (derived as enum in OpenAPI)

Each schema has a SchemaType which maps to OpenAPI types: StringType, IntegerType, NumberType, BooleanType, ArrayType, ObjectType, NullType.

Custom Schemas

You can customize automatically derived schemas:

implicit val myTypeSchema: Schema[MyType] = implicitly[Schema[MyType]]
.withDescription("Description of MyType")
.withDefault(MyType("example", 42))

Best Practices

  1. Use descriptive names: Provide clear description and summary fields for better documentation
  2. Test multiple scenarios: Include both success and error cases in your tests
  3. Leverage type safety: Use specific types for parameters rather than generic String
  4. Document security: Always specify securitySchemes when endpoints require authentication
  5. Use strict header checks sparingly: Set strictHeaderCheck = false unless you need exact header matching
  6. Organize by resource: Group related paths together in your test files

Common Patterns

Testing Pagination

supports(
method = GET,
queryParameters = (
q[Option[Int]]("limit"),
q[Option[Int]]("offset")
)
)(
onRequest(queryParameters = (Some(10), Some(0)))
.respondsWith[PagedResponse[User]](OK)
.assert { ctx =>
val response = ctx.performRequest(allRoutes)
response.body.items should have size 10
response.body.total should be > 10
}
)

Testing Error Responses

onRequest(pathParameters = (999L))
.respondsWith[ErrorResponse](
statusCode = NotFound,
description = "User not found"
)
.assert { ctx =>
val response = ctx.performRequest(allRoutes)
response.body.code shouldBe "USER_NOT_FOUND"
response.body.message should include("999")
}

Testing Form Submissions

onRequest(
body = FormOf[LoginForm](
"username" -> "alice",
"password" -> "secret"
)
)
.respondsWith[AuthToken](OK)
.assert { ctx =>
val response = ctx.performRequest(allRoutes)
response.body.token should not be empty
}

See Also