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

endpoints4s.openapi.model.OpenApi.scala Maven / Gradle / Ivy

The newest version!
package endpoints4s.openapi.model

import endpoints4s.algebra.{ExternalDocumentationObject, Tag}
import endpoints4s.{Encoder, Hashing}

/** @see [[https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.0.md]]
  * @note Throws an exception on creation if several tags have the same name but not the same other attributes.
  */
final class OpenApi private (
    val info: Info,
    private val innerPaths: collection.Map[String, PathItem],
    val components: Components,
    val servers: Seq[Server]
) extends Serializable {

  def paths: Map[String, PathItem] = innerPaths.toMap

  override def toString =
    s"OpenApi($info, $paths, $components, $servers)"

  override def equals(other: Any): Boolean =
    other match {
      case that: OpenApi =>
        info == that.info && paths == that.paths && components == that.components && servers == that.servers
      case _ => false
    }

  override def hashCode(): Int = Hashing.hash(info, paths, components, servers)

  val tags: Set[Tag] = OpenApi.extractTags(innerPaths)

  private[this] def copy(
      info: Info = info,
      paths: collection.Map[String, PathItem] = innerPaths,
      components: Components = components,
      servers: Seq[Server] = servers
  ): OpenApi =
    new OpenApi(info, paths, components, servers)

  def withInfo(info: Info): OpenApi =
    copy(info = info)

  def withPaths(paths: Map[String, PathItem]): OpenApi =
    copy(paths = paths)

  def withComponents(components: Components): OpenApi =
    copy(components = components)

  def withServers(servers: Seq[Server]): OpenApi =
    copy(servers = servers)
}

object OpenApi {

  val openApiVersion = "3.0.0"

  def apply(info: Info, paths: Map[String, PathItem], components: Components): OpenApi =
    new OpenApi(info, paths, components, Nil)

  def apply(info: Info, paths: collection.Map[String, PathItem], components: Components): OpenApi =
    new OpenApi(info, paths, components, Nil)

  def apply(
      info: Info,
      paths: collection.Map[String, PathItem],
      components: Components,
      servers: Seq[Server]
  ): OpenApi =
    new OpenApi(info, paths, components, servers)

  private def mapJson[A](map: collection.Map[String, A])(f: A => ujson.Value): ujson.Obj = {
    val result = ujson.Obj()
    //preserve order defined by user or sort by key to minimize diff
    val stableMap = map match {
      case map: collection.mutable.LinkedHashMap[String, A] => map
      case map: collection.immutable.ListMap[String, A]     => map
      case map                                              => map.toSeq.sortBy(_._1)
    }
    stableMap.foreach { case (k, v) => result.value.put(k, f(v)) }
    result
  }

  private[openapi] def schemaJson(schema: Schema): ujson.Obj = {
    val result = ujson.Obj()

    for (description <- schema.description) {
      result.value.put("description", ujson.Str(description))
    }
    for (example <- schema.example) {
      result.value.put("example", example)
    }
    for (title <- schema.title) {
      result.value.put("title", title)
    }
    for (default <- schema.default) {
      result.value.put("default", default)
    }

    schema match {
      case primitive: Schema.Primitive =>
        result.value.put("type", ujson.Str(primitive.name))
        primitive.format.foreach(s => result.value.put("format", ujson.Str(s)))
        primitive.minimum.foreach(d => result.value.put("minimum", ujson.Num(d)))
        primitive.exclusiveMinimum.foreach(b => result.value.put("exclusiveMinimum", ujson.Bool(b)))
        primitive.maximum.foreach(d => result.value.put("maximum", ujson.Num(d)))
        primitive.exclusiveMaximum.foreach(b => result.value.put("exclusiveMaximum", ujson.Bool(b)))
        primitive.multipleOf.foreach(d => result.value.put("multipleOf", ujson.Num(d)))
      case obj: Schema.Object =>
        result.value.put("type", "object")
        val properties = ujson.Obj()
        obj.properties.foreach { (p: Schema.Property) =>
          val schema = p.schema
            .withDefinedDescription(p.description)
            .withDefinedDefault(p.defaultValue)
          properties.value.put(p.name, schemaJson(schema))
        }
        result.value.put("properties", properties)

        val required = obj.properties.filter(_.isRequired).map(_.name)
        if (required.nonEmpty) {
          result.value.put("required", ujson.Arr.from(required))
        }
        obj.additionalProperties.foreach(p =>
          result.value.put("additionalProperties", schemaJson(p))
        )
      case array: Schema.Array =>
        result.value.put("type", "array")
        array.elementType match {
          case Left(value) =>
            result.value.put("items", schemaJson(value))
          case Right(value) =>
            // Best effort (not 100% accurate) to represent the heterogeneous array in OpenAPI 3.0
            // This should be changed with OpenAPI 3.1 and more idiomatic representation using `prefixItems`
            result.value ++= List(
              "items" -> schemaJson(
                Schema.OneOf(
                  alternatives = Schema.EnumeratedAlternatives(value),
                  description = None,
                  example = None,
                  title = None
                )
              ),
              "minItems" -> ujson.Num(value.length.toDouble),
              "maxItems" -> ujson.Num(value.length.toDouble)
            )
        }
      case enm: Schema.Enum =>
        result.value ++= schemaJson(
          enm.elementType.withDefinedDescription(enm.description)
        ).value
        result.value.put("enum", ujson.Arr.from(enm.values))
      case oneOf: Schema.OneOf =>
        result.value ++=
          (oneOf.alternatives match {
            case discAlternatives: Schema.DiscriminatedAlternatives =>
              val mapping = ujson.Obj()
              discAlternatives.alternatives.foreach {
                case (tag, ref: Schema.Reference) =>
                  mapping.value.put(tag, ujson.Str(Schema.Reference.toRefPath(ref.name)))
                case _ =>
              }
              val discriminator = ujson.Obj()
              discriminator.value += "propertyName" -> ujson.Str(
                discAlternatives.discriminatorFieldName
              )
              if (mapping.value.nonEmpty) {
                discriminator.value += "mapping" -> mapping
              }
              List(
                "oneOf" ->
                  ujson.Arr.from(discAlternatives.alternatives.map(kv => schemaJson(kv._2))),
                "discriminator" -> discriminator
              )
            case enumAlternatives: Schema.EnumeratedAlternatives =>
              List(
                "oneOf" -> ujson.Arr.from(enumAlternatives.alternatives.map(schemaJson))
              )
          })
      case allOf: Schema.AllOf =>
        result.value.put("allOf", ujson.Arr.from(allOf.schemas.map(schemaJson)))
      case reference: Schema.Reference =>
        /* In OpenAPI 3.0 (and 2.0), reference schemas are special in that all
         * their sibling values are ignored!
         *
         * This means that if any other sibling schema fields have been set
         * (eg. for a `description`, `example`, etc.), we need to nest the
         * schema reference object inside an `allOf` field.
         *
         * See .
         */
        val refSchemaName = ujson.Str(Schema.Reference.toRefPath(reference.name))
        if (result.value.isEmpty) {
          result.value.put("$ref", refSchemaName)
        } else {
          result.value.put("allOf", ujson.Arr(ujson.Obj("$ref" -> refSchemaName)))
        }
    }

    result
  }

