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

smithy4s.http.internals.SchemaVisitorMetadataWriter.scala Maven / Gradle / Ivy

There is a newer version: 0.19.0-41-91762fb
Show newest version
/*
 *  Copyright 2021-2024 Disney Streaming
 *
 *  Licensed under the Tomorrow Open Source Technology License, Version 1.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *     https://disneystreaming.github.io/TOST-1.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package smithy4s
package http
package internals

import smithy.api.HttpQueryParams
import smithy4s.http.HttpBinding
import smithy4s.http.internals.MetaEncode._
import smithy4s.schema.{
  CollectionTag,
  EnumTag,
  EnumValue,
  Field,
  Primitive,
  SchemaVisitor
}
import smithy4s.schema.Alt
import smithy4s.schema.CompilationCache
import java.util.Base64

/**
 * This schema visitor works on data that is annotated with :
 * - smithy.api.httpLabel
 * - smithy.api.httpHeader
 * - smithy.api.httpPrefixHeaders
 * - smithy.api.httpQuery
 * - smithy.api.httpQueryParams
 *
 * As such, assumptions are made using the information of what types can be
 * annotated in the smithy specs.
 *
 */
class SchemaVisitorMetadataWriter(
    val cache: CompilationCache[MetaEncode],
    commaDelimitedEncoding: Boolean,
    explicitDefaultsEncoding: Boolean
) extends SchemaVisitor.Cached[MetaEncode] {
  self =>
  override def primitive[P](
      shapeId: ShapeId,
      hints: Hints,
      tag: Primitive[P]
  ): MetaEncode[P] = {
    val hasMedia = hints.has(smithy.api.MediaType)
    Primitive.stringWriter(tag, hints) match {
      case Some(write) if hasMedia =>
        StringValueMetaEncode(
          write andThen (str =>
            Base64.getEncoder().encodeToString(str.getBytes())
          )
        )
      case Some(write) => StringValueMetaEncode(write)
      case None        => MetaEncode.empty[P]
    }
  }

  override def collection[C[_], A](
      shapeId: ShapeId,
      hints: Hints,
      tag: CollectionTag[C],
      member: Schema[A]
  ): MetaEncode[C[A]] = {
    val amendedMember = member.addHints(httpHints(hints))
    SchemaVisitorHeaderMerge(amendedMember) match {
      case Some(toMergeableValue) if commaDelimitedEncoding =>
        StringValueMetaEncode[C[A]] { c =>
          tag.iterator(c).map(toMergeableValue).mkString(", ")
        }
      case _ =>
        self(amendedMember) match {
          case StringValueMetaEncode(f) =>
            StringListMetaEncode[C[A]](c => tag.iterator(c).map(f).toList)
          case _ => MetaEncode.empty
        }
    }

  }

  override def option[A](schema: Schema[A]): MetaEncode[Option[A]] =
    self(schema) match {
      case StringValueMetaEncode(f) =>
        StringValueMetaEncode {
          case Some(value) => f(value)
          case None        => ""
        }
      case StringListMetaEncode(f) =>
        StringListMetaEncode {
          case Some(value) => f(value)
          case None        => List.empty
        }
      case StringMapMetaEncode(f) =>
        StringMapMetaEncode {
          case Some(value) => f(value)
          case None        => Map.empty
        }
      case StringListMapMetaEncode(f) =>
        StringListMapMetaEncode {
          case Some(value) => f(value)
          case None        => Map.empty
        }
      case EmptyMetaEncode        => EmptyMetaEncode
      case StructureMetaEncode(_) => EmptyMetaEncode
    }

  override def map[K, V](
      shapeId: ShapeId,
      hints: Hints,
      key: Schema[K],
      value: Schema[V]
  ): MetaEncode[Map[K, V]] = {
    (self(key), self(value.addHints(httpHints(hints)))) match {
      case (StringValueMetaEncode(keyF), StringValueMetaEncode(valueF)) =>
        StringMapMetaEncode(map =>
          map.map { case (k, v) =>
            (keyF(k), valueF(v))
          }
        )
      case (StringValueMetaEncode(keyF), StringListMetaEncode(valueF)) =>
        StringListMapMetaEncode(map =>
          map.map { case (k, v) =>
            (keyF(k), valueF(v))
          }
        )
      case _ => MetaEncode.empty
    }
  }

  override def enumeration[E](
      shapeId: ShapeId,
      hints: Hints,
      tag: EnumTag[E],
      values: List[EnumValue[E]],
      total: E => EnumValue[E]
  ): MetaEncode[E] =
    tag match {
      case EnumTag.IntEnum() =>
        StringValueMetaEncode(e => total(e).intValue.toString())
      case _ =>
        StringValueMetaEncode(e => total(e).stringValue)
    }

  override def struct[S](
      shapeId: ShapeId,
      hints: Hints,
      fields: Vector[Field[S, _]],
      make: IndexedSeq[Any] => S
  ): MetaEncode[S] = {
    def encodeField[A](
        field: Field[S, A]
    ): Option[(Metadata, S) => Metadata] = {
      val fieldHints = field.hints
      HttpBinding
        .fromHints(field.label, fieldHints, hints)
        .map { binding =>
          val encoder = self(field.schema.addHints(Hints(binding)))
          val updateFunction = encoder.updateMetadata(binding)
          (metadata, s) =>
            if (explicitDefaultsEncoding)
              field.get(s) match {
                case None => metadata
                case _    => updateFunction(metadata, field.get(s))
              }
            else
              field.getUnlessDefault(s) match {
                case Some(nonDefaultA) => updateFunction(metadata, nonDefaultA)
                case None              => metadata
              }
        }
    }
    // pull out the query params field as it must be applied last to the metadata
    val (queryParamFieldVec, theRest) =
      fields.partition(_.memberHints.has[HttpQueryParams])
    val queryParams =
      queryParamFieldVec.flatMap(field => encodeField(field)).headOption
    val updateFunctions = theRest.flatMap(field => encodeField(field))

    StructureMetaEncode(s => {
      //  this is a non commutative operation
      val metadata =
        updateFunctions.foldLeft(Metadata.empty)((metadata, updateFunction) =>
          updateFunction(metadata, s)
        )
      queryParams.fold(metadata)(updateFunction => updateFunction(metadata, s))
    })
  }

  override def union[U](
      shapeId: ShapeId,
      hints: Hints,
      alternatives: Vector[Alt[U, _]],
      dispatcher: Alt.Dispatcher[U]
  ): MetaEncode[U] = MetaEncode.empty

  override def biject[A, B](
      schema: Schema[A],
      bijection: Bijection[A, B]
  ): MetaEncode[B] = self(schema).contramap(bijection.from)

  override def refine[A, B](
      schema: Schema[A],
      refinement: Refinement[A, B]
  ): MetaEncode[B] = self(schema).contramap(refinement.from)

  override def lazily[A](suspend: Lazy[Schema[A]]): MetaEncode[A] =
    MetaEncode.empty
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy