Skip to content

Commit

Permalink
Add resource trial routes (#4274)
Browse files Browse the repository at this point in the history
* Add resource practice routes

---------

Co-authored-by: Simon Dumas <simon.dumas@epfl.ch>
  • Loading branch information
imsdu and Simon Dumas authored Sep 22, 2023
1 parent 80f591b commit 16483bc
Show file tree
Hide file tree
Showing 41 changed files with 1,098 additions and 390 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import ch.epfl.bluebrain.nexus.delta.sdk.permissions.Permissions.resources.{read
import ch.epfl.bluebrain.nexus.delta.sdk.resources.NexusSource.DecodingOption
import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceRejection.{InvalidJsonLdFormat, InvalidSchemaRejection, ResourceNotFound}
import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.{Resource, ResourceRejection}
import ch.epfl.bluebrain.nexus.delta.sdk.resources.{NexusSource, Resources, ResourcesPractice}
import ch.epfl.bluebrain.nexus.delta.sdk.resources.{NexusSource, Resources}
import io.circe.{Json, Printer}
import monix.bio.IO
import monix.execution.Scheduler
Expand All @@ -47,7 +47,6 @@ final class ResourcesRoutes(
identities: Identities,
aclCheck: AclCheck,
resources: Resources,
resourcesPractice: ResourcesPractice,
schemeDirectives: DeltaSchemeDirectives,
index: IndexingAction.Execute[Resource]
)(implicit
Expand Down Expand Up @@ -173,16 +172,6 @@ final class ResourcesRoutes(
)
}
},
(pathPrefix("validate") & get & pathEndOrSingleSlash & idSegmentRef(id)) { id =>
authorizeFor(ref, Write).apply {
emit(
resourcesPractice
.validate(id, ref, schemaOpt)
.leftWiden[ResourceRejection]
)
}

},
// Fetch a resource original source
(pathPrefix("source") & get & pathEndOrSingleSlash & idSegmentRef(id)) { id =>
authorizeFor(ref, Read).apply {
Expand Down Expand Up @@ -273,7 +262,6 @@ object ResourcesRoutes {
identities: Identities,
aclCheck: AclCheck,
resources: Resources,
resourcesPractice: ResourcesPractice,
projectsDirectives: DeltaSchemeDirectives,
index: IndexingAction.Execute[Resource]
)(implicit
Expand All @@ -283,7 +271,7 @@ object ResourcesRoutes {
ordering: JsonKeyOrdering,
fusionConfig: FusionConfig,
decodingOption: DecodingOption
): Route = new ResourcesRoutes(identities, aclCheck, resources, resourcesPractice, projectsDirectives, index).routes
): Route = new ResourcesRoutes(identities, aclCheck, resources, projectsDirectives, index).routes

def asSourceWithMetadata(
resource: ResourceF[Resource]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
package ch.epfl.bluebrain.nexus.delta.routes

import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.Route
import cats.syntax.all._
import ch.epfl.bluebrain.nexus.delta.kernel.effect.migration._
import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.schemas
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.RemoteContextResolution
import ch.epfl.bluebrain.nexus.delta.rdf.utils.JsonKeyOrdering
import ch.epfl.bluebrain.nexus.delta.routes.ResourcesTrialRoutes.SchemaInput._
import ch.epfl.bluebrain.nexus.delta.routes.ResourcesTrialRoutes.{GenerateSchema, GenerationInput}
import ch.epfl.bluebrain.nexus.delta.sdk.SchemaResource
import ch.epfl.bluebrain.nexus.delta.sdk.acls.AclCheck
import ch.epfl.bluebrain.nexus.delta.sdk.circe.CirceUnmarshalling
import ch.epfl.bluebrain.nexus.delta.sdk.directives.DeltaDirectives._
import ch.epfl.bluebrain.nexus.delta.sdk.directives.{AuthDirectives, DeltaSchemeDirectives}
import ch.epfl.bluebrain.nexus.delta.sdk.identities.Identities
import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller
import ch.epfl.bluebrain.nexus.delta.sdk.marshalling.RdfMarshalling
import ch.epfl.bluebrain.nexus.delta.sdk.model.IdSegment.IriSegment
import ch.epfl.bluebrain.nexus.delta.sdk.model.{BaseUri, IdSegment}
import ch.epfl.bluebrain.nexus.delta.sdk.permissions.Permissions.resources.{write => Write}
import ch.epfl.bluebrain.nexus.delta.sdk.resources.NexusSource.DecodingOption
import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceRejection
import ch.epfl.bluebrain.nexus.delta.sdk.resources.{NexusSource, ResourcesTrial}
import ch.epfl.bluebrain.nexus.delta.sdk.schemas.Schemas
import ch.epfl.bluebrain.nexus.delta.sdk.schemas.model.SchemaRejection
import ch.epfl.bluebrain.nexus.delta.sourcing.model.ProjectRef
import io.circe.generic.extras.Configuration
import io.circe.generic.extras.semiauto.deriveConfiguredDecoder
import io.circe.{Decoder, Json}
import monix.bio.IO
import monix.execution.Scheduler

import scala.annotation.nowarn

/**
* The resource trial routes allowing to do read-only operations on resources
*/
final class ResourcesTrialRoutes(
identities: Identities,
aclCheck: AclCheck,
generateSchema: GenerateSchema,
resourcesTrial: ResourcesTrial,
schemeDirectives: DeltaSchemeDirectives
)(implicit
baseUri: BaseUri,
s: Scheduler,
cr: RemoteContextResolution,
ordering: JsonKeyOrdering,
decodingOption: DecodingOption
) extends AuthDirectives(identities, aclCheck)
with CirceUnmarshalling
with RdfMarshalling {

import schemeDirectives._
def routes: Route =
baseUriPrefix(baseUri.prefix) {
concat(validateRoute, generateRoute)
}

private def validateRoute: Route =
pathPrefix("resources") {
extractCaller { implicit caller =>
resolveProjectRef.apply { project =>
authorizeFor(project, Write).apply {
(get & idSegment & idSegmentRef & pathPrefix("validate") & pathEndOrSingleSlash) { (schema, id) =>
val schemaOpt = underscoreToOption(schema)
emit(
resourcesTrial.validate(id, project, schemaOpt).leftWiden[ResourceRejection]
)
}
}
}
}
}

private def generateRoute: Route =
(get & pathPrefix("trial") & pathPrefix("resources")) {
extractCaller { implicit caller =>
(resolveProjectRef & pathEndOrSingleSlash) { project =>
authorizeFor(project, Write).apply {
(entity(as[GenerationInput])) { input =>
generate(project, input)
}
}
}
}
}

// Call the generate method matching the schema input
private def generate(project: ProjectRef, input: GenerationInput)(implicit caller: Caller) =
input.schema match {
case ExistingSchema(schemaId) =>
emit(resourcesTrial.generate(project, schemaId, input.resource).flatMap(_.asJson))
case NewSchema(schemaSource) =>
emit(
generateSchema(project, schemaSource, caller).flatMap { schema =>
resourcesTrial.generate(project, schema, input.resource).flatMap(_.asJson)
}
)
}

}

object ResourcesTrialRoutes {

type GenerateSchema = (ProjectRef, Json, Caller) => IO[SchemaRejection, SchemaResource]

sealed private[routes] trait SchemaInput extends Product

private[routes] object SchemaInput {

// Validate the generated resource with an existing schema
final case class ExistingSchema(id: IdSegment) extends SchemaInput

// Validate the generated resource with the new schema bundled in the request
final case class NewSchema(json: Json) extends SchemaInput

implicit val schemaInputDecoder: Decoder[SchemaInput] =
Decoder.instance { hc =>
val value = hc.value
val existingSchema = value.asString.map { s => ExistingSchema(IdSegment(s)) }
val newSchema = NewSchema(value)
Right(existingSchema.getOrElse(newSchema))
}
}

private val noSchema = ExistingSchema(IriSegment(schemas.resources))

final private[routes] case class GenerationInput(schema: SchemaInput = noSchema, resource: NexusSource)

private[routes] object GenerationInput {
@nowarn("cat=unused")
implicit def generationInputDecoder(implicit decodingOption: DecodingOption): Decoder[GenerationInput] = {
implicit val configuration: Configuration = Configuration.default.withDefaults
implicit val nexusSourceDecoder: Decoder[NexusSource] = NexusSource.nexusSourceDecoder
deriveConfiguredDecoder[GenerationInput]
}
}

def apply(
identities: Identities,
aclCheck: AclCheck,
schemas: Schemas,
resourcesTrial: ResourcesTrial,
schemeDirectives: DeltaSchemeDirectives
)(implicit
baseUri: BaseUri,
s: Scheduler,
cr: RemoteContextResolution,
ordering: JsonKeyOrdering,
decodingOption: DecodingOption
): ResourcesTrialRoutes =
new ResourcesTrialRoutes(
identities,
aclCheck,
(project, source, caller) => schemas.createDryRun(project, source)(caller).toBIO[SchemaRejection],
resourcesTrial,
schemeDirectives
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ class DeltaModule(appCfg: AppConfig, config: Config)(implicit classLoader: Class
include(ResolversModule)
include(SchemasModule)
include(ResourcesModule)
include(ResourcesTrialModule)
include(MultiFetchModule)
include(IdentitiesModule)
include(VersionModule)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.ResolverResolution.ResourceRe
import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.{ResolverContextResolution, Resolvers, ResourceResolution}
import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceRejection.ProjectContextRejection
import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.{Resource, ResourceEvent}
import ch.epfl.bluebrain.nexus.delta.sdk.resources.{Resources, ResourcesImpl, ResourcesPractice, ValidateResource}
import ch.epfl.bluebrain.nexus.delta.sdk.resources.{Resources, ResourcesConfig, ResourcesImpl, ValidateResource}
import ch.epfl.bluebrain.nexus.delta.sdk.schemas.Schemas
import ch.epfl.bluebrain.nexus.delta.sdk.schemas.model.Schema
import ch.epfl.bluebrain.nexus.delta.sdk.sse.SseEncoder
Expand All @@ -40,11 +40,13 @@ object ResourcesModule extends ModuleDef {
ValidateResource(resourceResolution)(api)
}

make[ResourcesConfig].from { (config: AppConfig) => config.resources }

make[Resources].from {
(
validate: ValidateResource,
fetchContext: FetchContext[ContextRejection],
config: AppConfig,
config: ResourcesConfig,
resolverContextResolution: ResolverContextResolution,
api: JsonLdApi,
xas: Transactors,
Expand All @@ -55,7 +57,7 @@ object ResourcesModule extends ModuleDef {
validate,
fetchContext.mapRejection(ProjectContextRejection),
resolverContextResolution,
config.resources,
config,
xas
)(
api,
Expand All @@ -64,24 +66,6 @@ object ResourcesModule extends ModuleDef {
)
}

make[ResourcesPractice].from {
(
resources: Resources,
validate: ValidateResource,
fetchContext: FetchContext[ContextRejection],
contextResolution: ResolverContextResolution,
api: JsonLdApi,
clock: Clock[UIO],
uuidF: UUIDF
) =>
ResourcesPractice(
resources.fetch(_, _, None),
validate,
fetchContext.mapRejection(ProjectContextRejection),
contextResolution
)(api, clock, uuidF)
}

make[ResolverContextResolution].from {
(aclCheck: AclCheck, resolvers: Resolvers, resources: Resources, rcr: RemoteContextResolution @Id("aggregate")) =>
ResolverContextResolution(aclCheck, resolvers, resources, rcr)
Expand All @@ -96,7 +80,6 @@ object ResourcesModule extends ModuleDef {
identities: Identities,
aclCheck: AclCheck,
resources: Resources,
resourcesPractice: ResourcesPractice,
schemeDirectives: DeltaSchemeDirectives,
indexingAction: IndexingAction @Id("aggregate"),
shift: Resource.Shift,
Expand All @@ -105,13 +88,12 @@ object ResourcesModule extends ModuleDef {
cr: RemoteContextResolution @Id("aggregate"),
ordering: JsonKeyOrdering,
fusionConfig: FusionConfig,
config: AppConfig
config: ResourcesConfig
) =>
new ResourcesRoutes(
identities,
aclCheck,
resources,
resourcesPractice,
schemeDirectives,
indexingAction(_, _, _)(shift, cr)
)(
Expand All @@ -120,7 +102,7 @@ object ResourcesModule extends ModuleDef {
cr,
ordering,
fusionConfig,
config.resources.decodingOption
config.decodingOption
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package ch.epfl.bluebrain.nexus.delta.wiring

import cats.effect.Clock
import ch.epfl.bluebrain.nexus.delta.Main.pluginsMinPriority
import ch.epfl.bluebrain.nexus.delta.kernel.utils.UUIDF
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.JsonLdApi
import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.RemoteContextResolution
import ch.epfl.bluebrain.nexus.delta.rdf.utils.JsonKeyOrdering
import ch.epfl.bluebrain.nexus.delta.routes.ResourcesTrialRoutes
import ch.epfl.bluebrain.nexus.delta.sdk.PriorityRoute
import ch.epfl.bluebrain.nexus.delta.sdk.acls.AclCheck
import ch.epfl.bluebrain.nexus.delta.sdk.directives.DeltaSchemeDirectives
import ch.epfl.bluebrain.nexus.delta.sdk.identities.Identities
import ch.epfl.bluebrain.nexus.delta.sdk.model.BaseUri
import ch.epfl.bluebrain.nexus.delta.sdk.projects.FetchContext
import ch.epfl.bluebrain.nexus.delta.sdk.projects.FetchContext.ContextRejection
import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.ResolverContextResolution
import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceRejection.ProjectContextRejection
import ch.epfl.bluebrain.nexus.delta.sdk.resources.{Resources, ResourcesConfig, ResourcesTrial, ValidateResource}
import ch.epfl.bluebrain.nexus.delta.sdk.schemas.Schemas
import distage.ModuleDef
import izumi.distage.model.definition.Id
import monix.bio.UIO
import monix.execution.Scheduler

/**
* Resources trial wiring
*/
object ResourcesTrialModule extends ModuleDef {

make[ResourcesTrial].from {
(
resources: Resources,
validate: ValidateResource,
fetchContext: FetchContext[ContextRejection],
contextResolution: ResolverContextResolution,
api: JsonLdApi,
clock: Clock[UIO],
uuidF: UUIDF
) =>
ResourcesTrial(
resources.fetch(_, _, None),
validate,
fetchContext.mapRejection(ProjectContextRejection),
contextResolution
)(api, clock, uuidF)
}

make[ResourcesTrialRoutes].from {
(
identities: Identities,
aclCheck: AclCheck,
schemas: Schemas,
resourcesTrial: ResourcesTrial,
schemeDirectives: DeltaSchemeDirectives,
baseUri: BaseUri,
s: Scheduler,
cr: RemoteContextResolution @Id("aggregate"),
ordering: JsonKeyOrdering,
config: ResourcesConfig
) =>
ResourcesTrialRoutes(
identities,
aclCheck,
schemas,
resourcesTrial,
schemeDirectives
)(
baseUri,
s,
cr,
ordering,
config.decodingOption
)
}

many[PriorityRoute].add { (route: ResourcesTrialRoutes) =>
PriorityRoute(pluginsMinPriority - 1, route.routes, requiresStrictEntity = true)
}

}
Loading

0 comments on commit 16483bc

Please sign in to comment.