  private def securitySchemeJson(securityScheme: SecurityScheme): ujson.Obj = {
    val result = ujson.Obj()
    result.value.put("type", ujson.Str(securityScheme.`type`))
    for (description <- securityScheme.description) {
      result.value.put("description", ujson.Str(description))
    }
    for (name <- securityScheme.name) {
      result.value.put("name", ujson.Str(name))
    }
    for (in <- securityScheme.in) {
      result.value.put("in", ujson.Str(in))
    }
    for (scheme <- securityScheme.scheme) {
      result.value.put("scheme", ujson.Str(scheme))
    }
    for (bearerFormat <- securityScheme.bearerFormat) {
      result.value.put("bearerFormat", ujson.Str(bearerFormat))
    }
    result
  }

  private def infoJson(info: Info): ujson.Obj = {
    val result = ujson.Obj()
    result.value.put("title", ujson.Str(info.title))
    result.value.put("version", ujson.Str(info.version))
    info.description.foreach(description => result.value.put("description", ujson.Str(description)))
    result
  }

  private def componentsJson(components: Components): ujson.Obj =
    ujson.Obj(
      "schemas" -> mapJson(components.schemas)(schemaJson),
      "securitySchemes" -> mapJson(components.securitySchemes)(
        securitySchemeJson
      )
    )

  private def responseJson(response: Response): ujson.Obj = {
    val result = ujson.Obj()
    result.value.put("description", ujson.Str(response.description))
    if (response.headers.nonEmpty) {
      result.value.put("headers", mapJson(response.headers)(responseHeaderJson))
    }
    if (response.content.nonEmpty) {
      result.value.put("content", mapJson(response.content)(mediaTypeJson))
    }
    result
  }

  def responseHeaderJson(responseHeader: ResponseHeader): ujson.Value = {
    val result = ujson.Obj()
    result.value.put("schema", schemaJson(responseHeader.schema))
    if (responseHeader.required) {
      result.value.put("required", ujson.True)
    }
    responseHeader.description.foreach { description =>
      result.value.put("description", ujson.Str(description))
    }
    result
  }

  def mediaTypeJson(mediaType: MediaType): ujson.Value =
    mediaType.schema match {
      case Some(schema) => ujson.Obj("schema" -> schemaJson(schema))
      case None         => ujson.Obj()
    }

  private def operationJson(operation: Operation): ujson.Obj = {
    val obj = ujson.Obj()
    obj.value.put("responses", mapJson(operation.responses)(responseJson))
    operation.operationId.foreach { id =>
      obj.value.put("operationId", ujson.Str(id))
    }
    operation.summary.foreach { summary =>
      obj.value.put("summary", ujson.Str(summary))
    }
    operation.description.foreach { description =>
      obj.value.put("description", ujson.Str(description))
    }
    if (operation.parameters.nonEmpty) {
      obj.value.put(
        "parameters",
        ujson.Arr.from(
          operation.parameters.map(parameterJson)
        )
      )
    }
    operation.requestBody.foreach { requestBody =>
      obj.value.put("requestBody", requestBodyJson(requestBody))
    }
    if (operation.tags.nonEmpty) {
      val tags = ujson.Arr()
      operation.tags.foreach(tag => tags.value += ujson.Str(tag.name))
      obj.value.put("tags", tags)
    }
    if (operation.security.nonEmpty) {
      val security = ujson.Arr()
      operation.security.foreach(item => security.value += securityRequirementJson(item))
      obj.value.put("security", security)
    }
    if (operation.callbacks.nonEmpty) {
      obj.value.put("callbacks", mapJson(operation.callbacks)(pathsJson))
    }
    if (operation.deprecated) {
      obj.value.put("deprecated", ujson.True)
    }
    obj
  }

  private def parameterJson(parameter: Parameter): ujson.Value = {
    val result = ujson.Obj(
      "name" -> ujson.Str(parameter.name),
      "in" -> inJson(parameter.in),
      "schema" -> schemaJson(parameter.schema)
    )
    parameter.description.foreach { description =>
      result.value.put("description", ujson.Str(description))
    }
    if (parameter.required) {
      result.value.put("required", ujson.True)
    }
    result
  }

  private def inJson(in: In): ujson.Value =
    in match {
      case In.Query  => ujson.Str("query")
      case In.Path   => ujson.Str("path")
      case In.Header => ujson.Str("header")
      case In.Cookie => ujson.Str("cookie")
    }

  private def requestBodyJson(body: RequestBody): ujson.Value = {
    val result = ujson.Obj()
    result.value.put("required", ujson.True)
    result.value.put("content", mapJson(body.content)(mediaTypeJson))
    body.description.foreach { description =>
      result.value.put("description", ujson.Str(description))
    }
    result
  }

  private def tagJson(tag: Tag): ujson.Value = {
    val result = ujson.Obj()
    result.value.put("name", ujson.Str(tag.name))

    for (description <- tag.description) {
      result.value.put("description", description)
    }
    for (externalDocs <- tag.externalDocs) {
      result.value.put("externalDocs", externalDocumentationObjectJson(externalDocs))
    }
    result
  }

  private def serverJson(server: Server): ujson.Value = {
    val result = ujson.Obj()
    result.value.put("url", ujson.Str(server.url))
    for (description <- server.description) {
      result.value.put("description", ujson.Str(description))
    }
    if (server.variables.nonEmpty) {
      result.value.put("variables", mapJson(server.variables)(serverVariableJson))
    }
    result
  }

