blob: c6b30c8378978e636f4e8e253b4f198c52001c96 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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 org.apache.openwhisk.core.entity
import akka.http.scaladsl.model.ContentType
import spray.json.DefaultJsonProtocol._
import spray.json._
import org.apache.openwhisk.core.entity.size._
import scala.util.Try
object Attachments {
/**
* A marker for a field that is either inlined in an entity, or a reference
* to an attachment. In the case where the value is inlined, it (de)serializes
* to the same value as if it weren't wrapped.
*
* Note that such fields may be defined at any level of nesting in an entity,
* but the attachments will always be top-level. The logic for actually retrieving
* an attachment therefore must be separate for all use cases.
*/
sealed trait Attachment[+T]
case class Inline[T](value: T) extends Attachment[T]
case class Attached(attachmentName: String,
attachmentType: ContentType,
length: Option[Long] = None,
digest: Option[String] = None)
extends Attachment[Nothing]
// Attachments are considered free because the name/content type are system determined
// and a size check for the content is done during create/update
implicit class SizeAttachment[T](a: Attachment[T])(implicit ev: T => SizeConversion) extends SizeConversion {
def sizeIn(unit: SizeUnits.Unit): ByteSize = a match {
case Inline(v) => (v: SizeConversion).sizeIn(unit)
case _ => 0.bytes
}
}
implicit class OptionSizeAttachment[T](a: Option[Attachment[T]])(implicit ev: T => SizeConversion)
extends SizeConversion {
def sizeIn(unit: SizeUnits.Unit): ByteSize = a match {
case Some(Inline(v)) => (v: SizeConversion).sizeIn(unit)
case _ => 0.bytes
}
}
object Attached {
implicit val serdes = {
implicit val contentTypeSerdes = new RootJsonFormat[ContentType] {
override def write(c: ContentType) = JsString(c.value)
override def read(js: JsValue): ContentType = {
Try(js.convertTo[String]).toOption.flatMap(ContentType.parse(_).toOption).getOrElse {
throw new DeserializationException("Could not deserialize content-type")
}
}
}
jsonFormat4(Attached.apply)
}
}
implicit def serdes[T: JsonFormat] = new JsonFormat[Attachment[T]] {
val sub = implicitly[JsonFormat[T]]
def write(a: Attachment[T]): JsValue = a match {
case Inline(v) => sub.write(v)
case a: Attached => Attached.serdes.write(a)
}
def read(js: JsValue): Attachment[T] =
Try {
Inline(sub.read(js))
} recover {
case _: DeserializationException => Attached.serdes.read(js)
} getOrElse {
throw new DeserializationException("Could not deserialize as attachment record: " + js)
}
}
}