Created
December 22, 2020 09:45
-
-
Save umbreak/6dde957880db05993c282e2e6f64ba42 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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