  private def serverVariableJson(variable: ServerVariable): ujson.Value = {
    val result = ujson.Obj()
    result.value.put("default", ujson.Str(variable.default))
    for (description <- variable.description) {
      result.value.put("description", ujson.Str(description))
    }
    for (alternatives <- variable.`enum`) {
      result.value.put(
        "enum",
        ujson.Arr.from(alternatives.map(alternative => ujson.Str(alternative)))
      )
    }
    result
  }

  private def externalDocumentationObjectJson(
      externalDoc: ExternalDocumentationObject
  ): ujson.Value = {
    val result = ujson.Obj(
      "url" -> ujson.Str(externalDoc.url)
    )
    for (description <- externalDoc.description) {
      result.value.put("description", description)
    }
    result
  }

  private def securityRequirementJson(
      securityRequirement: SecurityRequirement
  ): ujson.Value =
    ujson.Obj(
      securityRequirement.name -> ujson.Arr.from(securityRequirement.scopes.map(ujson.Str))
    )

  private def pathsJson(paths: collection.Map[String, PathItem]): ujson.Obj =
    mapJson(paths)(pathItem => mapJson(pathItem.operations)(operationJson))

  val jsonEncoder: Encoder[OpenApi, ujson.Value] =
    openApi => {
      val result = ujson.Obj()
      result.value.put("openapi", ujson.Str(openApiVersion))
      result.value.put("info", infoJson(openApi.info))
      result.value.put("paths", pathsJson(openApi.innerPaths))

      if (openApi.servers.nonEmpty) {
        val servers = ujson.Arr()
        openApi.servers.foreach(server => servers.value += serverJson(server))
        result.value.put("servers", servers)
      }
      if (openApi.tags.nonEmpty) {
        val tagsAsJson = openApi.tags.map(tag => tagJson(tag)).toList
        result.value.put("tags", ujson.Arr.from(tagsAsJson))
      }
      if (openApi.components.schemas.nonEmpty || openApi.components.securitySchemes.nonEmpty) {
        result.value.put("components", componentsJson(openApi.components))
      }
      result.value
    }

  private def extractTags(paths: collection.Map[String, PathItem]): Set[Tag] = {
    val allTags = paths.flatMap { case (_, pathItem) =>
      pathItem.operations.map { case (_, operation) =>
        operation.tags
      }
    }.flatten

    val tagsByName = allTags.groupBy(_.name)
    tagsByName.foreach { case (_, listOfTags) =>
      val set = listOfTags.toSet
      if (set.size > 1) {
        throw new IllegalArgumentException(
          s"Found tags with the same name but different values: $set"
        )
      }
    }

    // Note that tags without any additional information will still be shown. However there is no
    // reason to add these tags to the root since tags with only names can and will be defined at
    // the moment they will be used in the endpoint descriptions themselves.
    allTags
      .filter(tag => tag.description.nonEmpty || tag.externalDocs.nonEmpty)
      .toSet
  }

  implicit val stringEncoder: Encoder[OpenApi, String] =
    openApi => jsonEncoder.encode(openApi).transform(ujson.StringRenderer()).toString

}

final class Info private (
    val title: String,
    val version: String,
    val description: Option[String]
) extends Serializable {

  override def toString: String =
    s"Info($title, $version, $description)"

  override def equals(other: Any): Boolean =
    other match {
      case that: Info =>
        title == that.title && version == that.version && description == that.description
      case _ => false
    }

  override def hashCode(): Int =
    Hashing.hash(title, version, description)

  private[this] def copy(
      title: String = title,
      version: String = version,
      description: Option[String] = description
  ): Info =
    new Info(title, version, description)

  def withTitle(title: String): Info =
    copy(title = title)

  def withVersion(version: String): Info =
    copy(version = version)

  def withDescription(description: Option[String]): Info =
    copy(description = description)

}

object Info {

  def apply(title: String, version: String): Info =
    new Info(title, version, None)

}

/** @see [[https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-object]] */
final class Server private (
    val url: String,
    val description: Option[String],
    val variables: Map[String, ServerVariable]
) extends Serializable {

  override def toString: String = s"Server($url, $description, $variables)"

  override def equals(other: Any): Boolean =
    other match {
      case that: Server =>
        url == that.url && description == that.description && variables == that.variables
      case _ => false
    }

  override def hashCode(): Int = Hashing.hash(url, description, variables)

  private[this] def copy(
      url: String = url,
      description: Option[String] = description,
      variables: Map[String, ServerVariable] = variables
  ): Server =
    new Server(url, description, variables)

  def withUrl(url: String): Server = copy(url = url)

  def withDescription(description: Option[String]): Server = copy(description = description)

  def withVariables(variables: Map[String, ServerVariable]): Server = copy(variables = variables)
}

object Server {

  def apply(url: String): Server =
    new Server(url, None, Map.empty)

}

/** @see [[https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#server-variable-object]] */
final class ServerVariable private (
    val default: String,
    val `enum`: Option[Seq[String]],
    val description: Option[String]
) extends Serializable {
  assert(
    `enum`.forall(_.nonEmpty),
    "The enumeration of the values to be used in the substitution must not be empty."
  )

  override def toString = s"ServerVariable($default, ${`enum`}, $description)"

  override def equals(other: Any): Boolean = other match {
    case that: ServerVariable =>
      default == that.default &&
        `enum` == that.`enum` &&
        description == that.description
    case _ => false
  }

  override def hashCode(): Int = Hashing.hash(default, `enum`, description)

  private[this] def copy(
      default: String = default,
      `enum`: Option[Seq[String]] = `enum`,
      description: Option[String] = description
  ): ServerVariable =
    new ServerVariable(default, `enum`, description)

  def withDefault(default: String): ServerVariable = copy(default = default)

  def withEnum(`enum`: Option[Seq[String]]): ServerVariable = copy(`enum` = `enum`)

  def withDescription(description: Option[String]): ServerVariable = copy(description = description)

}

object ServerVariable {
  def apply(default: String): ServerVariable =
    new ServerVariable(default, None, None)

}

final class PathItem private (
    val operations: Map[String, Operation]
) extends Serializable {

  override def toString =
    s"PathItem($operations)"

  override def equals(other: Any): Boolean =
    other match {
      case that: PathItem => operations == that.operations
      case _              => false
    }

  override def hashCode(): Int =
    Hashing.hash(operations)

  def withOperations(operations: Map[String, Operation]): PathItem =
    PathItem(operations)

}

object PathItem {

  def apply(operations: Map[String, Operation]): PathItem =
    new PathItem(operations)

}

