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

ai.mantik.elements.MantikHeader.scala Maven / Gradle / Ivy

The newest version!
/*
 * This file is part of the Mantik Project.
 * Copyright (c) 2020-2021 Mantik UG (Haftungsbeschränkt)
 * Authors: See AUTHORS file
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License version 3.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.
 *
 * Additionally, the following linking exception is granted:
 *
 * If you modify this Program, or any covered work, by linking or
 * combining it with other code, such other code is not for that reason
 * alone subject to any of the requirements of the GNU Affero GPL
 * version 3.
 *
 * You can be released from the requirements of the license by purchasing
 * a commercial license.
 */
package ai.mantik.elements

import ai.mantik.ds.element.SingleElementBundle
import ai.mantik.elements.errors.{ErrorCodes, InvalidMantikHeaderException, MantikException}
import ai.mantik.elements.meta.{MetaJson, MetaVariableException}
import io.circe.Decoder.Result
import io.circe.syntax._
import io.circe.yaml.{Printer, parser => YamlParser}
import io.circe._

import scala.reflect.ClassTag

/**
  * A MantikHeader file. Contains one Mantik Definition together with it's JSON representation.
  *
  * @param definition the base definition of the Mantik Item
  * @param metaJson   the JSON source of the item and it's meta variables
  * @param header     common optional meta fields of a MantikHeader (e.g. Name, Version, ...)
  *
  * Note: MetaJson and MantikHeaderMeta both have meta in their name, but are completely different things.
  */
case class MantikHeader[T <: MantikDefinition](
    definition: T,
    metaJson: MetaJson,
    header: MantikHeaderMeta
) {

  /** Returns the definition, if applicable. */
  def definitionAs[T <: MantikDefinition](implicit ct: ClassTag[T]): Either[MantikException, T] =
    cast[T].map(_.definition)

  def cast[T <: MantikDefinition](implicit ct: ClassTag[T]): Either[MantikException, MantikHeader[T]] = {
    definition match {
      case x: T => Right(MantikHeader[T](x, metaJson, header))
      case _ =>
        Left(
          ErrorCodes.MantikItemWrongType.toException(
            s"Expected ${ct.runtimeClass.getSimpleName}, got ${definition.getClass.getSimpleName}"
          )
        )
    }
  }

  /** Returns Yaml code */
  def toYaml: String = {
    Printer(preserveOrder = true).pretty(toJsonValue)
  }

  /** Returns Json code. */
  def toJson: String = toJsonValue.spaces2

  /** Returns the json value (before converting to string) */
  def toJsonValue: Json = metaJson.asJson

  override def toString: String = {
    val bridge = definition match {
      case b: MantikDefinitionWithBridge => Some(b.bridge)
      case _                             => None
    }
    s"MantikHeader(${definition.kind},bridge=${bridge},name=${header.name})"
  }

  /**
    * Update Meta Variable Values
    */
  @throws[MetaVariableException]("If a value is missing or of wrong type or not changeable.")
  def withMetaValues(values: (String, SingleElementBundle)*): MantikHeader[T] = {
    val updatedJson = metaJson.withMetaValues(values: _*)
    val resultCandidate = for {
      parsed <- MantikHeader.parseMetaJson(updatedJson)
      castedDefinition = parsed.definition.asInstanceOf[T]
    } yield MantikHeader(castedDefinition, parsed.metaJson, parsed.header)
    // parsing errors should not happen much (but is possible, types can go invalid)
    resultCandidate match {
      case Left(error)  => throw new MetaVariableException("Could not reparse with changed meta values ", error)
      case Right(value) => value
    }
  }

  /** Update Meta values of the Mantik Header.
    * (Updates the MetaJson accordingly)
    */
  def withMantikHeaderMeta(meta: MantikHeaderMeta): MantikHeader[T] = {
    val updatedJson = toJsonValue.deepMerge(meta.asJson).asObject.getOrElse {
      throw new IllegalStateException(s"Meta JSON should always be an object")
    }
    copy(
      header = meta,
      metaJson = metaJson.copy(
        sourceJson = updatedJson
      )
    )
  }

  /** Return violations (note: cannot spot bridge-related violations) */
  def violations: Seq[String] = {
    val mantikId = header.id
    mantikId.map(_.violations).getOrElse(Nil)
  }
}

