Skip to content

Commit

Permalink
Add a delimiter attribute to properly represent delimited values in e…
Browse files Browse the repository at this point in the history
…xamples/defaults (#3589)
  • Loading branch information
adamw authored Mar 11, 2024
1 parent 7301c06 commit 604bd9d
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ trait CodecExtensions2 {
Codec.string
.map(_.split(delimiter.value).toList)(_.mkString(delimiter.value))
.mapDecode(ls => DecodeResult.sequence(ls.map(codec.decode)).map(_.toList))(_.map(codec.encode))
.schema(codec.schema.asIterable[List].attribute(Schema.Explode.Attribute, Schema.Explode(false)))
.schema(
codec.schema
.asIterable[List]
.attribute(Schema.Explode.Attribute, Schema.Explode(false))
.attribute(Schema.Delimiter.Attribute, Schema.Delimiter(delimiter.value))
)
.map(Delimited[D, T](_))(_.values)
}
6 changes: 6 additions & 0 deletions core/src/main/scala/sttp/tapir/Schema.scala
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,12 @@ object Schema extends LowPrioritySchema with SchemaCompanionMacros {
val Attribute: AttributeKey[Explode] = new AttributeKey[Explode]("sttp.tapir.Schema.Explode")
}

/** Used in combination with explode, to properly represent delimited values in examples and default values (#3581) */
case class Delimiter(delimiter: String)
object Delimiter {
val Attribute: AttributeKey[Delimiter] = new AttributeKey[Delimiter]("sttp.tapir.Schema.Delimiter")
}

/** Corresponds to JsonSchema's `title` parameter which should be used for defining title of the object. */
case class Title(value: String)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,19 @@ package object apispec {
private[docs] def exampleValue(v: String): ExampleValue = ExampleSingleValue(v)
private[docs] def exampleValue[T](codec: Codec[_, T, _], e: T): Option[ExampleValue] = exampleValue(codec.schema, codec.encode(e))
private[docs] def exampleValue(schema: Schema[_], raw: Any): Option[ExampleValue] = {
(raw, schema.schemaType) match {
// #3581: if there's a delimiter and the encoded value is a string, the codec will have produced a final
// representation (with the delimiter applied), but in the docs we want to show the split values
val rawDelimited = schema.attribute(Schema.Delimiter.Attribute) match {
case None => raw
case Some(Schema.Delimiter(d)) =>
raw match {
case s: String => s.split(d).toSeq
case List(s: String) => s.split(d).toSeq
case _ => raw
}
}

(rawDelimited, schema.schemaType) match {
case (it: Iterable[_], SchemaType.SArray(_)) => Some(ExampleMultipleValue(it.map(rawToString).toList))
case (it: Iterable[_], _) => it.headOption.map(v => ExampleSingleValue(rawToString(v)))
case (it: Option[_], _) => it.map(v => ExampleSingleValue(rawToString(v)))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
openapi: 3.1.0
info:
title: Enums
version: '1.0'
paths:
/:
get:
operationId: getRoot
parameters:
- name: styles
in: query
required: false
explode: false
schema:
type: array
items:
$ref: '#/components/schemas/CornerStyle'
default:
- rounded
- straight
example:
- rounded
- straight
responses:
'200':
description: ''
'400':
description: 'Invalid value for: query parameter styles'
content:
text/plain:
schema:
type: string
components:
schemas:
CornerStyle:
type: string
enum:
- rounded
- straight
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import sttp.tapir.Schema.SName
import sttp.tapir.docs.openapi.VerifyYamlEnumerationTest._
import sttp.tapir.generic.auto._
import sttp.tapir.json.circe._
import sttp.tapir.model.CommaSeparated
import sttp.tapir.model.{CommaSeparated, Delimited}
import sttp.tapir.{Schema, Validator, _}

class VerifyYamlEnumerationTest extends AnyFunSuite with Matchers {
Expand Down Expand Up @@ -45,6 +45,19 @@ class VerifyYamlEnumerationTest extends AnyFunSuite with Matchers {

noIndentation(actualYaml) shouldBe expectedYaml
}

test("should support delimited query parameters with a default value") {
val q = query[CommaSeparated[CornerStyle.Value]]("styles")
.default(Delimited[",", CornerStyle.Value](List(CornerStyle.Rounded, CornerStyle.Straight)))
.example(Delimited[",", CornerStyle.Value](List(CornerStyle.Rounded, CornerStyle.Straight)))

val e = endpoint.get.in(q)

val expectedYaml = load("enum/expected_enum_in_delimited_query_with_default.yml")
val actualYaml = OpenAPIDocsInterpreter().toOpenAPI(e, "Enums", "1.0").toYaml

noIndentation(actualYaml) shouldBe expectedYaml
}
}

object VerifyYamlEnumerationTest {
Expand Down

0 comments on commit 604bd9d

Please sign in to comment.