final class Components private (
    val schemas: Map[String, Schema],
    val securitySchemes: Map[String, SecurityScheme]
) extends Serializable {

  override def toString: String =
    s"Components($schemas, $securitySchemes)"

  override def equals(other: Any): Boolean =
    other match {
      case that: Components =>
        schemas == that.schemas && securitySchemes == that.securitySchemes
      case _ => false
    }

  override def hashCode(): Int =
    Hashing.hash(schemas, securitySchemes)

  private[this] def copy(
      schemas: Map[String, Schema] = schemas,
      securitySchemes: Map[String, SecurityScheme] = securitySchemes
  ) = new Components(schemas, securitySchemes)

  def withSchemas(schemas: Map[String, Schema]): Components =
    copy(schemas = schemas)

  def withSecuritySchemas(
      securitySchemes: Map[String, SecurityScheme]
  ): Components =
    copy(securitySchemes = securitySchemes)

}

object Components {

  def apply(
      schemas: Map[String, Schema],
      securitySchemes: Map[String, SecurityScheme]
  ): Components =
    new Components(schemas, securitySchemes)

}

final class Operation private (
    val operationId: Option[String],
    val summary: Option[String],
    val description: Option[String],
    val parameters: List[Parameter],
    val requestBody: Option[RequestBody],
    val responses: Map[String, Response],
    val tags: List[Tag],
    val security: List[SecurityRequirement],
    val callbacks: Map[String, Map[String, PathItem]],
    val deprecated: Boolean
) extends Serializable {

  override def toString: String =
    s"Operation($operationId, $summary, $description, $parameters, $requestBody, $responses, $tags, $security, $callbacks, $deprecated)"

  override def equals(other: Any): Boolean =
    other match {
      case that: Operation =>
        operationId == that.operationId && summary == that.summary && description == that.description && parameters == that.parameters &&
          requestBody == that.requestBody && responses == that.responses && tags == that.tags &&
          security == that.security && callbacks == that.callbacks && deprecated == that.deprecated
    }

  override def hashCode(): Int =
    Hashing.hash(
      operationId,
      summary,
      description,
      parameters,
      requestBody,
      responses,
      tags,
      security,
      callbacks,
      deprecated
    )

  private[this] def copy(
      id: Option[String] = operationId,
      summary: Option[String] = summary,
      description: Option[String] = description,
      parameters: List[Parameter] = parameters,
      requestBody: Option[RequestBody] = requestBody,
      responses: Map[String, Response] = responses,
      tags: List[Tag] = tags,
      security: List[SecurityRequirement] = security,
      callbacks: Map[String, Map[String, PathItem]] = callbacks,
      deprecated: Boolean = deprecated
  ): Operation =
    Operation(
      id,
      summary,
      description,
      parameters,
      requestBody,
      responses,
      tags,
      security,
      callbacks,
      deprecated
    )

  def withOperationId(operationId: Option[String]): Operation =
    copy(id = operationId)

  def withSummary(summary: Option[String]): Operation =
    copy(summary = summary)

  def withDescription(description: Option[String]): Operation =
    copy(description = description)

  def withParameters(parameters: List[Parameter]): Operation =
    copy(parameters = parameters)

  def withRequestBody(requestBody: Option[RequestBody]): Operation =
    copy(requestBody = requestBody)

  def withResponses(responses: Map[String, Response]): Operation =
    copy(responses = responses)

  def withTags(tags: List[Tag]): Operation =
    copy(tags = tags)

  def withSecurity(security: List[SecurityRequirement]): Operation =
    copy(security = security)

  def withCallbacks(callbacks: Map[String, Map[String, PathItem]]): Operation =
    copy(callbacks = callbacks)

  def withDeprecated(deprecated: Boolean): Operation =
    copy(deprecated = deprecated)
}

object Operation {

  def apply(
      id: Option[String],
      summary: Option[String],
      description: Option[String],
      parameters: List[Parameter],
      requestBody: Option[RequestBody],
      responses: Map[String, Response],
      tags: List[Tag],
      security: List[SecurityRequirement],
      callbacks: Map[String, Map[String, PathItem]],
      deprecated: Boolean
  ): Operation =
    new Operation(
      id,
      summary,
      description,
      parameters,
      requestBody,
      responses,
      tags,
      security,
      callbacks,
      deprecated
    )

}

final class SecurityRequirement private (
    val name: String,
    val scheme: SecurityScheme,
    val scopes: List[String]
) extends Serializable {

  override def toString: String =
    s"SecurityRequirement($name, $scheme, $scopes)"

  override def equals(other: Any): Boolean =
    other match {
      case that: SecurityRequirement =>
        name == that.name && scheme == that.scheme && scopes == that.scopes
      case _ => false
    }

  override def hashCode(): Int =
    Hashing.hash(name, scheme, scopes)

  private[this] def copy(
      name: String = name,
      scheme: SecurityScheme = scheme,
      scopes: List[String] = scopes
  ): SecurityRequirement =
    new SecurityRequirement(name, scheme, scopes)

  def withName(name: String): SecurityRequirement =
    copy(name = name)

  def withScheme(scheme: SecurityScheme): SecurityRequirement =
    copy(scheme = scheme)

  def withScopes(scopes: List[String]): SecurityRequirement =
    copy(scopes = scopes)
}

object SecurityRequirement {

  def apply(
      name: String,
      scheme: SecurityScheme
  ): SecurityRequirement =
    new SecurityRequirement(name, scheme, Nil)

}

final class RequestBody private (
    val description: Option[String],
    val content: Map[String, MediaType]
) extends Serializable {
  assert(content.nonEmpty)

  override def toString: String =
    s"RequestBody($description, $content)"

  override def equals(other: Any): Boolean =
    other match {
      case that: RequestBody =>
        description == that.description && content == that.content
      case _ => false
    }

  override def hashCode(): Int =
    Hashing.hash(description, content)

  private[this] def copy(
      description: Option[String] = description,
      content: Map[String, MediaType] = content
  ): RequestBody =
    new RequestBody(description, content)

  def withDescription(description: Option[String]): RequestBody =
    copy(description = description)

  def withContent(content: Map[String, MediaType]): RequestBody =
    copy(content = content)
}

object RequestBody {

  def apply(description: Option[String], content: Map[String, MediaType]) =
    new RequestBody(description, content)

}

