Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add docs for delimited path parameters #4079

Merged
merged 5 commits into from
Oct 11, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions doc/endpoint/codecs.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,89 @@ Different codec formats can be used in different contexts. When defining a path,
with the `TextPlain` media type can be used. However, for bodies, any media type is allowed. For example, the
input/output described by `jsonBody[T]` requires a json codec.

## Handling Delimited Path Parameters
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the proposal! I think I'd suggest moving it to a top-level page linked in the How To section. The "codec" section over here is more of a reference, and this describes a specific use-case

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the How To section references links to .scala files. To follow that same pattern I'm thinking I'll convert this to DelimitedPathParameterExample.scala and add code comments. Unless I'm missing something and I can just attach a .md

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh no, I meant a regular .md page. There are links to examples in the Examples section, but you should be able to add a link here: https://github.com/softwaremill/tapir/blob/master/doc/index.md?plain=1#L158 to the new page

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't too sure where it should be housed so I made a dir for how-tos and placed it in there. Let me know if I should change that or rename it.


Tapir allows you to handle complex path parameters, such as lists of custom types separated by delimiters (e.g., commas).
This can be achieved using Codec.delimited, which facilitates the serialization and deserialization of delimited lists
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This can be achieved using Codec.delimited, which facilitates the serialization and deserialization of delimited lists
This can be achieved using `Codec.delimited`, which facilitates the serialization and deserialization of delimited lists

within path segments.

### Use Case
Suppose you want to define an endpoint that accepts a list of names as a comma-separated path parameter. Each name should
adhere to a specific pattern (e.g., only uppercase letters).

### Implementation Steps:

### 1. Define the Custom Type and Validator
Start by defining your custom type and the associated validator to enforce the desired pattern.

```scala
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can probably be compilation-checked? you can do this by adding mdoc:compile-only and then running compileDocumentation from sbt to verify that everything works

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran that locally and got a success but needed to have all the code and imports together. I left mdoc:compile-only on the last code snippet but let me know if that should be removed.

import sttp.tapir._
import sttp.tapir.generic.auto._
import sttp.tapir.Codec
import sttp.tapir.Validator

case class Name(value: String)

// Validator to ensure names consist of uppercase letters only
val nameValidator: Validator[String] = Validator.pattern("^[A-Z]+$")
```

### 2. Create Codecs for the Custom Type and Delimited List
Utilize `Codec.parsedString` for individual `Name` instances and `Codec.delimited` for handling the list.

```scala
// Codec for single Name
given Codec[String, Name, TextPlain] = Codec.parsedString(Name.apply)
.validate(nameValidator.contramap(_.value))

// Codec for a list of Names, delimited by commas
given Codec[String, Delimited[",", Name], TextPlain] = Codec.delimited
```

### 3. Define the Endpoint with Delimited Path Parameter
Incorporate the delimited codec into your endpoint definition to handle the list of names in the path.

```scala
val getUserEndpoint =
endpoint.get
.in("user" / path[List[Name]]("id"))
.out(stringBody)
```

### 4. Generated OpenAPI Schema
When you generate the OpenAPI documentation for this endpoint, the schema for the `id` path parameter will
correctly reflect it as an array with the specified pattern for each item.

```yaml
paths:
/user/{id}:
get:
operationId: getUserId
parameters:
- name: id
in: path
required: true
schema:
type: array
items:
type: string
pattern: ^[A-Z]+$
```

### Explanation
- `Codec.parsedString`: Transforms a `String` into a custom type (`Name`) and vice versa. It also applies validation to
ensure each `Name` adheres to the specified pattern.
- `Codec.delimited`: Handles the serialization and deserialization of a delimited list (e.g., comma-separated) of the
custom type. By specifying `Delimited[",", Name]`, Tapir knows how to split and join the list based on the delimiter.
- Endpoint Definition: The `path[List[Name]]("id")` indicates that the id path parameter should be treated as a list of
`Name` objects, utilizing the previously defined codecs.

### Validation
Validators play a crucial role in ensuring that each element within the delimited list meets the required criteria. In
this example, `nameValidator` ensures that each `Name` consists solely of uppercase letters. Tapir applies this validation
to each element in the list, providing robust input validation.


## Next

Read on about [custom types](customtypes.md).
Loading