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
| Parameter | Type | Required | Description |
|---|---|---|---|
path | String | Yes | The URL path pattern, supporting path parameters in {paramName} format (e.g., /users/{userId}) |
description | String | No | Detailed description of the path and its purpose |
summary | String | No | Brief summary of the path |
steps | BaklavaMethodDefinition* | Yes | One 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
| Parameter | Type | Required | Description |
|---|---|---|---|
method | BaklavaHttpMethod | Yes | HTTP method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS) |
securitySchemes | Seq[SecurityScheme] | No | Security schemes available for this operation (see Security) |
pathParameters | PathParameters | No | Path parameter definitions using p[T]() |
queryParameters | QueryParameters | No | Query parameter definitions using q[T]() |
headers | Headers | No | Header definitions using h[T]() |
description | String | No | Detailed operation description |
summary | String | No | Brief operation summary |
operationId | String | No | Unique identifier for the operation |
tags | Seq[String] | No | Tags for grouping operations in documentation |
steps | BaklavaIntermediateTestCase* | Yes | One 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
| Parameter | Type | Required | Description |
|---|---|---|---|
body | RequestBody | No | Request body (defaults to empty). Can be a case class, FormOf[T] for form data, or EmptyBodyInstance |
security | AppliedSecurity | No | Applied security credentials (see Security) |
pathParameters | PathParametersProvided | No | Actual path parameter values matching the definition |
queryParameters | QueryParametersProvided | No | Actual query parameter values matching the definition |
headers | HeadersProvided | No | Actual 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
| Parameter | Type | Required | Description |
|---|---|---|---|
statusCode | BaklavaHttpStatus | Yes | Expected HTTP status code (OK, Created, NotFound, etc.) |
headers | Seq[Header[?]] | No | Expected response headers using h[T]() |
description | String | No | Description of this response scenario |
strictHeaderCheck | Boolean | No | If true, response must contain exactly the specified headers (no more, no less) |
Type Parameter
ResponseBody: The expected response body type. UseEmptyBodyfor 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 parametersperformRequest(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 BigDecimaljava.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 parametersSeq[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:
HttpBearer: Bearer token authenticationHttpBasic: Basic authenticationApiKeyInHeader: API key in headerApiKeyInQuery: API key in query parameterApiKeyInCookie: API key in cookieMutualTls: Mutual TLS authenticationOpenIdConnectInBearer: OpenID Connect in bearer tokenOpenIdConnectInCookie: OpenID Connect in cookieOAuth2InBearer: OAuth2 in bearer tokenOAuth2InCookie: OAuth2 in cookie
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 security
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 resourcesPOST: Create resourcesPUT: Update/replace resourcesPATCH: Partially update resourcesDELETE: Delete resourcesHEAD: Retrieve headers onlyOPTIONS: Retrieve supported methodsTRACE: Diagnostic traceCONNECT: 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) OptiontypesArray[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
enumin 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
- Use descriptive names: Provide clear
descriptionandsummaryfields for better documentation - Test multiple scenarios: Include both success and error cases in your tests
- Leverage type safety: Use specific types for parameters rather than generic
String - Document security: Always specify
securitySchemeswhen endpoints require authentication - Use strict header checks sparingly: Set
strictHeaderCheck = falseunless you need exact header matching - 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
- Installation Guide - Setting up Baklava
- Output Formats - Generating documentation
- Configuration - Configuring Baklava