final class Response private (
    val description: String,
    val headers: Map[String, ResponseHeader],
    val content: Map[String, MediaType]
) extends Serializable {

  override def toString: String =
    s"Response($description, $headers, $content)"

  override def equals(other: Any): Boolean =
    other match {
      case that: Response =>
        description == that.description && headers == that.headers && content == that.content
      case _ => false
    }

  override def hashCode(): Int =
    Hashing.hash(description, headers, content)

  private[this] def copy(
      description: String = description,
      headers: Map[String, ResponseHeader] = headers,
      content: Map[String, MediaType] = content
  ): Response =
    new Response(description, headers, content)

  def withDescription(description: String): Response =
    copy(description = description)

  def withHeaders(headers: Map[String, ResponseHeader]): Response =
    copy(headers = headers)

  def withContent(content: Map[String, MediaType]): Response =
    copy(content = content)
}

object Response {

  def apply(
      description: String,
      headers: Map[String, ResponseHeader],
      content: Map[String, MediaType]
  ): Response =
    new Response(description, headers, content)

}

// Note: request headers don’t need a dedicated class because they are modeled as `Parameter`s
final class ResponseHeader private (
    val required: Boolean,
    val description: Option[String],
    val schema: Schema
) extends Serializable {

  override def toString: String =
    s"ResponseHeader($required, $description, $schema)"

  override def equals(other: Any): Boolean =
    other match {
      case that: ResponseHeader =>
        required == that.required && description == that.description && schema == that.schema
      case _ => false
    }

  override def hashCode(): Int =
    Hashing.hash(required, description, schema)

  private def copy(
      required: Boolean = required,
      description: Option[String] = description,
      schema: Schema = schema
  ): ResponseHeader =
    new ResponseHeader(required, description, schema)

  def withRequired(required: Boolean): ResponseHeader =
    copy(required = required)

  def withDescription(description: Option[String]): ResponseHeader =
    copy(description = description)

  def withSchema(schema: Schema): ResponseHeader =
    copy(schema = schema)

}

object ResponseHeader {

  def apply(
      required: Boolean,
      description: Option[String],
      schema: Schema
  ): ResponseHeader =
    new ResponseHeader(required, description, schema)

}

final class Parameter private (
    val name: String,
    val in: In,
    val required: Boolean,
    val description: Option[String],
    val schema: Schema // not specified in openapi spec but swagger-editor breaks without it for path parameters
) extends Serializable {

  override def toString: String =
    s"Parameter($name, $in, $required, $description, $schema)"

  override def equals(other: Any): Boolean =
    other match {
      case that: Parameter =>
        name == that.name && in == that.in && required == that.required &&
          description == that.description && schema == that.schema
      case _ => false
    }

  override def hashCode(): Int =
    Hashing.hash(name, in, required, description, schema)

  private[this] def copy(
      name: String = name,
      in: In = in,
      required: Boolean = required,
      description: Option[String] = description,
      schema: Schema = schema
  ): Parameter =
    new Parameter(name, in, required, description, schema)

  def withName(name: String): Parameter =
    copy(name = name)

  def withIn(in: In): Parameter =
    copy(in = in)

  def withDescription(description: Option[String]): Parameter =
    copy(description = description)

  def withSchema(schema: Schema): Parameter =
    copy(schema = schema)

}

object Parameter {

  def apply(
      name: String,
      in: In,
      required: Boolean,
      description: Option[String],
      schema: Schema
  ): Parameter =
    new Parameter(name, in, required, description, schema)

}

sealed trait In

object In {
  case object Query extends In
  case object Path extends In
  case object Header extends In
  case object Cookie extends In

  // All the possible values.
  val values: Seq[In] = Query :: Path :: Header :: Cookie :: Nil
}

final class MediaType private (val schema: Option[Schema]) extends Serializable {

  override def toString: String =
    s"Mediatype($schema)"

  override def equals(other: Any): Boolean =
    other match {
      case that: MediaType => schema == that.schema
      case _               => false
    }

  override def hashCode(): Int =
    Hashing.hash(schema)

  def withSchema(schema: Option[Schema]): MediaType = new MediaType(schema)
}

object MediaType {

  def apply(schema: Option[Schema]): MediaType = new MediaType(schema)

}

sealed trait Schema {
  def description: Option[String]
  def example: Option[ujson.Value]
  def default: Option[ujson.Value]
  def title: Option[String]

  /** @return The same schema with its description overridden by the given `description`,
    *         or stay unchanged if this one is empty.
    */
  def withDefinedDescription(description: Option[String]): Schema =
    this match {
      case s: Schema.Object =>
        s.withDescription(description.orElse(s.description))
      case s: Schema.Array =>
        s.withDescription(description.orElse(s.description))
      case s: Schema.Enum =>
        s.withDescription(description.orElse(s.description))
      case s: Schema.Primitive =>
        s.withDescription(description.orElse(s.description))
      case s: Schema.OneOf =>
        s.withDescription(description.orElse(s.description))
      case s: Schema.AllOf =>
        s.withDescription(description.orElse(s.description))
      case s: Schema.Reference =>
        s.withDescription(description.orElse(s.description))
    }

  /** @return The same schema with its default overridden by the given `default`,
    *         or stay unchanged if this one is empty.
    */
  def withDefinedDefault(default: Option[ujson.Value]): Schema =
    this match {
      case s: Schema.Object =>
        s.withDefault(default.orElse(s.default))
      case s: Schema.Array =>
        s.withDefault(default.orElse(s.default))
      case s: Schema.Enum =>
        s.withDefault(default.orElse(s.default))
      case s: Schema.Primitive =>
        s.withDefault(default.orElse(s.default))
      case s: Schema.OneOf =>
        s.withDefault(default.orElse(s.default))
      case s: Schema.AllOf =>
        s.withDefault(default.orElse(s.default))
      case s: Schema.Reference =>
        s.withDefault(default.orElse(s.default))
    }
}

object Schema {

