Skip to content

Commit

Permalink
Add endpoint to show source of resolved resources (#4465)
Browse files Browse the repository at this point in the history
* Add endpoint to show source of resolved resources

* Refactor output

* Match statement mismatch

* Match statement mismatch

* Documentation

* Remove unwanted test
  • Loading branch information
shinyhappydan authored Nov 7, 2023
1 parent 1f42c1b commit cb7fb21
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 107 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.{contexts, schemas}
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.{ContextValue, RemoteContextResolution}
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.encoder.JsonLdEncoder
import ch.epfl.bluebrain.nexus.delta.rdf.utils.JsonKeyOrdering
import ch.epfl.bluebrain.nexus.delta.routes.ResolutionType.{AllResolversInProject, SingleResolver}
import ch.epfl.bluebrain.nexus.delta.sdk._
import ch.epfl.bluebrain.nexus.delta.sdk.acls.AclCheck
import ch.epfl.bluebrain.nexus.delta.sdk.ce.DeltaDirectives._
Expand Down Expand Up @@ -87,20 +88,21 @@ final class ResolversRoutes(
(baseUriPrefix(baseUri.prefix) & replaceUri("resolvers", schemas.resolvers)) {
pathPrefix("resolvers") {
extractCaller { implicit caller =>
(resolveProjectRef & indexingMode) { (ref, mode) =>
def index(resolver: ResolverResource): IO[Unit] = indexAction(resolver.value.project, resolver, mode)
val authorizeRead = authorizeFor(ref, Read)
val authorizeWrite = authorizeFor(ref, Write)
(resolveProjectRef & indexingMode) { (project, indexingMode) =>
def index(resolver: ResolverResource): IO[Unit] =
indexAction(resolver.value.project, resolver, indexingMode)
val authorizeRead = authorizeFor(project, Read)
val authorizeWrite = authorizeFor(project, Write)
concat(
pathEndOrSingleSlash {
// Create a resolver without an id segment
(post & noParameter("rev") & entity(as[Json])) { payload =>
authorizeWrite {
emitMetadata(Created, resolvers.create(ref, payload).flatTap(index))
emitMetadata(Created, resolvers.create(project, payload).flatTap(index))
}
}
},
idSegment { id =>
idSegment { resolver =>
concat(
pathEndOrSingleSlash {
concat(
Expand All @@ -109,55 +111,72 @@ final class ResolversRoutes(
(parameter("rev".as[Int].?) & pathEndOrSingleSlash & entity(as[Json])) {
case (None, payload) =>
// Create a resolver with an id segment
emitMetadata(Created, resolvers.create(id, ref, payload).flatTap(index))
emitMetadata(Created, resolvers.create(resolver, project, payload).flatTap(index))
case (Some(rev), payload) =>
// Update a resolver
emitMetadata(resolvers.update(id, ref, rev, payload).flatTap(index))
emitMetadata(resolvers.update(resolver, project, rev, payload).flatTap(index))
}
}
},
(delete & parameter("rev".as[Int])) { rev =>
authorizeWrite {
// Deprecate a resolver
emitMetadataOrReject(resolvers.deprecate(id, ref, rev).flatTap(index))
emitMetadataOrReject(resolvers.deprecate(resolver, project, rev).flatTap(index))
}
},
// Fetches a resolver
(get & idSegmentRef(id)) { id =>
(get & idSegmentRef(resolver)) { resolverRef =>
emitOrFusionRedirect(
ref,
id,
project,
resolverRef,
authorizeRead {
emitFetch(resolvers.fetch(id, ref))
emitFetch(resolvers.fetch(resolverRef, project))
}
)
}
)
},
// Fetches a resolver original source
(pathPrefix("source") & get & pathEndOrSingleSlash & idSegmentRef(id) & authorizeRead) { id =>
emitSource(resolvers.fetch(id, ref))
(pathPrefix("source") & get & pathEndOrSingleSlash & idSegmentRef(resolver) & authorizeRead) {
resolverRef =>
emitSource(resolvers.fetch(resolverRef, project))
},
// Tags
(pathPrefix("tags") & pathEndOrSingleSlash) {
concat(
// Fetch a resolver tags
(get & idSegmentRef(id) & authorizeRead) { id =>
emitTags(resolvers.fetch(id, ref))
(get & idSegmentRef(resolver) & authorizeRead) { resolverRef =>
emitTags(resolvers.fetch(resolverRef, project))
},
// Tag a resolver
(post & parameter("rev".as[Int])) { rev =>
authorizeWrite {
entity(as[Tag]) { case Tag(tagRev, tag) =>
emitMetadata(Created, resolvers.tag(id, ref, tag, tagRev, rev).flatTap(index))
emitMetadata(Created, resolvers.tag(resolver, project, tag, tagRev, rev).flatTap(index))
}
}
}
)
},
// Fetch a resource using a resolver
(idSegmentRef & pathEndOrSingleSlash) { resourceIdRef =>
resolve(resourceIdRef, ref, underscoreToOption(id))
(get & idSegmentRef) { resourceIdRef =>
concat(
pathEndOrSingleSlash {
parameter("showReport".as[Boolean].withDefault(default = false)) { showReport =>
val outputType =
if (showReport) ResolvedResourceOutputType.Report else ResolvedResourceOutputType.JsonLd
resolveResource(resourceIdRef, project, resolutionType(resolver), outputType)
}
},
(pathPrefix("source") & pathEndOrSingleSlash) {
resolveResource(
resourceIdRef,
project,
resolutionType(resolver),
ResolvedResourceOutputType.Source
)
}
)
}
)
}
Expand All @@ -167,23 +186,48 @@ final class ResolversRoutes(
}
}

private def resolve(resourceSegment: IdSegmentRef, projectRef: ProjectRef, resolverId: Option[IdSegment])(implicit
private def resolveResource(
resource: IdSegmentRef,
project: ProjectRef,
resolutionType: ResolutionType,
output: ResolvedResourceOutputType
)(implicit
caller: Caller
): Route =
authorizeFor(projectRef, Permissions.resources.read).apply {
parameter("showReport".as[Boolean].withDefault(default = false)) { showReport =>
def emitResult[R: JsonLdEncoder](io: IO[MultiResolutionResult[R]]) =
if (showReport)
emit(io.map(_.report).attemptNarrow[ResolverRejection])
else
emit(io.map(_.value.jsonLdValue).attemptNarrow[ResolverRejection])

resolverId.fold(emitResult(multiResolution(resourceSegment, projectRef))) { resolverId =>
emitResult(multiResolution(resourceSegment, projectRef, resolverId))
authorizeFor(project, Permissions.resources.read).apply {
def emitResult[R: JsonLdEncoder](io: IO[MultiResolutionResult[R]]) = {
output match {
case ResolvedResourceOutputType.Report => emit(io.map(_.report).attemptNarrow[ResolverRejection])
case ResolvedResourceOutputType.JsonLd => emit(io.map(_.value.jsonLdValue).attemptNarrow[ResolverRejection])
case ResolvedResourceOutputType.Source => emit(io.map(_.value.source).attemptNarrow[ResolverRejection])
}
}

resolutionType match {
case ResolutionType.AllResolversInProject => emitResult(multiResolution(resource, project))
case SingleResolver(resolver) => emitResult(multiResolution(resource, project, resolver))
}
}

private def resolutionType(segment: IdSegment): ResolutionType = {
underscoreToOption(segment) match {
case Some(resolver) => SingleResolver(resolver)
case None => AllResolversInProject
}
}
}

sealed trait ResolutionType
object ResolutionType {
case object AllResolversInProject extends ResolutionType
case class SingleResolver(id: IdSegment) extends ResolutionType
}

sealed trait ResolvedResourceOutputType
object ResolvedResourceOutputType {
case object Report extends ResolvedResourceOutputType
case object JsonLd extends ResolvedResourceOutputType
case object Source extends ResolvedResourceOutputType
}

object ResolversRoutes {
Expand Down
Loading

0 comments on commit cb7fb21

Please sign in to comment.