object MantikHeader {

  /** Generates a MantikHeader from pure Definition, automatically serializing to JSON. */
  def pure[T <: MantikDefinition](definition: T): MantikHeader[T] = {
    MantikHeader(
      definition,
      MetaJson(
        metaVariables = Nil,
        missingMetaVariables = true,
        sourceJson = (definition: MantikDefinition).asJsonObject
      ),
      header = MantikHeaderMeta()
    )
  }

  /** Parse a YAML File. */
  def fromYaml(content: String): Either[InvalidMantikHeaderException, MantikHeader[MantikDefinition]] = {
    fromYamlWithoutCheck(content).flatMap { mantikHeader =>
      mantikHeader.violations match {
        case s if s.isEmpty => Right(mantikHeader)
        case violations     => Left(new InvalidMantikHeaderException(s"Invalid MantikHeader: ${violations.mkString(",")}"))
      }
    }
  }

  /** Parse a YAML file without further checking of violations. */
  def fromYamlWithoutCheck(content: String): Either[InvalidMantikHeaderException, MantikHeader[MantikDefinition]] = {
    YamlParser.parse(content) match {
      case Left(error) => Left(InvalidMantikHeaderException.wrap(error))
      case Right(json) => parseSingleDefinition(json)
    }
  }

  /** Parse a YAML File, expecting a single definition only. */
  def fromYamlWithType[T <: MantikDefinition](
      content: String
  )(implicit classTag: ClassTag[T]): Either[MantikException, MantikHeader[T]] = {
    for {
      parsed <- fromYaml(content)
      casted <- parsed.cast[T]
    } yield casted
  }

  def parseSingleDefinition(json: Json): Either[InvalidMantikHeaderException, MantikHeader[MantikDefinition]] = {
    json.as[MetaJson].flatMap(parseMetaJson).left.map {
      InvalidMantikHeaderException.wrap
    }
  }

  def parseMetaJson(metaJson: MetaJson): Either[InvalidMantikHeaderException, MantikHeader[MantikDefinition]] = {
    (for {
      applied <- metaJson.appliedJson.left.map { error => DecodingFailure(error, Nil) }
      definition <- applied.as[MantikDefinition]
      header <- applied.as[MantikHeaderMeta]
    } yield MantikHeader(definition, metaJson, header)).left.map(InvalidMantikHeaderException.wrap)
  }

  /** Generate the mantikHeader for a trained algorithm out of a trainable algorithm definition. */
  def generateTrainedMantikHeader(
      trainable: MantikHeader[TrainableAlgorithmDefinition]
  ): Either[MantikException, MantikHeader[AlgorithmDefinition]] = {
    val trainedBridge = trainable.definition.trainedBridge.getOrElse(
      trainable.definition.bridge // if no override given, use the same bridge
    )
    val updatedJsonObject = trainable.metaJson.withFixedVariables.copy(
      sourceJson = trainable.metaJson.sourceJson
        .remove("name")
        .remove("version")
        .remove("trainedBridge")
        .add("bridge", trainedBridge.asJson)
        .add("kind", Json.fromString(MantikDefinition.AlgorithmKind))
    )
    MantikHeader.parseMetaJson(updatedJsonObject).flatMap(_.cast[AlgorithmDefinition])
  }

  /** Encodes a MantikHeader to it's json value. */
  implicit def encoder[T <: MantikDefinition]: Encoder.AsObject[MantikHeader[T]] =
    new Encoder.AsObject[MantikHeader[T]] {
      override def encodeObject(a: MantikHeader[T]): JsonObject = a.metaJson.asJsonObject
    }

  /** Decodes a MantikHeader from JSON. */
  implicit def decoder[T <: MantikDefinition: ClassTag]: Decoder[MantikHeader[T]] = new Decoder[MantikHeader[T]] {
    override def apply(c: HCursor): Result[MantikHeader[T]] = {
      val result = for {
        metaJson <- c.as[MetaJson]
        parsed <- MantikHeader.parseMetaJson(metaJson)
        casted <- parsed.cast[T]
      } yield casted
      result match {
        case Left(e: DecodingFailure) => Left(e)
        case Left(other)              => new Left(DecodingFailure(other.getMessage, Nil))
        case Right(ok)                => Right(ok)
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy