Skip to content

Instantly share code, notes, and snippets.

@umbreak
Created December 22, 2020 09:45
Show Gist options
  • Save umbreak/6dde957880db05993c282e2e6f64ba42 to your computer and use it in GitHub Desktop.
Save umbreak/6dde957880db05993c282e2e6f64ba42 to your computer and use it in GitHub Desktop.
package ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder
import cats.implicits._
import ch.epfl.bluebrain.nexus.delta.rdf.IriOrBNode.BNode
import ch.epfl.bluebrain.nexus.delta.rdf.graph.{Dot, NTriples}
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.{JsonLdApi, JsonLdOptions}
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.{ContextValue, RemoteContextResolution}
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.{CompactedJsonLd, ExpandedJsonLd}
import ch.epfl.bluebrain.nexus.delta.rdf.syntax._
import ch.epfl.bluebrain.nexus.delta.rdf.{IriOrBNode, RdfError}
import io.circe.Encoder
import io.circe.syntax._
import monix.bio.{IO, UIO}
trait JsonLdEncoder[A] { self =>
/**
* The context for the passed value
*/
def context(value: A): ContextValue
/**
* Converts a value of type ''A'' to [[ExpandedJsonLd]] format.
*
* @param value the value to be converted into a JSON-LD expanded document
*/
def expand(
value: A
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[RdfError, ExpandedJsonLd]
/**
* Converts a value to [[CompactedJsonLd]]
* @param value the value to be converted into a JSON-LD compacted document
*/
def compact(
value: A
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[RdfError, CompactedJsonLd]
/**
* Converts a value of type ''A'' to [[Dot]] format.
*
* @param value the value to be converted to Dot format
*/
def dot(
value: A
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[RdfError, Dot] =
for {
expanded <- expand(value)
graph <- IO.fromEither(expanded.toGraph)
dot <- graph.toDot(context(value))
} yield dot
/**
* Converts a value of type ''A'' to [[NTriples]] format.
*
* @param value the value to be converted to n-triples format
*/
def ntriples(
value: A
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[RdfError, NTriples] =
for {
expanded <- expand(value)
graph <- IO.fromEither(expanded.toGraph)
ntriples <- IO.fromEither(graph.toNTriples)
} yield ntriples
def mapContext(f: ContextValue => ContextValue): JsonLdEncoder[A] =
new JsonLdEncoder[A] {
override def context(value: A): ContextValue = f(self.context(value))
override def expand(
value: A
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[RdfError, ExpandedJsonLd] =
self.expand(value)
override def compact(
value: A
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[RdfError, CompactedJsonLd] =
self.compact(value)
}
}
object JsonLdEncoder {
sealed trait MergeContext extends Product with Serializable {
def appliesOnLeft: Boolean = this == MergeContext.Left || this == MergeContext.Both
def appliesOnRight: Boolean = this == MergeContext.Right || this == MergeContext.Both
}
object MergeContext {
final case object Left extends MergeContext
final case object Right extends MergeContext
final case object Both extends MergeContext
final case object None extends MergeContext
type Left = Left.type
type Right = Right.type
type Both = Both.type
type None = None.type
final def left: Left = Left
final def right: Left = Left
final def both: Left = Left
final def none: Left = Left
}
private def randomRootNode[A]: A => BNode = (_: A) => BNode.random
/**
* Creates a [[JsonLdEncoder]] using the available Circe Encoder to convert ''A'' to Json
* and uses the result as the already compacted form.
*
* @param context the context
*/
def compactedFromCirce[A: Encoder.AsObject](context: ContextValue): JsonLdEncoder[A] =
compactedFromCirce(randomRootNode, context)
/**
* Creates a [[JsonLdEncoder]] using the available Circe Encoder to convert ''A'' to Json
* and uses the result as the already compacted form.
*
* @param id the rootId
* @param ctx the context
*/
def compactedFromCirce[A: Encoder.AsObject](id: IriOrBNode, ctx: ContextValue): JsonLdEncoder[A] =
compactedFromCirce((_: A) => id, ctx)
/**
* Creates a [[JsonLdEncoder]] using the available Circe Encoder to convert ''A'' to Json
* and uses the result as the already compacted form.
*
* @param fId the function to obtain the rootId
* @param ctx the context
*/
def compactedFromCirce[A: Encoder.AsObject](fId: A => IriOrBNode, ctx: ContextValue): JsonLdEncoder[A] =
new JsonLdEncoder[A] {
override def compact(
value: A
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[RdfError, CompactedJsonLd] =
UIO.pure(CompactedJsonLd.unsafe(fId(value), ctx, value.asJsonObject))
override def expand(
value: A
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[RdfError, ExpandedJsonLd] =
for {
compacted <- compact(value)
expanded <- compacted.toExpanded
} yield expanded
override def context(value: A): ContextValue = ctx
}
/**
* Creates a [[JsonLdEncoder]] from an implicitly available Circe Encoder that turns an ''A'' to Json
* and computes the compacted and expanded form from it.
*
* @param context the context
*/
def computeFromCirce[A: Encoder](context: ContextValue): JsonLdEncoder[A] =
computeFromCirce(randomRootNode, context)
/**
* Creates a [[JsonLdEncoder]] from an implicitly available Circe Encoder that turns an ''A'' to to Json
* and computes the compacted and expanded form from it.
*
* @param id the rootId
* @param ctx the context
*/
def computeFromCirce[A: Encoder](id: IriOrBNode, ctx: ContextValue): JsonLdEncoder[A] =
computeFromCirce((_: A) => id, ctx)
/**
* Creates a [[JsonLdEncoder]] from an implicitly available Circe Encoder that turns an ''A'' to Json
* and computes the compacted and expanded form from it.
*
* @param fId the function to obtain the rootId
* @param ctx the context
*/
def computeFromCirce[A: Encoder](fId: A => IriOrBNode, ctx: ContextValue): JsonLdEncoder[A] =
new JsonLdEncoder[A] {
override def compact(
value: A
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[RdfError, CompactedJsonLd] =
for {
expanded <- expand(value)
compacted <- expanded.toCompacted(context(value))
} yield compacted
override def expand(
value: A
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[RdfError, ExpandedJsonLd] = {
val json = value.asJson.addContext(context(value).contextObj)
ExpandedJsonLd(json).map {
case expanded if fId(value).isBNode && expanded.rootId.isIri => expanded
case expanded => expanded.replaceId(fId(value))
}
}
override def context(value: A): ContextValue = value.asJson.topContextValueOrEmpty merge ctx
}
/**
* Creates an encoder composing two different encoders and a decomposition function ''f''.
*
* The root IriOrBNode used to build the resulting ''jsonld'' is picked from the ''f''.
*
* The decomposed ''A'' and ''B'' are resolved using the corresponding encoders and their results are merged.
* If there are keys present in both the resulting encoding of ''A'' and ''B'', the keys will be overridden with the
* values of ''B''.
*
* @param f the function to decomposed the value of the target encoder into values the passed encoders and its IriOrBNode
* @tparam A the generic type for values of the first passed encoder
* @tparam B the generic type for values of the second passed encoder
* @tparam C the generic type for values of the target encoder
*/
def compose[A, B, C](
f: C => (A, B, IriOrBNode),
mergeContext: MergeContext = MergeContext.none
)(implicit A: JsonLdEncoder[A], B: JsonLdEncoder[B]): JsonLdEncoder[C] =
new JsonLdEncoder[C] {
private def encodersFromMergeCtxStrategy(a: A, b: B): (JsonLdEncoder[A], JsonLdEncoder[B]) = {
val AA = if(mergeContext.appliesOnLeft) A.mapContext(B.context(b).merge) else A
val BB = if(mergeContext.appliesOnRight) B.mapContext(A.context(a).merge) else B
(AA, BB)
}
override def compact(
value: C
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[RdfError, CompactedJsonLd] = {
val (a, b, rootId) = f(value)
val (encoderA, encoderB) = encodersFromMergeCtxStrategy(a, b)
(encoderA.compact(a), encoderB.compact(b)).mapN { case (compactedA, compactedB) =>
compactedA.merge(rootId, compactedB)
}
}
override def expand(
value: C
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[RdfError, ExpandedJsonLd] = {
val (a, b, rootId) = f(value)
val (encoderA, encoderB) = encodersFromMergeCtxStrategy(a, b)
(encoderA.expand(a), encoderB.expand(b)).mapN { case (expandedA, expandedB) =>
expandedA.replaceId(rootId).merge(rootId, expandedB.replaceId(rootId))
}
}
override def context(value: C): ContextValue = {
val (a, b, _) = f(value)
A.context(a).merge(B.context(b))
}
}
implicit val jsonLdEncoderUnit: JsonLdEncoder[Unit] = new JsonLdEncoder[Unit] {
override def compact(
value: Unit
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[RdfError, CompactedJsonLd] =
IO.pure(CompactedJsonLd.empty)
override def expand(
value: Unit
)(implicit opts: JsonLdOptions, api: JsonLdApi, rcr: RemoteContextResolution): IO[RdfError, ExpandedJsonLd] =
IO.pure(ExpandedJsonLd.empty)
override def context(value: Unit): ContextValue = ContextValue.empty
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment