diff --git a/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesPracticeRoutes.scala b/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesPracticeRoutes.scala index 53b9c5baa9..73f4a4d19f 100644 --- a/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesPracticeRoutes.scala +++ b/delta/app/src/main/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesPracticeRoutes.scala @@ -62,12 +62,12 @@ final class ResourcesPracticeRoutes( private def validateRoute: Route = pathPrefix("resources") { extractCaller { implicit caller => - resolveProjectRef.apply { ref => - authorizeFor(ref, Write).apply { + resolveProjectRef.apply { project => + authorizeFor(project, Write).apply { (get & idSegment & idSegmentRef & pathPrefix("validate") & pathEndOrSingleSlash) { (schema, id) => val schemaOpt = underscoreToOption(schema) emit( - resourcesPractice.validate(id, ref, schemaOpt).leftWiden[ResourceRejection] + resourcesPractice.validate(id, project, schemaOpt).leftWiden[ResourceRejection] ) } } @@ -78,10 +78,10 @@ final class ResourcesPracticeRoutes( private def practiceRoute: Route = (get & pathPrefix("practice") & pathPrefix("resources")) { extractCaller { implicit caller => - (resolveProjectRef & pathEndOrSingleSlash) { ref => - authorizeFor(ref, Write).apply { + (resolveProjectRef & pathEndOrSingleSlash) { project => + authorizeFor(project, Write).apply { (entity(as[GenerationInput])) { input => - generate(ref, input) + generate(project, input) } } } diff --git a/delta/app/src/test/resources/practice/generated-resource.json b/delta/app/src/test/resources/practice/generated-resource.json new file mode 100644 index 0000000000..54a43920a5 --- /dev/null +++ b/delta/app/src/test/resources/practice/generated-resource.json @@ -0,0 +1,24 @@ +{ + "@context": [ + "https://bluebrain.github.io/nexus/contexts/metadata.json", + { + "@vocab": "https://bluebrain.github.io/nexus/vocabulary/" + }, + "https://neuroshapes.org" + ], + "@id": "https://bluebrain.github.io/nexus/vocabulary/myId", + "@type": "Morphology", + "name": "Morphology 001", + "_constrainedBy": "https://bluebrain.github.io/nexus/vocabulary/myschema", + "_createdAt": "1970-01-01T00:00:00Z", + "_createdBy": "http://localhost/v1/realms/wonderland/users/alice", + "_deprecated": false, + "_incoming": "http://localhost/v1/resources/myorg/myproj/_/https:%2F%2Fbluebrain.github.io%2Fnexus%2Fvocabulary%2FmyId/incoming", + "_outgoing": "http://localhost/v1/resources/myorg/myproj/_/https:%2F%2Fbluebrain.github.io%2Fnexus%2Fvocabulary%2FmyId/outgoing", + "_project": "http://localhost/v1/projects/myorg/myproj", + "_rev": 1, + "_schemaProject": "http://localhost/v1/projects/myorg/myproj", + "_self": "http://localhost/v1/resources/myorg/myproj/_/https:%2F%2Fbluebrain.github.io%2Fnexus%2Fvocabulary%2FmyId", + "_updatedAt": "1970-01-01T00:00:00Z", + "_updatedBy": "http://localhost/v1/realms/wonderland/users/alice" +} \ No newline at end of file diff --git a/delta/app/src/test/resources/practice/resource-without-schema.json b/delta/app/src/test/resources/practice/resource-without-schema.json deleted file mode 100644 index a60afe5685..0000000000 --- a/delta/app/src/test/resources/practice/resource-without-schema.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "result": { - "@context": [ - "https://bluebrain.github.io/nexus/contexts/metadata.json", - { - "@vocab": "https://bluebrain.github.io/nexus/vocabulary/" - }, - "https://neuroshapes.org" - ], - "@id": "https://bluebrain.github.io/nexus/vocabulary/myId", - "@type": "Morphology", - "name": "Morphology 001", - "_constrainedBy": "https://bluebrain.github.io/nexus/vocabulary/myschema", - "_createdAt": "1970-01-01T00:00:00Z", - "_createdBy": "http://localhost/v1/realms/wonderland/users/alice", - "_deprecated": false, - "_incoming": "http://localhost/v1/resources/myorg/myproj/_/https:%2F%2Fbluebrain.github.io%2Fnexus%2Fvocabulary%2FmyId/incoming", - "_outgoing": "http://localhost/v1/resources/myorg/myproj/_/https:%2F%2Fbluebrain.github.io%2Fnexus%2Fvocabulary%2FmyId/outgoing", - "_project": "http://localhost/v1/projects/myorg/myproj", - "_rev": 1, - "_schemaProject": "http://localhost/v1/projects/myorg/myproj", - "_self": "http://localhost/v1/resources/myorg/myproj/_/https:%2F%2Fbluebrain.github.io%2Fnexus%2Fvocabulary%2FmyId", - "_updatedAt": "1970-01-01T00:00:00Z", - "_updatedBy": "http://localhost/v1/realms/wonderland/users/alice" - } -} \ No newline at end of file diff --git a/delta/app/src/test/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesPracticeRoutesSpec.scala b/delta/app/src/test/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesPracticeRoutesSpec.scala index db976ca478..577b15509f 100644 --- a/delta/app/src/test/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesPracticeRoutesSpec.scala +++ b/delta/app/src/test/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesPracticeRoutesSpec.scala @@ -139,7 +139,10 @@ class ResourcesPracticeRoutesSpec extends BaseRouteSpec with ResourceInstanceFix val payload = json"""{ "resource": $validSource }""" Get(s"/v1/practice/resources/$projectRef/", payload.toEntity) ~> asAlice ~> routes ~> check { response.status shouldEqual StatusCodes.OK - response.asJson shouldEqual jsonContentOf("practice/resource-without-schema.json") + val jsonResponse = response.asJsonObject + jsonResponse("schema") shouldBe empty + jsonResponse("result") shouldEqual Some(jsonContentOf("practice/generated-resource.json")) + jsonResponse("error") shouldBe empty } } @@ -204,8 +207,8 @@ class ResourcesPracticeRoutesSpec extends BaseRouteSpec with ResourceInstanceFix } s"successfully validate $myId for a user with access against the unconstrained schema" in { - val unconstrainedEncoded = UrlUtils.encode(schemas.resources.toString) - Get(s"/v1/resources/$projectRef/$unconstrainedEncoded/myId/validate") ~> asAlice ~> routes ~> check { + val unconstrained = UrlUtils.encode(schemas.resources.toString) + Get(s"/v1/resources/$projectRef/$unconstrained/myId/validate") ~> asAlice ~> routes ~> check { response.status shouldEqual StatusCodes.OK response.asJson shouldEqual json"""{ diff --git a/delta/app/src/test/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesRoutesSpec.scala b/delta/app/src/test/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesRoutesSpec.scala index 0a58a7ee23..b89ed3beae 100644 --- a/delta/app/src/test/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesRoutesSpec.scala +++ b/delta/app/src/test/scala/ch/epfl/bluebrain/nexus/delta/routes/ResourcesRoutesSpec.scala @@ -71,6 +71,7 @@ class ResourcesRoutesSpec extends BaseRouteSpec with IOFromMap { private val myIdEncoded = UrlUtils.encode(myId.toString) private val myId2Encoded = UrlUtils.encode(myId2.toString) private val payload = jsonContentOf("resources/resource.json", "id" -> myId) + private val payloadWithoutId = payload.removeKeys(keywords.id) private val payloadWithBlankId = jsonContentOf("resources/resource.json", "id" -> "") private val payloadWithUnderscoreFields = jsonContentOf("resources/resource-with-underscore-fields.json", "id" -> myId5) @@ -177,7 +178,7 @@ class ResourcesRoutesSpec extends BaseRouteSpec with IOFromMap { "fail to create a resource that does not validate against a schema" in { Put( "/v1/resources/myorg/myproject/nxv:myschema/wrong", - payload.removeKeys(keywords.id).replaceKeyWithValue("number", "wrong").toEntity + payloadWithoutId.replaceKeyWithValue("number", "wrong").toEntity ) ~> routes ~> check { response.status shouldEqual StatusCodes.BadRequest response.asJson shouldEqual jsonContentOf("/resources/errors/invalid-resource.json") @@ -185,10 +186,7 @@ class ResourcesRoutesSpec extends BaseRouteSpec with IOFromMap { } "fail to create a resource against a schema that does not exist" in { - Put( - "/v1/resources/myorg/myproject/pretendschema/wrong", - payload.removeKeys(keywords.id).replaceKeyWithValue("number", "wrong").toEntity - ) ~> routes ~> check { + Put("/v1/resources/myorg/myproject/pretendschema/", payloadWithoutId.toEntity) ~> routes ~> check { status shouldEqual StatusCodes.NotFound response.asJson shouldEqual jsonContentOf("/schemas/errors/invalid-schema-2.json") } diff --git a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/ResourcesPracticeSuite.scala b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/ResourcesPracticeSuite.scala index d46c049bd0..d36b8e0cd7 100644 --- a/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/ResourcesPracticeSuite.scala +++ b/delta/sdk/src/test/scala/ch/epfl/bluebrain/nexus/delta/sdk/resources/ResourcesPracticeSuite.scala @@ -5,6 +5,7 @@ import ch.epfl.bluebrain.nexus.delta.rdf.Vocabulary.{contexts, nxv, schema} import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.api.{JsonLdApi, JsonLdJavaApi} import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.JsonLdContext.keywords import ch.epfl.bluebrain.nexus.delta.rdf.jsonld.context.{ContextValue, RemoteContextResolution} +import ch.epfl.bluebrain.nexus.delta.sdk.SchemaResource import ch.epfl.bluebrain.nexus.delta.sdk.generators.{ProjectGen, ResourceGen, SchemaGen} import ch.epfl.bluebrain.nexus.delta.sdk.identities.model.Caller import ch.epfl.bluebrain.nexus.delta.sdk.projects.FetchContextDummy @@ -12,11 +13,13 @@ import ch.epfl.bluebrain.nexus.delta.sdk.projects.model.ApiMappings import ch.epfl.bluebrain.nexus.delta.sdk.syntax._ import ch.epfl.bluebrain.nexus.delta.sdk.resolvers.ResolverContextResolution import ch.epfl.bluebrain.nexus.delta.sdk.resources.ValidationResult._ +import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.{Resource, ResourceGenerationResult, ResourceRejection} import ch.epfl.bluebrain.nexus.delta.sdk.resources.model.ResourceRejection.{InvalidResource, ProjectContextRejection, ReservedResourceId} import ch.epfl.bluebrain.nexus.delta.sourcing.model.ResourceRef.Revision import ch.epfl.bluebrain.nexus.testkit.bio.BioSuite import ch.epfl.bluebrain.nexus.testkit.{IOFixedClock, TestHelpers} -import monix.bio.IO +import monix.bio.{IO, UIO} +import munit.Location import java.util.UUID @@ -58,6 +61,22 @@ class ResourcesPracticeSuite extends BioSuite with ValidateResourceFixture with private val source = NexusSource(jsonContentOf("resources/resource.json", "id" -> id)) private val resourceSchema = nxv + "schema" + private def assertSuccess( + io: UIO[ResourceGenerationResult] + )(schema: Option[SchemaResource], result: Resource)(implicit loc: Location) = + io.map { generated => + assertEquals(generated.schema, schema) + assertEquals(generated.attempt.map(_.value), Right(result)) + } + + private def assertError( + io: UIO[ResourceGenerationResult] + )(schema: Option[SchemaResource], error: ResourceRejection)(implicit loc: Location) = + io.map { generated => + assertEquals(generated.schema, schema) + assertEquals(generated.attempt.map(_.value), Left(error)) + } + test("Successfully generates a resource") { val practice = ResourcesPractice( (_, _) => fetchResourceFail, @@ -68,12 +87,7 @@ class ResourcesPracticeSuite extends BioSuite with ValidateResourceFixture with val expectedData = ResourceGen.resource(id, projectRef, source.value, Revision(resourceSchema, defaultSchemaRevision)) - - for { - generated <- practice.generate(projectRef, resourceSchema, source) - _ = assertEquals(generated.schema, None) - _ = assertEquals(generated.attempt.map(_.value), Right(expectedData)) - } yield () + assertSuccess(practice.generate(projectRef, resourceSchema, source))(None, expectedData) } test("Successfully generates a resource with a new schema") { @@ -92,12 +106,7 @@ class ResourcesPracticeSuite extends BioSuite with ValidateResourceFixture with val expectedData = ResourceGen.resource(id, projectRef, source.value, Revision(anotherSchema, defaultSchemaRevision)) - - for { - generated <- practice.generate(projectRef, schema, source) - _ = assertEquals(generated.schema, Some(schema)) - _ = assertEquals(generated.attempt.map(_.value), Right(expectedData)) - } yield () + assertSuccess(practice.generate(projectRef, schema, source))(Some(schema), expectedData) } test("Fail when validation raises an error") { @@ -109,11 +118,7 @@ class ResourcesPracticeSuite extends BioSuite with ValidateResourceFixture with resolverContextResolution ) - for { - generated <- practice.generate(projectRef, resourceSchema, source) - _ = assertEquals(generated.schema, None) - _ = assertEquals(generated.attempt, Left(expectedError)) - } yield () + assertError(practice.generate(projectRef, resourceSchema, source))(None, expectedError) } test("Validate a resource against a new schema reference") { diff --git a/docs/src/main/paradox/docs/delta/api/practice.md b/docs/src/main/paradox/docs/delta/api/practice.md index 35ca71de53..4c9045c55d 100644 --- a/docs/src/main/paradox/docs/delta/api/practice.md +++ b/docs/src/main/paradox/docs/delta/api/practice.md @@ -1,6 +1,6 @@ # Practice -Practice operations contains read-only operations designed to help users compose and validate their -resources before effectively save them in Nexus. +Practice operations contain read-only operations designed to help users compose and validate their +resources before effectively saving them in Nexus. @@@ note { .tip title="Authorization notes" } @@ -35,6 +35,7 @@ This field is optional and defaults to no SHACL validation. * `{resource}`: Json: The resource payload to test and validate The Json response will contain: + * The generated resource in the compacted JSON-LD format if the generation and the validation was successful * The generated schema if a new schema payload was provided * The error if the one of the steps fails (invalid resource/invalid new schema/existing schema not found/...) @@ -48,4 +49,21 @@ Payload : @@snip [payload.json](assets/practice/resources/payload.json) Response -: @@snip [created.json](assets/practice/resources/generated.json) \ No newline at end of file +: @@snip [created.json](assets/practice/resources/generated.json) + +## Validate + +This operation runs validation of a resource against a schema. This would be useful to test whether resources would +match the shape of a new schema. + +``` +GET /v1/resources/{org_label}/{project_label}/{schema_id}/{resource_id}/validate +``` + +**Example** + +Request +: @@snip [validate.sh](assets/resources/validate.sh) + +Response +: @@snip [validated.json](assets/resources/validated.json) \ No newline at end of file diff --git a/docs/src/main/paradox/docs/delta/api/resources-api.md b/docs/src/main/paradox/docs/delta/api/resources-api.md index 4686a05724..2732923162 100644 --- a/docs/src/main/paradox/docs/delta/api/resources-api.md +++ b/docs/src/main/paradox/docs/delta/api/resources-api.md @@ -148,24 +148,6 @@ Request Response : @@snip [refreshed.json](assets/resources/updated.json) - -## Validate - -This operation runs validation of a resource against a schema. This would be useful to test whether resources would -match the shape of a new schema. - -``` -GET /v1/resources/{org_label}/{project_label}/{schema_id}/{resource_id}/validate -``` - -**Example** - -Request -: @@snip [validate.sh](assets/resources/validate.sh) - -Response -: @@snip [validated.json](assets/resources/validated.json) - ## Tag Links a resource revision to a specific name.