  final class Object private (
      val properties: List[Property],
      val additionalProperties: Option[Schema],
      val description: Option[String],
      val example: Option[ujson.Value],
      val title: Option[String],
      val default: Option[ujson.Value]
  ) extends Schema
      with Serializable {

    override def toString: String =
      s"Object($properties, $additionalProperties, $description, $example, $title, $default)"

    override def equals(other: Any): Boolean =
      other match {
        case that: Object =>
          properties == that.properties && additionalProperties == that.additionalProperties &&
            description == that.description && example == that.example &&
            title == that.title && default == that.default
        case _ => false
      }

    override def hashCode(): Int =
      Hashing.hash(
        properties,
        additionalProperties,
        description,
        example,
        title,
        default
      )

    private[this] def copy(
        properties: List[Property] = properties,
        additionalProperties: Option[Schema] = additionalProperties,
        description: Option[String] = description,
        example: Option[ujson.Value] = example,
        title: Option[String] = title,
        default: Option[ujson.Value] = default
    ): Object =
      new Object(properties, additionalProperties, description, example, title, default)

    def withProperty(properties: List[Property]): Object =
      copy(properties = properties)

    def withAdditionalProperties(additionalProperties: Option[Schema]): Object =
      copy(additionalProperties = additionalProperties)

    def withDescription(description: Option[String]): Object =
      copy(description = description)

    def withExample(example: Option[ujson.Value]): Object =
      copy(example = example)

    def withTitle(title: Option[String]): Object =
      copy(title = title)

    def withDefault(default: Option[ujson.Value]): Object =
      copy(default = default)
  }

  object Object {

    def apply(
        properties: List[Property],
        additionalProperties: Option[Schema],
        description: Option[String],
        example: Option[ujson.Value],
        title: Option[String]
    ): Object =
      new Object(properties, additionalProperties, description, example, title, None)

  }

  final class Array private (
      val elementType: Either[Schema, List[Schema]],
      val description: Option[String],
      val example: Option[ujson.Value],
      val title: Option[String],
      val default: Option[ujson.Value]
  ) extends Schema
      with Serializable {

    override def toString: String =
      s"Array($elementType, $description, $example, $title, $default)"

    override def equals(other: Any): Boolean =
      other match {
        case that: Array =>
          elementType == that.elementType && description == that.description &&
            example == that.example && title == that.title && default == that.default
        case _ => false
      }

    override def hashCode(): Int =
      Hashing.hash(elementType, description, example, title, default)

    private[this] def copy(
        elementType: Either[Schema, List[Schema]] = elementType,
        description: Option[String] = description,
        example: Option[ujson.Value] = example,
        title: Option[String] = title,
        default: Option[ujson.Value] = default
    ): Array =
      new Array(elementType, description, example, title, default)

    def withElementType(elementType: Either[Schema, List[Schema]]): Array =
      copy(elementType = elementType)

    def withDescription(description: Option[String]): Array =
      copy(description = description)

    def withExample(example: Option[ujson.Value]): Array =
      copy(example = example)

    def withTitle(title: Option[String]): Array =
      copy(title = title)

    def withDefault(default: Option[ujson.Value]): Array =
      copy(default = default)

  }

  object Array {

    def apply(
        elementType: Either[Schema, List[Schema]],
        description: Option[String],
        example: Option[ujson.Value],
        title: Option[String]
    ): Array =
      new Array(elementType, description, example, title, None)

  }

  final class Enum private (
      val elementType: Schema,
      val values: List[ujson.Value],
      val description: Option[String],
      val example: Option[ujson.Value],
      val title: Option[String],
      val default: Option[ujson.Value]
  ) extends Schema
      with Serializable {

    override def toString: String =
      s"Enum($elementType, $values, $description, $example, $title, $default)"

    override def equals(other: Any): Boolean =
      other match {
        case that: Enum =>
          elementType == that.elementType && values == that.values && description == that.description &&
            example == that.example && title == that.title && default == that.default
        case _ => false
      }

    override def hashCode(): Int =
      Hashing.hash(elementType, values, description, example, title, default)

    private[this] def copy(
        elementType: Schema = elementType,
        values: List[ujson.Value] = values,
        description: Option[String] = description,
        example: Option[ujson.Value] = example,
        title: Option[String] = title,
        default: Option[ujson.Value] = default
    ): Enum =
      new Enum(elementType, values, description, example, title, default)

    def withElementType(elementType: Schema): Enum =
      copy(elementType = elementType)

    def withValues(values: List[ujson.Value]): Enum =
      copy(values = values)

    def withDescription(description: Option[String]): Enum =
      copy(description = description)

    def withExample(example: Option[ujson.Value]): Enum =
      copy(example = example)

    def withTitle(title: Option[String]): Enum =
      copy(title = title)

    def withDefault(default: Option[ujson.Value]): Enum =
      copy(default = default)
  }

  object Enum {

    def apply(
        elementType: Schema,
        values: List[ujson.Value],
        description: Option[String],
        example: Option[ujson.Value],
        title: Option[String]
    ): Enum = new Enum(elementType, values, description, example, title, None)

  }

  final class Property private (
      val name: String,
      val schema: Schema,
      val isRequired: Boolean,
      val defaultValue: Option[ujson.Value],
      val description: Option[String]
  ) extends Serializable {

    def this(
        name: String,
        schema: Schema,
        isRequired: Boolean,
        description: Option[String]
    ) = this(name, schema, isRequired, None, description)

    override def toString: String =
      s"Property($name, $schema, $isRequired, $defaultValue, $description)"

    override def equals(other: Any): Boolean =
      other match {
        case that: Property =>
          name == that.name && schema == that.schema && isRequired == that.isRequired &&
            defaultValue == that.defaultValue && description == that.description
        case _ => false
      }

    override def hashCode(): Int =
      Hashing.hash(name, schema, isRequired, defaultValue, description)

    private[this] def copy(
        name: String = name,
        schema: Schema = schema,
        isRequired: Boolean = isRequired,
        defaultValue: Option[ujson.Value] = defaultValue,
        description: Option[String] = description
    ): Property =
      new Property(name, schema, isRequired, defaultValue, description)

    def withName(name: String): Property =
      copy(name = name)

    def withSchema(schema: Schema): Property =
      copy(schema = schema)

    def withIsRequired(isRequired: Boolean): Property =
      copy(isRequired = isRequired)

    def withDefaultValue(defaultValue: Option[ujson.Value]): Property =
      copy(defaultValue = defaultValue)

    def withDescription(description: Option[String]): Property =
      copy(description = description)
  }

  object Property {

    def apply(
        name: String,
        schema: Schema,
        isRequired: Boolean,
        defaultValue: Option[ujson.Value],
        description: Option[String]
    ): Property = new Property(name, schema, isRequired, defaultValue, description)

    def apply(
        name: String,
        schema: Schema,
        isRequired: Boolean,
        description: Option[String]
    ): Property = new Property(name, schema, isRequired, None, description)
  }

