All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sttp.tapir.docs.openapi.EndpointInputToDecodeFailureOutput.scala Maven / Gradle / Ivy

package sttp.tapir.docs.openapi

import sttp.model.MediaType
import sttp.tapir.internal._
import sttp.tapir.{Codec, CodecFormat, EndpointIO, EndpointInput, SchemaType}

import scala.annotation.tailrec

private[openapi] object EndpointInputToDecodeFailureOutput {
  def defaultBadRequestDescription(input: EndpointInput[_]): Option[String] = {
    val fallibleBasicInputs = input.asVectorOfBasicInputs(includeAuth = false).filter(inputMayFailWithBadRequest)

    if (fallibleBasicInputs.nonEmpty) Some(badRequestDescription(fallibleBasicInputs))
    else None
  }

  private def inputMayFailWithBadRequest(input: EndpointInput.Basic[_]) = input match {
    case EndpointInput.FixedMethod(_, _, _)     => false
    case EndpointInput.FixedPath(_, _, _)       => false
    case EndpointIO.Empty(_, _)                 => false
    case EndpointInput.PathCapture(_, codec, _) => decodingMayFail(codec)
    case EndpointIO.OneOfBody(variants, _)      => variants.exists(variant => decodingMayFail(variant.codec))
    // there's always a body (if empty - represented as ""), so if it's a plain string body, decoding won't fail
    case i: EndpointIO.Body[_, _] => decodingMayFail(i.codec)
    case i: EndpointInput.Atom[_] => decodingMayFail(i.codec) || !i.codec.schema.isOptional
  }

  private def decodingMayFail[CF <: CodecFormat](codec: Codec[_, _, CF]): Boolean =
    codec.format.mediaType != MediaType.TextPlain ||
      codec.schema.hasValidation ||
      codec.schema.format.nonEmpty ||
      codec.schema.schemaType != SchemaType.SString()

  private def badRequestDescription(fallibleBasicInputs: Vector[EndpointInput.Basic[_]]) =
    fallibleBasicInputs.map(failureSourceMessage).distinct.mkString(", ")

  /** Describes the source of the failure: in which part of the request did the failure occur. Currently the same as
    * `DefaultDecodeFailureHandler.FailureMessages.failureSourceMessage`
    */
  @tailrec
  def failureSourceMessage(input: EndpointInput[_]): String =
    input match {
      case EndpointInput.FixedMethod(_, _, _)      => s"Invalid value for: method"
      case EndpointInput.FixedPath(_, _, _)        => s"Invalid value for: path segment"
      case EndpointInput.PathCapture(name, _, _)   => s"Invalid value for: path parameter ${name.getOrElse("?")}"
      case EndpointInput.PathsCapture(_, _)        => s"Invalid value for: path"
      case EndpointInput.Query(name, _, _, _)      => s"Invalid value for: query parameter $name"
      case EndpointInput.QueryParams(_, _)         => "Invalid value for: query parameters"
      case EndpointInput.Cookie(name, _, _)        => s"Invalid value for: cookie $name"
      case _: EndpointInput.ExtractFromRequest[_]  => "Invalid value"
      case a: EndpointInput.Auth[_, _]             => failureSourceMessage(a.input)
      case _: EndpointInput.MappedPair[_, _, _, _] => "Invalid value"
      case _: EndpointIO.Body[_, _]                => s"Invalid value for: body"
      case _: EndpointIO.StreamBodyWrapper[_, _]   => s"Invalid value for: body"
      case EndpointIO.Header(name, _, _)           => s"Invalid value for: header $name"
      case EndpointIO.FixedHeader(name, _, _)      => s"Invalid value for: header $name"
      case EndpointIO.Headers(_, _)                => s"Invalid value for: headers"
      case _                                       => "Invalid value"
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy