Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
sttp.tapir.docs.openapi.EndpointToOpenAPIPaths.scala Maven / Gradle / Ivy
package sttp.tapir.docs.openapi
import sttp.model.Method
import sttp.apispec.{Schema => ASchema, SchemaType => ASchemaType}
import sttp.apispec.openapi._
import sttp.tapir._
import sttp.tapir.EndpointIO.OneOfBody
import sttp.tapir.docs.apispec.DocsExtensionAttribute.{RichEndpointIOInfo, RichEndpointInfo}
import sttp.tapir.docs.apispec.schema.TSchemaToASchema
import sttp.tapir.docs.apispec.{DocsExtensions, SecurityRequirementsForEndpoints, SecuritySchemes, namedPathComponents}
import sttp.tapir.internal._
import scala.collection.immutable.ListMap
private[openapi] class EndpointToOpenAPIPaths(
tschemaToASchema: TSchemaToASchema,
securitySchemes: SecuritySchemes,
options: OpenAPIDocsOptions
) {
private val codecToMediaType = new CodecToMediaType(tschemaToASchema)
private val endpointToOperationResponse = new EndpointToOperationResponse(tschemaToASchema, codecToMediaType, options)
private val securityRequirementsForEndpoint = new SecurityRequirementsForEndpoints(securitySchemes)
def pathItem(e: AnyEndpoint): (String, PathItem) = {
import Method._
val inputs = filterOutHiddenInputs(e.asVectorOfBasicInputs(includeAuth = false))
val pathComponents = namedPathComponents(inputs)
val method = e.method.getOrElse(Method.GET)
val defaultId = options.operationIdGenerator(e, pathComponents, method)
val operation = Some(endpointToOperation(defaultId, e, inputs))
val pathItem = PathItem(
get = if (method == GET) operation else None,
put = if (method == PUT) operation else None,
post = if (method == POST) operation else None,
delete = if (method == DELETE) operation else None,
options = if (method == OPTIONS) operation else None,
head = if (method == HEAD) operation else None,
patch = if (method == PATCH) operation else None,
trace = if (method == TRACE) operation else None
)
(e.showPathTemplate(showQueryParam = None, includeAuth = false, showNoPathAs = "/", showPathsAs = None), pathItem)
}
private def filterOutHiddenInputs(inputs: Vector[EndpointInput.Basic[_]]) = inputs.collect {
// EndpointInput.Basic is either OneOfBody or Atom
case OneOfBody(variants, mapping) =>
OneOfBody(
variants.filterNot(_.codec.schema.hidden),
mapping
)
case a: EndpointInput.Atom[_] if !a.codec.schema.hidden => a
}
private def endpointToOperation(defaultId: String, e: AnyEndpoint, inputs: Vector[EndpointInput.Basic[_]]): Operation = {
val parameters = operationParameters(inputs).distinct.toList
val body: Vector[ReferenceOr[RequestBody]] = operationInputBody(inputs)
val responses: ListMap[ResponsesKey, ReferenceOr[Response]] = endpointToOperationResponse(e)
Operation(
tags = e.info.tags.toList,
summary = e.info.summary,
description = e.info.description,
operationId = e.info.name.orElse(Some(defaultId)),
parameters = parameters.map(Right(_)),
requestBody = body.headOption,
responses = Responses(responses),
deprecated = if (e.info.deprecated) Some(true) else None,
security = securityRequirementsForEndpoint(e),
extensions = DocsExtensions.fromIterable(e.info.docsExtensions)
)
}
private def operationInputBody(inputs: Vector[EndpointInput.Basic[_]]) = {
inputs.collect {
case EndpointIO.Body(_, codec, info) =>
Right(
RequestBody(
info.description,
codecToMediaType(codec, info.examples, None, Nil),
Some(!codec.schema.isOptional),
DocsExtensions.fromIterable(info.docsExtensions)
)
)
case EndpointIO.OneOfBody(variants, _) =>
Right(
RequestBody(
variants.flatMap(_.info.description).headOption,
variants
.flatMap(variant => codecToMediaType(variant.codec, variant.info.examples, Some(variant.range.toString), Nil))
.toListMap,
Some(!variants.forall(_.codec.schema.isOptional)),
DocsExtensions.fromIterable(variants.flatMap(_.info.docsExtensions))
)
)
case EndpointIO.StreamBodyWrapper(StreamBodyIO(_, codec, info, _, encodedExamples)) =>
Right(
RequestBody(
info.description,
codecToMediaType(codec, info.examples, None, encodedExamples),
Some(true),
DocsExtensions.fromIterable(info.docsExtensions)
)
)
}
}
private def operationParameters(inputs: Vector[EndpointInput.Basic[_]]) = {
inputs.collect {
case q: EndpointInput.Query[_] => enrich(q, queryToParameter(q))
case p: EndpointInput.PathCapture[_] => enrich(p, pathCaptureToParameter(p))
case h: EndpointIO.Header[_] => enrich(h, headerToParameter(h))
case c: EndpointInput.Cookie[_] => enrich(c, cookieToParameter(c))
case f: EndpointIO.FixedHeader[_] => enrich(f, fixedHeaderToParameter(f))
}
}
private def headerToParameter[T](header: EndpointIO.Header[T]) =
EndpointInputToParameterConverter.from(header, tschemaToASchema(header.codec))
private def fixedHeaderToParameter[T](header: EndpointIO.FixedHeader[_]) =
EndpointInputToParameterConverter.from(header, ASchema(ASchemaType.String))
private def cookieToParameter[T](cookie: EndpointInput.Cookie[T]) =
EndpointInputToParameterConverter.from(cookie, tschemaToASchema(cookie.codec))
private def pathCaptureToParameter[T](p: EndpointInput.PathCapture[T]) =
EndpointInputToParameterConverter.from(p, tschemaToASchema(p.codec))
private def queryToParameter[T](query: EndpointInput.Query[T]) = query.codec.format match {
// use `schema` for simple plain text scenarios and `content` for complex serializations, e.g. JSON
// see https://swagger.io/docs/specification/describing-parameters/#schema-vs-content
case CodecFormat.TextPlain() => EndpointInputToParameterConverter.from(query, tschemaToASchema(query.codec))
case _ => EndpointInputToParameterConverter.from(query, codecToMediaType(query.codec, query.info.examples, None, Nil))
}
private def enrich(e: EndpointInput.Atom[_], p: Parameter): Parameter = addExplode(e, p)
private def addExplode(e: EndpointInput.Atom[_], p: Parameter): Parameter =
(e, e.codec.schema.attribute(Schema.Explode.Attribute)) match {
// see https://swagger.io/specification/#parameter-object for defaults
case ((_: EndpointInput.Query[_]), Some(Schema.Explode(false))) => p.explode(false)
case ((_: EndpointInput.Cookie[_]), Some(Schema.Explode(false))) => p.explode(false)
case ((_: EndpointIO.Header[_]), Some(Schema.Explode(true))) => p.explode(true)
case ((_: EndpointIO.FixedHeader[_]), Some(Schema.Explode(true))) => p.explode(true)
case ((_: EndpointInput.PathCapture[_]), Some(Schema.Explode(true))) => p.explode(true)
case _ => p
}
}