  final class Primitive private (
      val name: String,
      val format: Option[String],
      val description: Option[String],
      val example: Option[ujson.Value],
      val title: Option[String],
      val default: Option[ujson.Value],
      val minimum: Option[Double],
      val exclusiveMinimum: Option[Boolean],
      val maximum: Option[Double],
      val exclusiveMaximum: Option[Boolean],
      val multipleOf: Option[Double]
  ) extends Schema
      with Serializable {

    override def toString: String =
      s"Primitive($name, $format, $description, $example, $title, $default, $minimum, $exclusiveMinimum, $maximum, $exclusiveMaximum, $multipleOf)"

    override def equals(other: Any): Boolean =
      other match {
        case that: Primitive =>
          name == that.name && format == that.format && description == that.description &&
            example == that.example && title == that.title && default == that.default &&
            minimum == that.minimum && exclusiveMinimum == that.exclusiveMinimum &&
            maximum == that.maximum && exclusiveMaximum == that.exclusiveMaximum &&
            multipleOf == that.multipleOf
        case _ => false
      }

    override def hashCode(): Int =
      Hashing.hash(
        name,
        format,
        description,
        example,
        title,
        default,
        minimum,
        exclusiveMinimum,
        maximum,
        exclusiveMaximum,
        multipleOf
      )

    private[this] def copy(
        name: String = name,
        format: Option[String] = format,
        description: Option[String] = description,
        example: Option[ujson.Value] = example,
        title: Option[String] = title,
        default: Option[ujson.Value] = default,
        minimum: Option[Double] = minimum,
        exclusiveMinimum: Option[Boolean] = exclusiveMinimum,
        maximum: Option[Double] = maximum,
        exclusiveMaximum: Option[Boolean] = exclusiveMaximum,
        multipleOf: Option[Double] = multipleOf
    ): Primitive =
      new Primitive(
        name,
        format,
        description,
        example,
        title,
        default,
        minimum,
        exclusiveMinimum,
        maximum,
        exclusiveMaximum,
        multipleOf
      )

    def withName(name: String): Primitive =
      copy(name = name)

    def withFormat(format: Option[String]): Primitive =
      copy(format = format)

    def withDescription(description: Option[String]): Primitive =
      copy(description = description)

    def withExample(example: Option[ujson.Value]): Primitive =
      copy(example = example)

    def withTitle(title: Option[String]): Primitive =
      copy(title = title)

    def withDefault(default: Option[ujson.Value]): Primitive =
      copy(default = default)

    def withMinimum(minimum: Option[Double]): Primitive =
      copy(minimum = minimum)

    def withExclusiveMinimum(exclusiveMinimum: Option[Boolean]): Primitive =
      copy(exclusiveMinimum = exclusiveMinimum)

    def withMaximum(maximum: Option[Double]): Primitive =
      copy(maximum = maximum)

    def withExclusiveMaximum(exclusiveMaximum: Option[Boolean]): Primitive =
      copy(exclusiveMaximum = exclusiveMaximum)

    def withMultipleOf(multipleOf: Option[Double]): Primitive =
      copy(multipleOf = multipleOf)
  }

  object Primitive {

    def apply(
        name: String,
        format: Option[String],
        description: Option[String],
        example: Option[ujson.Value],
        title: Option[String]
    ): Primitive =
      new Primitive(name, format, description, example, title, None, None, None, None, None, None)

  }

  final class OneOf private (
      val alternatives: Alternatives,
      val description: Option[String],
      val example: Option[ujson.Value],
      val title: Option[String],
      val default: Option[ujson.Value]
  ) extends Schema
      with Serializable {

    override def toString: String =
      s"OneOf($alternatives, $description, $example, $title, $default)"

    override def equals(other: Any): Boolean =
      other match {
        case that: OneOf =>
          alternatives == that.alternatives && description == that.description &&
            example == that.example && title == that.title && default == that.default
        case _ => false
      }

    override def hashCode(): Int =
      Hashing.hash(alternatives, description, example, title, default)

    private[this] def copy(
        alternatives: Alternatives = alternatives,
        description: Option[String] = description,
        example: Option[ujson.Value] = example,
        title: Option[String] = title,
        default: Option[ujson.Value] = default
    ): OneOf =
      new OneOf(alternatives, description, example, title, default)

    def withAlternatives(alternatives: Alternatives): OneOf =
      copy(alternatives = alternatives)

    def withDescription(description: Option[String]): OneOf =
      copy(description = description)

    def withExample(example: Option[ujson.Value]): OneOf =
      copy(example = example)

    def withTitle(title: Option[String]): OneOf =
      copy(title = title)

    def withDefault(default: Option[ujson.Value]): OneOf =
      copy(default = default)

  }

  object OneOf {

    def apply(
        alternatives: Alternatives,
        description: Option[String],
        example: Option[ujson.Value],
        title: Option[String]
    ): OneOf = new OneOf(alternatives, description, example, title, None)

  }

  sealed trait Alternatives

  final class DiscriminatedAlternatives private (
      val discriminatorFieldName: String,
      val alternatives: List[(String, Schema)]
  ) extends Alternatives
      with Serializable {

    override def toString: String =
      s"DiscriminatedAlternatives($discriminatorFieldName, $alternatives)"

    override def equals(other: Any): Boolean =
      other match {
        case that: DiscriminatedAlternatives =>
          discriminatorFieldName == that.discriminatorFieldName && alternatives == that.alternatives
        case _ =>
          false
      }

    override def hashCode(): Int =
      Hashing.hash(discriminatorFieldName, alternatives)

    private[this] def copy(
        discriminatorFieldName: String = discriminatorFieldName,
        alternatives: List[(String, Schema)] = alternatives
    ): DiscriminatedAlternatives =
      new DiscriminatedAlternatives(discriminatorFieldName, alternatives)

    def withDiscriminatorFieldName(
        discriminiatorFieldName: String
    ): DiscriminatedAlternatives =
      copy(discriminatorFieldName = discriminiatorFieldName)

    def withAlternatives(
        alternatives: List[(String, Schema)]
    ): DiscriminatedAlternatives =
      copy(alternatives = alternatives)
  }

  object DiscriminatedAlternatives {

    def apply(
        discriminatorFieldName: String,
        alternatives: List[(String, Schema)]
    ): DiscriminatedAlternatives =
      new DiscriminatedAlternatives(discriminatorFieldName, alternatives)

  }

  final class EnumeratedAlternatives private (
      val alternatives: List[Schema]
  ) extends Alternatives
      with Serializable {

    override def toString: String =
      s"EnumeratedAlternatives($alternatives)"

    override def equals(other: Any): Boolean =
      other match {
        case that: EnumeratedAlternatives => alternatives == that.alternatives
        case _                            => false
      }

    override def hashCode(): Int =
      Hashing.hash(alternatives)

    def withAlternatives(alternatives: List[Schema]): EnumeratedAlternatives =
      new EnumeratedAlternatives(alternatives)
  }

  object EnumeratedAlternatives {

    def apply(alternatives: List[Schema]): EnumeratedAlternatives =
      new EnumeratedAlternatives(alternatives)

  }

  final class AllOf private (
      val schemas: List[Schema],
      val description: Option[String],
      val example: Option[ujson.Value],
      val title: Option[String],
      val default: Option[ujson.Value]
  ) extends Schema
      with Serializable {

    override def toString: String =
      s"AllOf($schemas, $description, $example, $title, $default)"

    override def equals(other: Any): Boolean =
      other match {
        case that: AllOf =>
          schemas == that.schemas && description == that.description &&
            example == that.example && title == that.title && default == that.default
        case _ => false
      }

    override def hashCode(): Int =
      Hashing.hash(schemas, description, example, title, default)

    private[this] def copy(
        schemas: List[Schema] = schemas,
        description: Option[String] = description,
        example: Option[ujson.Value] = example,
        title: Option[String] = title,
        default: Option[ujson.Value] = default
    ): AllOf =
      new AllOf(schemas, description, example, title, default)

    def withSchemas(schemas: List[Schema]): AllOf =
      copy(schemas = schemas)

    def withDescription(description: Option[String]): AllOf =
      copy(description = description)

    def withExample(example: Option[ujson.Value]): AllOf =
      copy(example = example)

    def withTitle(title: Option[String]): AllOf =
      copy(title = title)

    def withDefault(default: Option[ujson.Value]): AllOf =
      copy(default = default)

  }

  object AllOf {

    def apply(
        schemas: List[Schema],
        description: Option[String],
        example: Option[ujson.Value],
        title: Option[String]
    ): AllOf = new AllOf(schemas, description, example, title, None)

  }

  final class Reference private (
      val name: String,
      val original: Option[Schema],
      val description: Option[String],
      val example: Option[ujson.Value],
      val title: Option[String], // you probably want the title on the original schema!
      val default: Option[ujson.Value]
  ) extends Schema
      with Serializable {

    override def toString: String =
      s"Reference($name, $original, $description, $example, $title, $default)"

    override def equals(other: Any): Boolean =
      other match {
        case that: Reference =>
          name == that.name && original == that.original && description == that.description &&
            example == that.example && title == that.title && default == that.default
        case _ => false
      }

    override def hashCode(): Int =
      Hashing.hash(name, original, description, example, title, default)

    private[this] def copy(
        name: String = name,
        original: Option[Schema] = original,
        description: Option[String] = description,
        example: Option[ujson.Value] = example,
        title: Option[String] = title,
        default: Option[ujson.Value] = default
    ) = new Reference(name, original, description, example, title, default)

    def withName(name: String): Reference = copy(name = name)

    def withOriginal(original: Option[Schema]): Reference =
      copy(original = original)

    def withDescription(description: Option[String]): Reference =
      copy(description = description)

    def withExample(example: Option[ujson.Value]): Reference =
      copy(example = example)

    def withTitle(description: Option[String]): Reference =
      copy(description = description)

    def withDefault(default: Option[ujson.Value]): Reference =
      copy(default = default)
  }

  object Reference {

    def apply(
        name: String,
        original: Option[Schema],
        description: Option[String]
    ): Reference = new Reference(name, original, description, None, None, None)

    def toRefPath(name: String): String =
      s"#/components/schemas/$name"
  }

  val simpleUUID: Primitive = Primitive("string", format = Some("uuid"), None, None, None)
  val simpleString: Primitive = Primitive("string", None, None, None, None)
  val simpleInteger: Primitive = Primitive("integer", format = Some("int32"), None, None, None)
  val simpleLong: Primitive = Primitive("integer", format = Some("int64"), None, None, None)
  val simpleBoolean: Primitive = Primitive("boolean", None, None, None, None)
  val simpleNumber: Primitive = Primitive("number", format = Some("double"), None, None, None)

}

final class SecurityScheme private (
    val `type`: String, // TODO This should be a sealed trait, the `type` field should only exist in the JSON representation
    val description: Option[String],
    val name: Option[String],
    val in: Option[String], // TODO Create a typed enumeration
    val scheme: Option[String],
    val bearerFormat: Option[String]
) extends Serializable {

  override def toString: String =
    s"SecurityScheme(${`type`}, $description, $name, $in, $scheme, $bearerFormat)"

  override def equals(other: Any): Boolean =
    other match {
      case that: SecurityScheme =>
        `type` == that.`type` && description == that.description && name == that.name &&
          in == that.in && scheme == that.scheme && bearerFormat == that.bearerFormat
      case _ =>
        false
    }

  override def hashCode(): Int =
    Hashing.hash(`type`, description, name, in, scheme, bearerFormat)

  private[this] def copy(
      `type`: String = `type`,
      description: Option[String] = description,
      name: Option[String] = name,
      in: Option[String] = in,
      scheme: Option[String] = scheme,
      bearerFormat: Option[String] = bearerFormat
  ) = new SecurityScheme(`type`, description, name, in, scheme, bearerFormat)

  def withType(tpe: String): SecurityScheme =
    copy(`type` = tpe)

  def withDescription(description: Option[String]): SecurityScheme =
    copy(description = description)

  def withName(name: Option[String]): SecurityScheme =
    copy(name = name)

  def withIn(in: Option[String]): SecurityScheme =
    copy(in = in)

  def withScheme(scheme: Option[String]): SecurityScheme =
    copy(scheme = scheme)

  def withBearerFormat(bearerFormat: Option[String]): SecurityScheme =
    copy(bearerFormat = bearerFormat)

}

object SecurityScheme {

  def apply(
      `type`: String,
      description: Option[String],
      name: Option[String],
      in: Option[String],
      scheme: Option[String],
      bearerFormat: Option[String]
  ): SecurityScheme =
    new SecurityScheme(`type`, description, name, in, scheme, bearerFormat)

  def httpBasic: SecurityScheme =
    SecurityScheme(
      `type` = "http",
      description = Some("Http Basic Authentication"),
      name = None,
      in = None,
      scheme = Some("basic"),
      bearerFormat = None
    )
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy