From af95516d5183979df623d688ccf322a6129b8325 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Kleinb=C3=B6lting?= Date: Mon, 20 Nov 2023 18:19:50 +0100 Subject: [PATCH] chore: Move ProjectIri to KnoraProject (#2944) --- .../org/knora/webapi/ITTestDataFactory.scala | 6 -- .../listsmessages/ListsMessagesADMSpec.scala | 3 +- .../admin/GroupsResponderADMSpec.scala | 5 +- .../admin/ListsResponderADMSpec.scala | 29 ++++--- .../admin/ProjectsResponderADMSpec.scala | 29 ++++--- .../src/main/scala/dsp/valueobjects/Iri.scala | 38 +--------- .../groupsmessages/GroupsPayloadsADM.scala | 1 + .../listsmessages/ListsPayloadsADM.scala | 1 + .../PermissionsMessagesADM.scala | 2 - .../ProjectsMessagesADM.scala | 3 +- .../TriplestoreMessages.scala | 4 +- .../responders/admin/ListsResponderADM.scala | 76 +++++++++++-------- .../admin/ProjectsResponderADM.scala | 13 ++-- .../webapi/routing/admin/GroupsRouteADM.scala | 3 +- .../admin/lists/CreateListItemsRouteADM.scala | 5 +- .../admin/lists/UpdateListItemsRouteADM.scala | 3 +- .../api/model/ProjectsEndpointsRequests.scala | 1 - .../api/service/ProjectsADMRestService.scala | 2 +- .../admin/domain/model/KnoraProject.scala | 29 ++++++- .../domain/service/KnoraProjectRepo.scala | 4 +- .../domain/service/ProjectADMService.scala | 2 +- .../repo/service/KnoraProjectRepoLive.scala | 29 +++---- .../common/api/RestPermissionService.scala | 2 +- .../domain/service/OntologyRepo.scala | 6 +- .../repo/service/OntologyRepoLive.scala | 5 +- .../cache/impl/CacheServiceInMemImpl.scala | 4 +- .../test/scala/dsp/valueobjects/IriSpec.scala | 55 +------------- .../org/knora/webapi/TestDataFactory.scala | 6 +- .../admin/ProjectADMRestServiceMock.scala | 1 + .../admin/ProjectsResponderADMMock.scala | 10 +-- .../admin/ProjectsServiceLiveSpec.scala | 6 +- .../domain/model}/KnoraProjectSpec.scala | 42 +++++++++- .../repo/KnoraProjectRepoInMemory.scala | 4 +- .../service/ProjectADMServiceSpec.scala | 3 +- 34 files changed, 208 insertions(+), 224 deletions(-) rename webapi/src/test/scala/{dsp/valueobjects => org/knora/webapi/slice/admin/domain/model}/KnoraProjectSpec.scala (78%) diff --git a/integration/src/test/scala/org/knora/webapi/ITTestDataFactory.scala b/integration/src/test/scala/org/knora/webapi/ITTestDataFactory.scala index f42ead3c00..015318d730 100644 --- a/integration/src/test/scala/org/knora/webapi/ITTestDataFactory.scala +++ b/integration/src/test/scala/org/knora/webapi/ITTestDataFactory.scala @@ -5,7 +5,6 @@ package org.knora.webapi -import dsp.valueobjects.Iri._ import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM._ /** @@ -26,9 +25,4 @@ object ITTestDataFactory { IriIdentifier .fromString(iri) .getOrElse(throw new IllegalArgumentException(s"Invalid IriIdentifier $iri.")) - - def projectIri(iri: String): ProjectIri = - ProjectIri - .make(iri) - .getOrElse(throw new IllegalArgumentException(s"Invalid ProjectIri $iri.")) } diff --git a/integration/src/test/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsMessagesADMSpec.scala b/integration/src/test/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsMessagesADMSpec.scala index 9161333946..6c3877aff2 100644 --- a/integration/src/test/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsMessagesADMSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsMessagesADMSpec.scala @@ -20,6 +20,7 @@ import org.knora.webapi.messages.store.triplestoremessages.StringLiteralSequence import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 import org.knora.webapi.sharedtestdata.SharedListsTestDataADM import org.knora.webapi.sharedtestdata.SharedTestDataADM +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri /** * This spec is used to test 'ListAdminMessages'. @@ -126,7 +127,7 @@ class ListsMessagesADMSpec extends CoreSpec with ListADMJsonProtocol { ListChildNodeCreateRequestADM( createChildNodeRequest = ListChildNodeCreatePayloadADM( parentNodeIri = ListIri.make(exampleListIri).fold(e => throw e.head, v => v), - projectIri = ProjectIri.make(SharedTestDataADM.imagesProjectIri).fold(e => throw e.head, v => v), + projectIri = ProjectIri.unsafeFrom(SharedTestDataADM.imagesProjectIri), position = Some(Position.make(-3).fold(e => throw e.head, v => v)), labels = Labels .make(Seq(V2.StringLiteralV2(value = "New child node", language = Some("en")))) diff --git a/integration/src/test/scala/org/knora/webapi/responders/admin/GroupsResponderADMSpec.scala b/integration/src/test/scala/org/knora/webapi/responders/admin/GroupsResponderADMSpec.scala index eeb494582b..08c79e9439 100644 --- a/integration/src/test/scala/org/knora/webapi/responders/admin/GroupsResponderADMSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/responders/admin/GroupsResponderADMSpec.scala @@ -20,6 +20,7 @@ import org.knora.webapi.messages.admin.responder.groupsmessages._ import org.knora.webapi.messages.admin.responder.usersmessages.UserInformationTypeADM import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 import org.knora.webapi.sharedtestdata.SharedTestDataADM +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.util.MutableTestIri import pekko.actor.Status.Failure @@ -80,7 +81,7 @@ class GroupsResponderADMSpec extends CoreSpec { ) ) .fold(e => throw e.head, v => v), - project = ProjectIri.make(SharedTestDataADM.imagesProjectIri).fold(e => throw e.head, v => v), + project = ProjectIri.unsafeFrom(SharedTestDataADM.imagesProjectIri), status = GroupStatus.active, selfjoin = GroupSelfJoin.make(false).fold(e => throw e.head, v => v) ), @@ -113,7 +114,7 @@ class GroupsResponderADMSpec extends CoreSpec { descriptions = GroupDescriptions .make(Seq(V2.StringLiteralV2(value = "NewGroupDescription", language = Some("en")))) .fold(e => throw e.head, v => v), - project = ProjectIri.make(SharedTestDataADM.imagesProjectIri).fold(e => throw e.head, v => v), + project = ProjectIri.unsafeFrom(SharedTestDataADM.imagesProjectIri), status = GroupStatus.active, selfjoin = GroupSelfJoin.make(false).fold(e => throw e.head, v => v) ), diff --git a/integration/src/test/scala/org/knora/webapi/responders/admin/ListsResponderADMSpec.scala b/integration/src/test/scala/org/knora/webapi/responders/admin/ListsResponderADMSpec.scala index c0f67bf89e..a252af4dc1 100644 --- a/integration/src/test/scala/org/knora/webapi/responders/admin/ListsResponderADMSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/responders/admin/ListsResponderADMSpec.scala @@ -5,7 +5,8 @@ package org.knora.webapi.responders.admin -import org.apache.pekko +import org.apache.pekko.actor.Status.Failure +import org.apache.pekko.testkit._ import java.util.UUID @@ -25,11 +26,9 @@ import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2 import org.knora.webapi.sharedtestdata.SharedListsTestDataADM import org.knora.webapi.sharedtestdata.SharedTestDataADM import org.knora.webapi.sharedtestdata.SharedTestDataADM2._ +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.util.MutableTestIri -import pekko.actor.Status.Failure -import pekko.testkit._ - /** * Tests [[ListsResponderADM]]. */ @@ -155,7 +154,7 @@ class ListsResponderADMSpec extends CoreSpec with ImplicitSender { "create a list" in { appActor ! ListRootNodeCreateRequestADM( createRootNode = ListRootNodeCreatePayloadADM( - projectIri = ProjectIri.make(imagesProjectIri).fold(e => throw e.head, v => v), + projectIri = ProjectIri.unsafeFrom(imagesProjectIri), name = Some(ListName.make("neuelistename").fold(e => throw e.head, v => v)), labels = Labels .make(Seq(V2.StringLiteralV2(value = "Neue Liste", language = Some("de")))) @@ -195,7 +194,7 @@ class ListsResponderADMSpec extends CoreSpec with ImplicitSender { val nameWithSpecialCharacter = "a new \\\"name\\\"" appActor ! ListRootNodeCreateRequestADM( createRootNode = ListRootNodeCreatePayloadADM( - projectIri = ProjectIri.make(imagesProjectIri).fold(e => throw e.head, v => v), + projectIri = ProjectIri.unsafeFrom(imagesProjectIri), name = Some(ListName.make(nameWithSpecialCharacter).fold(e => throw e.head, v => v)), labels = Labels .make(Seq(V2.StringLiteralV2(value = labelWithSpecialCharacter, language = Some("de")))) @@ -235,7 +234,7 @@ class ListsResponderADMSpec extends CoreSpec with ImplicitSender { listIri = newListIri.get, changeNodeRequest = ListNodeChangePayloadADM( listIri = ListIri.make(newListIri.get).fold(e => throw e.head, v => v), - projectIri = ProjectIri.make(imagesProjectIri).fold(e => throw e.head, v => v), + projectIri = ProjectIri.unsafeFrom(imagesProjectIri), name = Some(ListName.make("updated name").fold(e => throw e.head, v => v)), labels = Some( Labels @@ -289,7 +288,7 @@ class ListsResponderADMSpec extends CoreSpec with ImplicitSender { "not update basic list information if name is duplicate" in { val name = Some(ListName.make("sommer").fold(e => throw e.head, v => v)) - val projectIRI = ProjectIri.make(imagesProjectIri).fold(e => throw e.head, v => v) + val projectIRI = ProjectIri.unsafeFrom(imagesProjectIri) appActor ! NodeInfoChangeRequestADM( listIri = newListIri.get, changeNodeRequest = ListNodeChangePayloadADM( @@ -313,7 +312,7 @@ class ListsResponderADMSpec extends CoreSpec with ImplicitSender { appActor ! ListChildNodeCreateRequestADM( createChildNodeRequest = ListChildNodeCreatePayloadADM( parentNodeIri = ListIri.make(newListIri.get).fold(e => throw e.head, v => v), - projectIri = ProjectIri.make(imagesProjectIri).fold(e => throw e.head, v => v), + projectIri = ProjectIri.unsafeFrom(imagesProjectIri), name = Some(ListName.make("first").fold(e => throw e.head, v => v)), labels = Labels .make(Seq(V2.StringLiteralV2(value = "New First Child List Node Value", language = Some("en")))) @@ -366,7 +365,7 @@ class ListsResponderADMSpec extends CoreSpec with ImplicitSender { appActor ! ListChildNodeCreateRequestADM( createChildNodeRequest = ListChildNodeCreatePayloadADM( parentNodeIri = ListIri.make(newListIri.get).fold(e => throw e.head, v => v), - projectIri = ProjectIri.make(imagesProjectIri).fold(e => throw e.head, v => v), + projectIri = ProjectIri.unsafeFrom(imagesProjectIri), name = Some(ListName.make("second").fold(e => throw e.head, v => v)), position = Some(Position.make(0).fold(e => throw e.head, v => v)), labels = Labels @@ -420,7 +419,7 @@ class ListsResponderADMSpec extends CoreSpec with ImplicitSender { appActor ! ListChildNodeCreateRequestADM( createChildNodeRequest = ListChildNodeCreatePayloadADM( parentNodeIri = ListIri.make(secondChildIri.get).fold(e => throw e.head, v => v), - projectIri = ProjectIri.make(imagesProjectIri).fold(e => throw e.head, v => v), + projectIri = ProjectIri.unsafeFrom(imagesProjectIri), name = Some(ListName.make("third").fold(e => throw e.head, v => v)), labels = Labels .make(Seq(V2.StringLiteralV2(value = "New Third Child List Node Value", language = Some("en")))) @@ -474,7 +473,7 @@ class ListsResponderADMSpec extends CoreSpec with ImplicitSender { appActor ! ListChildNodeCreateRequestADM( createChildNodeRequest = ListChildNodeCreatePayloadADM( parentNodeIri = ListIri.make(newListIri.get).fold(e => throw e.head, v => v), - projectIri = ProjectIri.make(imagesProjectIri).fold(e => throw e.head, v => v), + projectIri = ProjectIri.unsafeFrom(imagesProjectIri), name = Some(ListName.make("fourth").fold(e => throw e.head, v => v)), position = givenPosition, labels = Labels @@ -832,7 +831,7 @@ class ListsResponderADMSpec extends CoreSpec with ImplicitSender { requestingUser = SharedTestDataADM.anythingAdminUser, apiRequestID = UUID.randomUUID ) - expectMsg(Failure(BadRequestException(s"Node ${nodeInUseIri} cannot be deleted, because it is in use."))) + expectMsg(Failure(BadRequestException(s"Node $nodeInUseIri cannot be deleted, because it is in use."))) } @@ -845,7 +844,7 @@ class ListsResponderADMSpec extends CoreSpec with ImplicitSender { ) val usedChild = "http://rdfh.ch/lists/0001/treeList10" expectMsg( - Failure(BadRequestException(s"Node ${nodeIri} cannot be deleted, because its child ${usedChild} is in use.")) + Failure(BadRequestException(s"Node $nodeIri cannot be deleted, because its child $usedChild is in use.")) ) } @@ -858,7 +857,7 @@ class ListsResponderADMSpec extends CoreSpec with ImplicitSender { apiRequestID = UUID.randomUUID ) expectMsg( - Failure(BadRequestException(s"Node ${nodeInUseInOntologyIri} cannot be deleted, because it is in use.")) + Failure(BadRequestException(s"Node $nodeInUseInOntologyIri cannot be deleted, because it is in use.")) ) } diff --git a/integration/src/test/scala/org/knora/webapi/responders/admin/ProjectsResponderADMSpec.scala b/integration/src/test/scala/org/knora/webapi/responders/admin/ProjectsResponderADMSpec.scala index 6f2fce2107..c4e89469c1 100644 --- a/integration/src/test/scala/org/knora/webapi/responders/admin/ProjectsResponderADMSpec.scala +++ b/integration/src/test/scala/org/knora/webapi/responders/admin/ProjectsResponderADMSpec.scala @@ -44,6 +44,7 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender { "used to query for project information" should { "return information for every project excluding system projects" in { appActor ! ProjectsGetRequestADM() + val received = expectMsgType[ProjectsGetResponseADM](timeout) assert(received.projects.contains(SharedTestDataADM.imagesProject)) assert(received.projects.contains(SharedTestDataADM.incunabulaProject)) @@ -178,8 +179,8 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender { SharedTestDataADM.rootUser, UUID.randomUUID() ) - val received: ProjectOperationResponseADM = expectMsgType[ProjectOperationResponseADM](timeout) + val received: ProjectOperationResponseADM = expectMsgType[ProjectOperationResponseADM](timeout) received.project.shortname should be("newproject") received.project.shortcode should be(shortcode.toUpperCase) // upper case received.project.longname should contain("project longname") @@ -195,6 +196,7 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender { requestingUser = rootUser, apiRequestID = UUID.randomUUID() ) + // Check Administrative Permission of ProjectAdmin val receivedApAdmin: AdministrativePermissionsForProjectGetResponseADM = expectMsgType[AdministrativePermissionsForProjectGetResponseADM] @@ -304,8 +306,8 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender { SharedTestDataADM.rootUser, UUID.randomUUID() ) - val received: ProjectOperationResponseADM = expectMsgType[ProjectOperationResponseADM](timeout) + val received: ProjectOperationResponseADM = expectMsgType[ProjectOperationResponseADM](timeout) received.project.longname should contain(Iri.fromSparqlEncodedString(longnameWithSpecialCharacter)) received.project.description should be( Seq( @@ -316,7 +318,6 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender { ) ) received.project.keywords should contain(Iri.fromSparqlEncodedString(keywordWithSpecialCharacter)) - } "return a 'DuplicateValueException' during creation if the supplied project shortname is not unique" in { @@ -358,7 +359,7 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender { } "UPDATE a project" in { - val iri = ITTestDataFactory.projectIri(newProjectIri.get) + val iri = ProjectIri.unsafeFrom(newProjectIri.get) val updatedLongname = Longname.unsafeFrom("updated project longname") val updatedDescription = List( Description.unsafeFrom( @@ -404,7 +405,7 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender { "return 'NotFound' if a not existing project IRI is submitted during update" in { val longname = Longname.unsafeFrom("longname") - val iri = ITTestDataFactory.projectIri(notExistingProjectButValidProjectIri) + val iri = ProjectIri.unsafeFrom(notExistingProjectButValidProjectIri) appActor ! ProjectChangeRequestADM( projectIri = iri, projectUpdatePayload = ProjectUpdateRequest(longname = Some(longname)), @@ -600,33 +601,29 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender { "used to query keywords" should { "return all unique keywords for all projects" in { appActor ! ProjectsKeywordsGetRequestADM() + val received: ProjectsKeywordsGetResponseADM = expectMsgType[ProjectsKeywordsGetResponseADM](timeout) received.keywords.size should be(21) } "return all keywords for a single project" in { - val iri = ITTestDataFactory.projectIri(SharedTestDataADM.incunabulaProject.id) - appActor ! ProjectKeywordsGetRequestADM( - projectIri = iri + appActor ! ProjectKeywordsGetRequestADM(projectIri = + ProjectIri.unsafeFrom(SharedTestDataADM.incunabulaProject.id) ) + val received: ProjectKeywordsGetResponseADM = expectMsgType[ProjectKeywordsGetResponseADM](timeout) received.keywords should be(SharedTestDataADM.incunabulaProject.keywords) } "return empty list for a project without keywords" in { - val iri = ITTestDataFactory.projectIri(SharedTestDataADM.dokubibProject.id) - appActor ! ProjectKeywordsGetRequestADM( - projectIri = iri - ) + appActor ! ProjectKeywordsGetRequestADM(ProjectIri.unsafeFrom(SharedTestDataADM.dokubibProject.id)) + val received: ProjectKeywordsGetResponseADM = expectMsgType[ProjectKeywordsGetResponseADM](timeout) received.keywords should be(Seq.empty[String]) } "return 'NotFound' when the project IRI is unknown" in { - val iri = ITTestDataFactory.projectIri(notExistingProjectButValidProjectIri) - appActor ! ProjectKeywordsGetRequestADM( - projectIri = iri - ) + appActor ! ProjectKeywordsGetRequestADM(ProjectIri.unsafeFrom(notExistingProjectButValidProjectIri)) expectMsg(Failure(NotFoundException(s"Project '$notExistingProjectButValidProjectIri' not found."))) } diff --git a/webapi/src/main/scala/dsp/valueobjects/Iri.scala b/webapi/src/main/scala/dsp/valueobjects/Iri.scala index d01825550b..f0be8970ad 100644 --- a/webapi/src/main/scala/dsp/valueobjects/Iri.scala +++ b/webapi/src/main/scala/dsp/valueobjects/Iri.scala @@ -8,7 +8,6 @@ package dsp.valueobjects import com.google.gwt.safehtml.shared.UriUtils.encodeAllowEscapes import org.apache.commons.lang3.StringUtils import org.apache.commons.validator.routines.UrlValidator -import zio.json.JsonCodec import zio.json.JsonDecoder import zio.json.JsonEncoder import zio.prelude.Validation @@ -18,9 +17,10 @@ import scala.util.Try import dsp.errors.BadRequestException import dsp.errors.ValidationException -sealed trait Iri { +trait Iri { val value: String } + object Iri { type IRI = String @@ -229,40 +229,6 @@ object Iri { } } - /** - * ProjectIri value object. - */ - sealed abstract case class ProjectIri private (value: String) extends Iri - object ProjectIri { self => - - implicit val codec: JsonCodec[ProjectIri] = new JsonCodec[ProjectIri]( - JsonEncoder[String].contramap(_.value), - JsonDecoder[String].mapOrFail(ProjectIri.make(_).toEitherWith(e => e.head.getMessage)) - ) - - def make(value: String): Validation[ValidationException, ProjectIri] = - if (value.isEmpty) Validation.fail(ValidationException(IriErrorMessages.ProjectIriMissing)) - else { - val isUuid: Boolean = UuidUtil.hasValidLength(value.split("/").last) - - if (!isProjectIri(value)) - Validation.fail(ValidationException(IriErrorMessages.ProjectIriInvalid)) - else if (isUuid && !UuidUtil.hasSupportedVersion(value)) - Validation.fail(ValidationException(IriErrorMessages.UuidVersionInvalid)) - else - Validation - .fromOption(validateAndEscapeProjectIri(value)) - .mapError(_ => ValidationException(IriErrorMessages.ProjectIriInvalid)) - .map(new ProjectIri(_) {}) - } - - def make(value: Option[String]): Validation[ValidationException, Option[ProjectIri]] = - value match { - case Some(v) => self.make(v).map(Some(_)) - case None => Validation.succeed(None) - } - } - /** * Base64Uuid value object. * This is base64 encoded UUID version without paddings. diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsPayloadsADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsPayloadsADM.scala index 909331d06c..634bc8c5e5 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsPayloadsADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/groupsmessages/GroupsPayloadsADM.scala @@ -7,6 +7,7 @@ package org.knora.webapi.messages.admin.responder.groupsmessages import dsp.valueobjects.Group.* import dsp.valueobjects.Iri.* +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri /** * Group create payload diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsPayloadsADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsPayloadsADM.scala index bc92735f0a..31b3c30a00 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsPayloadsADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/listsmessages/ListsPayloadsADM.scala @@ -7,6 +7,7 @@ package org.knora.webapi.messages.admin.responder.listsmessages import dsp.valueobjects.Iri.* import dsp.valueobjects.List.* +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri /** * List root node and child node creation payloads diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/permissionsmessages/PermissionsMessagesADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/permissionsmessages/PermissionsMessagesADM.scala index 20ae4e0963..0b85a398d2 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/permissionsmessages/PermissionsMessagesADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/permissionsmessages/PermissionsMessagesADM.scala @@ -23,7 +23,6 @@ import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectsADMJso import org.knora.webapi.messages.admin.responder.usersmessages.UserADM import org.knora.webapi.messages.store.triplestoremessages.TriplestoreJsonProtocol import org.knora.webapi.messages.traits.Jsonable -import org.knora.webapi.slice.resourceinfo.domain.InternalIri import pekko.http.scaladsl.marshallers.sprayjson.SprayJsonSupport @@ -881,7 +880,6 @@ case class PermissionsDataADM( /* Is the user a member of the ProjectAdmin group */ def isProjectAdmin(projectIri: IRI): Boolean = groupsPerProject.getOrElse(projectIri, List.empty[IRI]).contains(OntologyConstants.KnoraAdmin.ProjectAdmin) - def isProjectAdmin(projectIri: InternalIri): Boolean = isProjectAdmin(projectIri.value) /* Does the user have the 'ProjectAdminAllPermission' permission for the project */ def hasProjectAdminAllPermissionFor(projectIri: IRI): Boolean = diff --git a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/projectsmessages/ProjectsMessagesADM.scala b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/projectsmessages/ProjectsMessagesADM.scala index 16e0a95331..fbc57db0eb 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/projectsmessages/ProjectsMessagesADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/admin/responder/projectsmessages/ProjectsMessagesADM.scala @@ -24,7 +24,6 @@ import dsp.errors.BadRequestException import dsp.errors.OntologyConstraintException import dsp.errors.ValidationException import dsp.valueobjects.Iri -import dsp.valueobjects.Iri.ProjectIri import dsp.valueobjects.RestrictedViewSize import dsp.valueobjects.V2 import org.knora.webapi.IRI @@ -374,7 +373,7 @@ object ProjectIdentifierADM { ) def fromString(value: String): Validation[ValidationException, IriIdentifier] = - ProjectIri.make(value).map(IriIdentifier(_)) + ProjectIri.from(value).map(IriIdentifier(_)) implicit val tapirCodec: Codec[String, IriIdentifier, TextPlain] = Codec.string.mapDecode(str => diff --git a/webapi/src/main/scala/org/knora/webapi/messages/store/triplestoremessages/TriplestoreMessages.scala b/webapi/src/main/scala/org/knora/webapi/messages/store/triplestoremessages/TriplestoreMessages.scala index 4b09724252..24c05680cf 100644 --- a/webapi/src/main/scala/org/knora/webapi/messages/store/triplestoremessages/TriplestoreMessages.scala +++ b/webapi/src/main/scala/org/knora/webapi/messages/store/triplestoremessages/TriplestoreMessages.scala @@ -161,7 +161,9 @@ case class RdfDataObject(path: String, name: String) /** * Represents the subject of a statement read from the triplestore. */ -sealed trait SubjectV2 +sealed trait SubjectV2 { + def value: String +} /** * Represents an IRI used as the subject of a statement. diff --git a/webapi/src/main/scala/org/knora/webapi/responders/admin/ListsResponderADM.scala b/webapi/src/main/scala/org/knora/webapi/responders/admin/ListsResponderADM.scala index ed12ebb2f6..0acd13eb93 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/admin/ListsResponderADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/admin/ListsResponderADM.scala @@ -39,6 +39,7 @@ import org.knora.webapi.messages.twirl.queries.sparql import org.knora.webapi.responders.IriLocker import org.knora.webapi.responders.IriService import org.knora.webapi.responders.Responder +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.admin.domain.service.ProjectADMService import org.knora.webapi.store.triplestore.api.TriplestoreService import org.knora.webapi.store.triplestore.api.TriplestoreService.Queries.Ask @@ -1056,7 +1057,9 @@ final case class ListsResponderADMLive( for { projectIri <- getProjectIriFromNode(nodeIri) // check if the requesting user is allowed to perform operation - _ = if (!requestingUser.permissions.isProjectAdmin(projectIri) && !requestingUser.permissions.isSystemAdmin) { + _ = if ( + !requestingUser.permissions.isProjectAdmin(projectIri.value) && !requestingUser.permissions.isSystemAdmin + ) { // not project or a system admin throw ForbiddenException(ListErrorMessages.ListChangePermission) } @@ -1065,7 +1068,7 @@ final case class ListsResponderADMLive( getUpdateNodeInfoSparqlStatement( changeNodeInfoRequest = ListNodeChangePayloadADM( listIri = ListIri.make(nodeIri).fold(e => throw e.head, v => v), - projectIri = ProjectIri.make(projectIri).fold(e => throw e.head, v => v), + projectIri = projectIri, name = Some(changeNodeNameRequest.name) ) ) @@ -1122,14 +1125,16 @@ final case class ListsResponderADMLive( projectIri <- getProjectIriFromNode(nodeIri) // check if the requesting user is allowed to perform operation - _ = if (!requestingUser.permissions.isProjectAdmin(projectIri) && !requestingUser.permissions.isSystemAdmin) { + _ = if ( + !requestingUser.permissions.isProjectAdmin(projectIri.value) && !requestingUser.permissions.isSystemAdmin + ) { // not project or a system admin throw ForbiddenException(ListErrorMessages.ListChangePermission) } changeNodeLabelsSparql <- getUpdateNodeInfoSparqlStatement( changeNodeInfoRequest = ListNodeChangePayloadADM( listIri = ListIri.make(nodeIri).fold(e => throw e.head, v => v), - projectIri = ProjectIri.make(projectIri).fold(e => throw e.head, v => v), + projectIri = projectIri, labels = Some(changeNodeLabelsRequest.labels) ) ) @@ -1185,7 +1190,9 @@ final case class ListsResponderADMLive( projectIri <- getProjectIriFromNode(nodeIri) // check if the requesting user is allowed to perform operation - _ = if (!requestingUser.permissions.isProjectAdmin(projectIri) && !requestingUser.permissions.isSystemAdmin) { + _ = if ( + !requestingUser.permissions.isProjectAdmin(projectIri.value) && !requestingUser.permissions.isSystemAdmin + ) { // not project or a system admin throw ForbiddenException(ListErrorMessages.ListChangePermission) } @@ -1193,7 +1200,7 @@ final case class ListsResponderADMLive( changeNodeCommentsSparql <- getUpdateNodeInfoSparqlStatement( ListNodeChangePayloadADM( listIri = ListIri.make(nodeIri).fold(e => throw e.head, v => v), - projectIri = ProjectIri.make(projectIri).fold(e => throw e.head, v => v), + projectIri = projectIri, comments = Some(changeNodeCommentsRequest.comments) ) ) @@ -1474,7 +1481,9 @@ final case class ListsResponderADMLive( dataNamedGraph <- getDataNamedGraph(projectIri) // check if the requesting user is allowed to perform operation - _ = if (!requestingUser.permissions.isProjectAdmin(projectIri) && !requestingUser.permissions.isSystemAdmin) { + _ = if ( + !requestingUser.permissions.isProjectAdmin(projectIri.value) && !requestingUser.permissions.isSystemAdmin + ) { // not project or a system admin throw ForbiddenException(ListErrorMessages.ListChangePermission) } @@ -1616,7 +1625,7 @@ final case class ListsResponderADMLive( */ def deleteListItem( nodeIri: IRI, - projectIri: IRI, + projectIri: ProjectIri, children: Seq[ListChildNodeADM], isRootNode: Boolean ): Task[IRI] = @@ -1714,7 +1723,9 @@ final case class ListsResponderADMLive( projectIri <- getProjectIriFromNode(nodeIri) // check if the requesting user is allowed to perform operation - _ = if (!requestingUser.permissions.isProjectAdmin(projectIri) && !requestingUser.permissions.isSystemAdmin) { + _ = if ( + !requestingUser.permissions.isProjectAdmin(projectIri.value) && !requestingUser.permissions.isSystemAdmin + ) { // not project or a system admin throw ForbiddenException(ListErrorMessages.ListChangePermission) } @@ -1822,7 +1833,7 @@ final case class ListsResponderADMLive( ): Task[String] = for { // get the data graph of the project. - dataNamedGraph <- getDataNamedGraph(changeNodeInfoRequest.projectIri.value) + dataNamedGraph <- getDataNamedGraph(changeNodeInfoRequest.projectIri) /* verify that the list name is unique for the project */ nodeNameUnique <- listNodeNameIsProjectUnique( @@ -1862,29 +1873,31 @@ final case class ListsResponderADMLive( * Helper method to get projectIri of a node. * * @param nodeIri the IRI of the node. - * @return a [[IRI]]. + * @return a [[ProjectIri]]. */ - private def getProjectIriFromNode(nodeIri: IRI): Task[IRI] = + private def getProjectIriFromNode(nodeIri: IRI): Task[ProjectIri] = for { maybeNode <- listNodeGetADM(nodeIri = nodeIri, shallow = true) - projectIri <- maybeNode match { - case Some(rootNode: ListRootNodeADM) => ZIO.attempt(rootNode.projectIri) - - case Some(childNode: ListChildNodeADM) => - for { - maybeRoot <- listNodeGetADM(nodeIri = childNode.hasRootNode, shallow = true) - rootProjectIri = maybeRoot match { - case Some(rootNode: ListRootNodeADM) => rootNode.projectIri - case _ => - throw BadRequestException( - s"Root node of $nodeIri was not found. Please verify the given IRI." - ) - } - } yield rootProjectIri - - case _ => throw BadRequestException(s"Node $nodeIri was not found. Please verify the given IRI.") - } + projectIriStr <- maybeNode match { + case Some(rootNode: ListRootNodeADM) => ZIO.succeed(rootNode.projectIri) + + case Some(childNode: ListChildNodeADM) => + for { + maybeRoot <- listNodeGetADM(childNode.hasRootNode, shallow = true) + iriStr <- maybeRoot.collect { case it: ListRootNodeADM => it } + .map(rootNode => ZIO.succeed(rootNode.projectIri)) + .getOrElse(ZIO.fail { + val msg = + s"Root node of $nodeIri was not found. Please verify the given IRI." + BadRequestException(msg) + }) + } yield iriStr + + case _ => + throw BadRequestException(s"Node $nodeIri was not found. Please verify the given IRI.") + } + projectIri <- ProjectIri.from(projectIriStr).toZIO.mapError(e => BadRequestException(e.getMessage)) } yield projectIri /** @@ -1906,11 +1919,10 @@ final case class ListsResponderADMLive( * @param projectIri the IRI of the project. * @return an [[IRI]]. */ - private def getDataNamedGraph(projectIri: IRI): Task[IRI] = + private def getDataNamedGraph(projectIri: ProjectIri): Task[IRI] = for { - projectId <- IriIdentifier.fromString(projectIri).toZIO.mapError(e => BadRequestException(e.getMessage)) project <- messageRelay - .ask[Option[ProjectADM]](ProjectGetADM(projectId)) + .ask[Option[ProjectADM]](ProjectGetADM(IriIdentifier.from(projectIri))) .someOrFail(BadRequestException(s"Project '$projectIri' not found.")) } yield ProjectADMService.projectDataNamedGraphV2(project).value diff --git a/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala b/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala index 62b754411b..d1b13f5c95 100644 --- a/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/responders/admin/ProjectsResponderADM.scala @@ -40,6 +40,7 @@ import org.knora.webapi.responders.Responder import org.knora.webapi.slice.admin.AdminConstants import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequests.ProjectCreateRequest import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequests.ProjectUpdateRequest +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.admin.domain.service.ProjectADMService import org.knora.webapi.store.cache.settings.CacheServiceSettings import org.knora.webapi.store.triplestore.api.TriplestoreService @@ -114,7 +115,7 @@ trait ProjectsResponderADM { * @param projectIri the IRI of the project. * @return keywords for a projects as [[ProjectKeywordsGetResponseADM]] */ - def projectKeywordsGetRequestADM(projectIri: Iri.ProjectIri): Task[ProjectKeywordsGetResponseADM] + def projectKeywordsGetRequestADM(projectIri: ProjectIri): Task[ProjectKeywordsGetResponseADM] /** * Get project's restricted view settings. @@ -166,7 +167,7 @@ trait ProjectsResponderADM { * [[ForbiddenException]] In the case that the user is not allowed to perform the operation. */ def changeBasicInformationRequestADM( - projectIri: Iri.ProjectIri, + projectIri: ProjectIri, updateReq: ProjectUpdateRequest, user: UserADM, apiRequestID: UUID @@ -389,7 +390,7 @@ final case class ProjectsResponderADMLive( * @param projectIri the IRI of the project. * @return keywords for a projects as [[ProjectKeywordsGetResponseADM]] */ - override def projectKeywordsGetRequestADM(projectIri: Iri.ProjectIri): Task[ProjectKeywordsGetResponseADM] = + override def projectKeywordsGetRequestADM(projectIri: ProjectIri): Task[ProjectKeywordsGetResponseADM] = for { id <- IriIdentifier.fromString(projectIri.value).toZIO.mapError(e => BadRequestException(e.getMessage)) keywords <- projectService @@ -466,7 +467,7 @@ final case class ProjectsResponderADMLive( * [[ForbiddenException]] In the case that the user is not allowed to perform the operation. */ override def changeBasicInformationRequestADM( - projectIri: Iri.ProjectIri, + projectIri: ProjectIri, updateReq: ProjectUpdateRequest, user: UserADM, apiRequestID: UUID @@ -476,7 +477,7 @@ final case class ProjectsResponderADMLive( * The actual change project task run with an IRI lock. */ def changeProjectTask( - projectIri: Iri.ProjectIri, + projectIri: ProjectIri, updateReq: ProjectUpdateRequest, requestingUser: UserADM ): Task[ProjectOperationResponseADM] = @@ -503,7 +504,7 @@ final case class ProjectsResponderADMLive( * * [[NotFoundException]] In the case that the project's IRI is not found. */ - private def updateProjectADM(projectIri: Iri.ProjectIri, projectUpdatePayload: ProjectUpdateRequest) = { + private def updateProjectADM(projectIri: ProjectIri, projectUpdatePayload: ProjectUpdateRequest) = { val areAllParamsNone: Boolean = projectUpdatePayload.productIterator.forall { case param: Option[Any] => param.isEmpty diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/GroupsRouteADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/GroupsRouteADM.scala index 4077f761a3..6910028b6e 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/GroupsRouteADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/admin/GroupsRouteADM.scala @@ -21,6 +21,7 @@ import org.knora.webapi.routing.KnoraRoute import org.knora.webapi.routing.KnoraRouteData import org.knora.webapi.routing.RouteUtilADM.* import org.knora.webapi.routing.RouteUtilZ +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import pekko.http.scaladsl.server.Directives.* import pekko.http.scaladsl.server.PathMatcher @@ -78,7 +79,7 @@ final case class GroupsRouteADM( val id: Validation[Throwable, Option[GroupIri]] = GroupIri.make(apiRequest.id) val name: Validation[Throwable, GroupName] = GroupName.make(apiRequest.name) val descriptions: Validation[Throwable, GroupDescriptions] = GroupDescriptions.make(apiRequest.descriptions) - val project: Validation[Throwable, ProjectIri] = ProjectIri.make(apiRequest.project) + val project: Validation[Throwable, ProjectIri] = ProjectIri.from(apiRequest.project) val status: Validation[Throwable, GroupStatus] = Validation.succeed(GroupStatus.make(apiRequest.status)) val selfjoin: Validation[Throwable, GroupSelfJoin] = GroupSelfJoin.make(apiRequest.selfjoin) val payloadValidation: Validation[Throwable, GroupCreatePayloadADM] = diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/CreateListItemsRouteADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/CreateListItemsRouteADM.scala index 59fb441171..a2990a28b7 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/CreateListItemsRouteADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/CreateListItemsRouteADM.scala @@ -25,6 +25,7 @@ import org.knora.webapi.routing.Authenticator import org.knora.webapi.routing.KnoraRoute import org.knora.webapi.routing.KnoraRouteData import org.knora.webapi.routing.RouteUtilADM.* +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import pekko.http.scaladsl.server.Directives.* import pekko.http.scaladsl.server.PathMatcher @@ -54,7 +55,7 @@ final case class CreateListItemsRouteADM( post { entity(as[ListRootNodeCreateApiRequestADM]) { apiRequest => requestContext => val maybeId: Validation[Throwable, Option[ListIri]] = ListIri.make(apiRequest.id) - val projectIri: Validation[Throwable, ProjectIri] = ProjectIri.make(apiRequest.projectIri) + val projectIri: Validation[Throwable, ProjectIri] = ProjectIri.from(apiRequest.projectIri) val maybeName: Validation[Throwable, Option[ListName]] = ListName.make(apiRequest.name) val labels: Validation[Throwable, Labels] = Labels.make(apiRequest.labels) val comments: Validation[Throwable, Comments] = Comments.make(apiRequest.comments) @@ -86,7 +87,7 @@ final case class CreateListItemsRouteADM( .when(iri != apiRequest.parentNodeIri) parentNodeIri = ListIri.make(apiRequest.parentNodeIri) id = ListIri.make(apiRequest.id) - projectIri = ProjectIri.make(apiRequest.projectIri) + projectIri = ProjectIri.from(apiRequest.projectIri) name = ListName.make(apiRequest.name) position = Position.make(apiRequest.position) labels = Labels.make(apiRequest.labels) diff --git a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/UpdateListItemsRouteADM.scala b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/UpdateListItemsRouteADM.scala index 051b8bb0ff..7a9653f1d9 100644 --- a/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/UpdateListItemsRouteADM.scala +++ b/webapi/src/main/scala/org/knora/webapi/routing/admin/lists/UpdateListItemsRouteADM.scala @@ -24,6 +24,7 @@ import org.knora.webapi.routing.Authenticator import org.knora.webapi.routing.KnoraRoute import org.knora.webapi.routing.KnoraRouteData import org.knora.webapi.routing.RouteUtilADM.* +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import pekko.http.scaladsl.server.Directives.* import pekko.http.scaladsl.server.PathMatcher @@ -135,7 +136,7 @@ final case class UpdateListItemsRouteADM( val validatedPayload = for { _ <- ZIO.fail(BadRequestException("Route and payload listIri mismatch.")).when(iri != apiRequest.listIri) listIri = ListIri.make(apiRequest.listIri) - projectIri = ProjectIri.make(apiRequest.projectIri) + projectIri = ProjectIri.from(apiRequest.projectIri) hasRootNode = ListIri.make(apiRequest.hasRootNode) position = Position.make(apiRequest.position) name = ListName.make(apiRequest.name) diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/model/ProjectsEndpointsRequests.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/model/ProjectsEndpointsRequests.scala index 3f180730ef..95dee8d3bf 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/model/ProjectsEndpointsRequests.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/model/ProjectsEndpointsRequests.scala @@ -8,7 +8,6 @@ package org.knora.webapi.slice.admin.api.model import zio.json.DeriveJsonCodec import zio.json.JsonCodec -import dsp.valueobjects.Iri.ProjectIri import org.knora.webapi.slice.admin.domain.model.KnoraProject.* object ProjectsEndpointsRequests { diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/ProjectsADMRestService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/ProjectsADMRestService.scala index ecbbc9a92a..fef388b285 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/ProjectsADMRestService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/api/service/ProjectsADMRestService.scala @@ -10,7 +10,6 @@ import zio.macros.accessible import dsp.errors.BadRequestException import dsp.errors.NotFoundException -import dsp.valueobjects.Iri.ProjectIri import dsp.valueobjects.RestrictedViewSize import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.* import org.knora.webapi.messages.admin.responder.projectsmessages.* @@ -22,6 +21,7 @@ import org.knora.webapi.slice.admin.api.model.ProjectImportResponse import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequests.ProjectCreateRequest import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequests.ProjectSetRestrictedViewSizeRequest import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequests.ProjectUpdateRequest +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode import org.knora.webapi.slice.admin.domain.model.KnoraProject.Status import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/KnoraProject.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/KnoraProject.scala index 715add74b2..dc47b1ed89 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/KnoraProject.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/model/KnoraProject.scala @@ -14,12 +14,16 @@ import scala.util.matching.Regex import dsp.errors.ValidationException import dsp.valueobjects.Iri +import dsp.valueobjects.Iri.isProjectIri +import dsp.valueobjects.Iri.validateAndEscapeProjectIri +import dsp.valueobjects.IriErrorMessages +import dsp.valueobjects.UuidUtil import dsp.valueobjects.V2 import org.knora.webapi.slice.admin.domain.model.KnoraProject.* import org.knora.webapi.slice.resourceinfo.domain.InternalIri case class KnoraProject( - id: InternalIri, + id: ProjectIri, shortname: Shortname, shortcode: Shortcode, longname: Option[Longname], @@ -32,6 +36,29 @@ case class KnoraProject( ) object KnoraProject { + final case class ProjectIri private (value: String) extends AnyVal + + object ProjectIri { + + implicit val codec: JsonCodec[ProjectIri] = + JsonCodec[String].transformOrFail(ProjectIri.from(_).toEitherWith(e => e.head.getMessage), _.value) + + def unsafeFrom(str: String): ProjectIri = from(str).fold(e => throw e.head, identity) + + def from(str: String): Validation[ValidationException, ProjectIri] = str match { + case str if str.isEmpty => + Validation.fail(ValidationException(IriErrorMessages.ProjectIriMissing)) + case str if !isProjectIri(str) => + Validation.fail(ValidationException(IriErrorMessages.ProjectIriInvalid)) + case str if UuidUtil.hasValidLength(str.split("/").last) && !UuidUtil.hasSupportedVersion(str) => + Validation.fail(ValidationException(IriErrorMessages.UuidVersionInvalid)) + case _ => + Validation + .fromOption(validateAndEscapeProjectIri(str)) + .mapError(_ => ValidationException(IriErrorMessages.ProjectIriInvalid)) + .map(ProjectIri(_)) + } + } final case class Shortcode private (value: String) extends AnyVal diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraProjectRepo.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraProjectRepo.scala index 17527a8844..c9d50b5787 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraProjectRepo.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/KnoraProjectRepo.scala @@ -9,11 +9,11 @@ import zio.Task import dsp.valueobjects.RestrictedViewSize import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM import org.knora.webapi.slice.admin.domain.model.KnoraProject +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.admin.domain.model.KnoraProject.Shortcode import org.knora.webapi.slice.common.repo.service.Repository -import org.knora.webapi.slice.resourceinfo.domain.InternalIri -trait KnoraProjectRepo extends Repository[KnoraProject, InternalIri] { +trait KnoraProjectRepo extends Repository[KnoraProject, ProjectIri] { def findById(id: ProjectIdentifierADM): Task[Option[KnoraProject]] def findByShortcode(code: Shortcode): Task[Option[KnoraProject]] = findById(ProjectIdentifierADM.ShortcodeIdentifier(code)) diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectADMService.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectADMService.scala index 5d2ea8aa16..85aca29840 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectADMService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/domain/service/ProjectADMService.scala @@ -83,7 +83,7 @@ final case class ProjectADMServiceLive( private def toKnoraProject(project: ProjectADM): KnoraProject = KnoraProject( - id = InternalIri.apply(project.id), + id = ProjectIri.unsafeFrom(project.id), shortname = Shortname.unsafeFrom(project.shortname), shortcode = Shortcode.unsafeFrom(project.shortcode), longname = project.longname.map(Longname.unsafeFrom), diff --git a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/KnoraProjectRepoLive.scala b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/KnoraProjectRepoLive.scala index 69c8b4fc40..090008e232 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/KnoraProjectRepoLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/admin/repo/service/KnoraProjectRepoLive.scala @@ -34,7 +34,7 @@ final case class KnoraProjectRepoLive( private implicit val sf: StringFormatter ) extends KnoraProjectRepo { - override def findById(id: InternalIri): Task[Option[KnoraProject]] = + override def findById(id: ProjectIri): Task[Option[KnoraProject]] = findOneByQuery(sparql.admin.txt.getProjects(maybeIri = Some(id.value), None, None)) override def findById(id: ProjectIdentifierADM): Task[Option[KnoraProject]] = { @@ -62,37 +62,38 @@ final case class KnoraProjectRepoLive( } private def toKnoraProject(subjectPropsTuple: (SubjectV2, ConstructPredicateObjects)): Task[KnoraProject] = { - val projectIri = InternalIri(subjectPropsTuple._1.toString) - val propsMap = subjectPropsTuple._2 + val (subject, propertiesMap) = subjectPropsTuple for { + projectIri <- ProjectIri.from(subject.value).toZIO shortname <- mapper - .getSingleOrFail[StringLiteralV2](ProjectShortname, propsMap) + .getSingleOrFail[StringLiteralV2](ProjectShortname, propertiesMap) .flatMap(l => Shortname.from(l.value).toZIO) shortcode <- mapper - .getSingleOrFail[StringLiteralV2](ProjectShortcode, propsMap) + .getSingleOrFail[StringLiteralV2](ProjectShortcode, propertiesMap) .flatMap(l => Shortcode.from(l.value).toZIO) longname <- mapper - .getSingleOption[StringLiteralV2](ProjectLongname, propsMap) + .getSingleOption[StringLiteralV2](ProjectLongname, propertiesMap) .flatMap(optLit => ZIO.foreach(optLit)(l => Longname.from(l.value).toZIO)) description <- mapper - .getNonEmptyChunkOrFail[StringLiteralV2](ProjectDescription, propsMap) + .getNonEmptyChunkOrFail[StringLiteralV2](ProjectDescription, propertiesMap) .map(_.map(l => V2.StringLiteralV2(l.value, l.language))) .flatMap(ZIO.foreach(_)(Description.from(_).toZIO)) keywords <- mapper - .getList[StringLiteralV2](ProjectKeyword, propsMap) + .getList[StringLiteralV2](ProjectKeyword, propertiesMap) .flatMap(l => ZIO.foreach(l.map(_.value).sorted)(Keyword.from(_).toZIO)) logo <- mapper - .getSingleOption[StringLiteralV2](ProjectLogo, propsMap) + .getSingleOption[StringLiteralV2](ProjectLogo, propertiesMap) .flatMap(optLit => ZIO.foreach(optLit)(l => Logo.from(l.value).toZIO)) status <- mapper - .getSingleOrFail[BooleanLiteralV2](StatusProp, propsMap) + .getSingleOrFail[BooleanLiteralV2](StatusProp, propertiesMap) .map(l => Status.from(l.value)) selfjoin <- mapper - .getSingleOrFail[BooleanLiteralV2](HasSelfJoinEnabled, propsMap) + .getSingleOrFail[BooleanLiteralV2](HasSelfJoinEnabled, propertiesMap) .map(l => SelfJoin.from(l.value)) - ontologies <- mapper - .getList[IriLiteralV2]("http://www.knora.org/ontology/knora-admin#belongsToOntology", propsMap) - .map(_.map(l => InternalIri(l.value))) + ontologies <- + mapper + .getList[IriLiteralV2]("http://www.knora.org/ontology/knora-admin#belongsToOntology", propertiesMap) + .map(_.map(l => InternalIri(l.value))) } yield KnoraProject( projectIri, shortname, diff --git a/webapi/src/main/scala/org/knora/webapi/slice/common/api/RestPermissionService.scala b/webapi/src/main/scala/org/knora/webapi/slice/common/api/RestPermissionService.scala index 6a4cd38e4c..b5972671c2 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/common/api/RestPermissionService.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/common/api/RestPermissionService.scala @@ -53,7 +53,7 @@ trait RestPermissionService { object RestPermissionService { def isActive(userADM: UserADM): Boolean = userADM.status def isSystemAdmin(user: UserADM): Boolean = user.permissions.isSystemAdmin - def isProjectAdmin(user: UserADM, project: KnoraProject): Boolean = user.permissions.isProjectAdmin(project.id) + def isProjectAdmin(user: UserADM, project: KnoraProject): Boolean = user.permissions.isProjectAdmin(project.id.value) def isSystemOrProjectAdmin(project: KnoraProject)(userADM: UserADM): Boolean = isSystemAdmin(userADM) || isProjectAdmin(userADM, project) } diff --git a/webapi/src/main/scala/org/knora/webapi/slice/ontology/domain/service/OntologyRepo.scala b/webapi/src/main/scala/org/knora/webapi/slice/ontology/domain/service/OntologyRepo.scala index 2470b89fd6..f3b6ff2952 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/ontology/domain/service/OntologyRepo.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/ontology/domain/service/OntologyRepo.scala @@ -11,6 +11,7 @@ import zio.macros.accessible import org.knora.webapi.messages.v2.responder.ontologymessages.ReadClassInfoV2 import org.knora.webapi.messages.v2.responder.ontologymessages.ReadOntologyV2 import org.knora.webapi.slice.admin.domain.model.KnoraProject +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.common.repo.service.Repository import org.knora.webapi.slice.resourceinfo.domain.InternalIri @@ -22,9 +23,8 @@ trait OntologyRepo extends Repository[ReadOntologyV2, InternalIri] { override def findAll(): Task[List[ReadOntologyV2]] def findByProject(project: KnoraProject): Task[List[ReadOntologyV2]] = findByProject(project.id) - def findByProject(projectId: InternalIri): Task[List[ReadOntologyV2]] - def findOntologyGraphsByProject(project: KnoraProject): Task[List[InternalIri]] = - findByProject(project).map(_.map(_.ontologyMetadata.ontologyIri.toInternalIri)) + + def findByProject(projectId: ProjectIri): Task[List[ReadOntologyV2]] def findClassBy(classIri: InternalIri): Task[Option[ReadClassInfoV2]] diff --git a/webapi/src/main/scala/org/knora/webapi/slice/ontology/repo/service/OntologyRepoLive.scala b/webapi/src/main/scala/org/knora/webapi/slice/ontology/repo/service/OntologyRepoLive.scala index 10dbb503b1..80034735ef 100644 --- a/webapi/src/main/scala/org/knora/webapi/slice/ontology/repo/service/OntologyRepoLive.scala +++ b/webapi/src/main/scala/org/knora/webapi/slice/ontology/repo/service/OntologyRepoLive.scala @@ -14,6 +14,7 @@ import scala.annotation.tailrec import org.knora.webapi.messages.SmartIri import org.knora.webapi.messages.v2.responder.ontologymessages.ReadClassInfoV2 import org.knora.webapi.messages.v2.responder.ontologymessages.ReadOntologyV2 +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.ontology.domain.service.OntologyRepo import org.knora.webapi.slice.ontology.repo.model.OntologyCacheData import org.knora.webapi.slice.resourceinfo.domain.InternalIri @@ -45,8 +46,8 @@ final case class OntologyRepoLive(private val converter: IriConverter, private v override def findAll(): Task[List[ReadOntologyV2]] = getCache.map(_.ontologies.values.toList) - override def findByProject(projectId: InternalIri): Task[List[ReadOntologyV2]] = - smartIriMapCache(projectId)(findByProject) + override def findByProject(projectId: ProjectIri): Task[List[ReadOntologyV2]] = + smartIriMapCache(InternalIri(projectId.value))(findByProject) private def findByProject(projectIri: SmartIri, cache: OntologyCacheData): List[ReadOntologyV2] = cache.ontologies.values.filter(_.ontologyMetadata.projectIri.contains(projectIri)).toList diff --git a/webapi/src/main/scala/org/knora/webapi/store/cache/impl/CacheServiceInMemImpl.scala b/webapi/src/main/scala/org/knora/webapi/store/cache/impl/CacheServiceInMemImpl.scala index 6ecd1b817c..edc8cb5256 100644 --- a/webapi/src/main/scala/org/knora/webapi/store/cache/impl/CacheServiceInMemImpl.scala +++ b/webapi/src/main/scala/org/knora/webapi/store/cache/impl/CacheServiceInMemImpl.scala @@ -8,7 +8,6 @@ package org.knora.webapi.store.cache.impl import zio.* import zio.stm.* -import dsp.valueobjects.Iri import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectADM import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.* @@ -18,6 +17,7 @@ import org.knora.webapi.messages.admin.responder.usersmessages.UserIdentifierTyp import org.knora.webapi.messages.store.cacheservicemessages.CacheServiceStatusOK import org.knora.webapi.messages.store.cacheservicemessages.CacheServiceStatusResponse import org.knora.webapi.slice.admin.domain.model.KnoraProject +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.store.cache.api.CacheService import org.knora.webapi.store.cache.api.EmptyKey import org.knora.webapi.store.cache.api.EmptyValue @@ -133,7 +133,7 @@ case class CacheServiceInMemImpl( * @param iri the project's IRI * @return an optional [[ProjectADM]]. */ - def getProjectByIri(iri: Iri.ProjectIri) = projects.get(iri.value).commit + def getProjectByIri(iri: ProjectIri) = projects.get(iri.value).commit /** * Retrieves the project by the SHORTNAME. diff --git a/webapi/src/test/scala/dsp/valueobjects/IriSpec.scala b/webapi/src/test/scala/dsp/valueobjects/IriSpec.scala index 27f7007304..a574e87c4a 100644 --- a/webapi/src/test/scala/dsp/valueobjects/IriSpec.scala +++ b/webapi/src/test/scala/dsp/valueobjects/IriSpec.scala @@ -43,7 +43,7 @@ object IriSpec extends ZIOSpecDefault { val uuidVersion3 = fromIri(userIriWithUUIDVersion3) val supportedUuid = fromIri(validUserIri) - def spec: Spec[Any, Throwable] = groupIriTest + listIriTest + projectIriTest + uuidTest + roleIriTest + userIriTest + def spec: Spec[Any, Throwable] = groupIriTest + listIriTest + uuidTest + roleIriTest + userIriTest private val groupIriTest = suite("IriSpec - GroupIri")( test("pass an empty value and return an error") { @@ -133,59 +133,6 @@ object IriSpec extends ZIOSpecDefault { } ) - private val projectIriTest = suite("IriSpec - ProjectIri")( - test("pass an empty value and return an error") { - assertTrue( - ProjectIri.make("") == Validation.fail(ValidationException(IriErrorMessages.ProjectIriMissing)), - ProjectIri.make(Some("")) == Validation.fail(ValidationException(IriErrorMessages.ProjectIriMissing)) - ) - }, - test("pass an invalid value and return an error") { - assertTrue( - ProjectIri.make(invalidIri) == Validation.fail(ValidationException(IriErrorMessages.ProjectIriInvalid)), - ProjectIri.make(Some(invalidIri)) == Validation.fail(ValidationException(IriErrorMessages.ProjectIriInvalid)) - ) - }, - test("pass an invalid IRI containing unsupported UUID version and return an error") { - assertTrue( - ProjectIri.make(projectIriWithUUIDVersion3) == Validation.fail( - ValidationException(IriErrorMessages.UuidVersionInvalid) - ), - ProjectIri.make(Some(projectIriWithUUIDVersion3)) == Validation.fail( - ValidationException(IriErrorMessages.UuidVersionInvalid) - ) - ) - }, - test("pass an invalid IRI containing the shortcode and return an error") { - assertTrue( - ProjectIri.make(invalidIri) == Validation.fail(ValidationException(IriErrorMessages.ProjectIriInvalid)) - ) - }, - test("pass a valid project IRI and successfully create value object") { - def makeProjectIri(iri: String) = ProjectIri.make(iri) - val maybeProjectIri = ProjectIri.make(Some(validProjectIri)) - - (for { - iri <- makeProjectIri(validProjectIri) - iri2 <- makeProjectIri(systemProject) - iri3 <- makeProjectIri(defaultSharedOntologiesProject) - beolIri <- makeProjectIri(beolProjectIri) - maybeIri <- maybeProjectIri - } yield assertTrue( - iri.value == validProjectIri, - iri2.value == systemProject, - iri3.value == defaultSharedOntologiesProject, - beolIri.value == beolProjectIri, - maybeIri.get == iri - )).toZIO - }, - test("successfully validate passing None") { - assertTrue( - ProjectIri.make(None) == Validation.succeed(None) - ) - } - ) - private val uuidTest = suite("IriSpec - Base64Uuid")( test("pass an empty value and return an error") { assertTrue(Base64Uuid.make("") == Validation.fail(ValidationException(IriErrorMessages.UuidMissing))) diff --git a/webapi/src/test/scala/org/knora/webapi/TestDataFactory.scala b/webapi/src/test/scala/org/knora/webapi/TestDataFactory.scala index cd6324c20c..8affb403eb 100644 --- a/webapi/src/test/scala/org/knora/webapi/TestDataFactory.scala +++ b/webapi/src/test/scala/org/knora/webapi/TestDataFactory.scala @@ -7,12 +7,10 @@ package org.knora.webapi import zio.NonEmptyChunk -import dsp.valueobjects.Iri.* import dsp.valueobjects.V2 import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.* import org.knora.webapi.slice.admin.domain.model.KnoraProject import org.knora.webapi.slice.admin.domain.model.KnoraProject.* -import org.knora.webapi.slice.resourceinfo.domain.InternalIri /** * Helps in creating value objects for tests. @@ -20,7 +18,7 @@ import org.knora.webapi.slice.resourceinfo.domain.InternalIri object TestDataFactory { val someProject = KnoraProject( - InternalIri("http://rdfh.ch/projects/0001"), + ProjectIri.unsafeFrom("http://rdfh.ch/projects/0001"), Shortname.unsafeFrom("shortname"), Shortcode.unsafeFrom("0001"), None, @@ -49,6 +47,6 @@ object TestDataFactory { def projectIri(iri: String): ProjectIri = ProjectIri - .make(iri) + .from(iri) .getOrElse(throw new IllegalArgumentException(s"Invalid ProjectIri $iri.")) } diff --git a/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectADMRestServiceMock.scala b/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectADMRestServiceMock.scala index a00d2d5133..5d88892f11 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectADMRestServiceMock.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectADMRestServiceMock.scala @@ -21,6 +21,7 @@ import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequests.ProjectC import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequests.ProjectSetRestrictedViewSizeRequest import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequests.ProjectUpdateRequest import org.knora.webapi.slice.admin.api.service.ProjectADMRestService +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri object ProjectADMRestServiceMock extends Mock[ProjectADMRestService] { object GetProjects extends Effect[Unit, Throwable, ProjectsGetResponseADM] diff --git a/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsResponderADMMock.scala b/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsResponderADMMock.scala index 4538b3b873..eebc6bbe77 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsResponderADMMock.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsResponderADMMock.scala @@ -15,11 +15,11 @@ import zio.mock.Proxy import java.util.UUID -import dsp.valueobjects.Iri import org.knora.webapi.messages.admin.responder.projectsmessages.* import org.knora.webapi.messages.admin.responder.usersmessages.UserADM import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequests.ProjectCreateRequest import org.knora.webapi.slice.admin.api.model.ProjectsEndpointsRequests.ProjectUpdateRequest +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri object ProjectsResponderADMMock extends Mock[ProjectsResponderADM] { @@ -31,7 +31,7 @@ object ProjectsResponderADMMock extends Mock[ProjectsResponderADM] { object ProjectAdminMembersGetRequestADM extends Effect[(ProjectIdentifierADM, UserADM), Throwable, ProjectAdminMembersGetResponseADM] object ProjectsKeywordsGetRequestADM extends Effect[Unit, Throwable, ProjectsKeywordsGetResponseADM] - object ProjectKeywordsGetRequestADM extends Effect[Iri.ProjectIri, Throwable, ProjectKeywordsGetResponseADM] + object ProjectKeywordsGetRequestADM extends Effect[ProjectIri, Throwable, ProjectKeywordsGetResponseADM] object ProjectRestrictedViewSettingsGetADM extends Effect[ProjectIdentifierADM, Throwable, Option[ProjectRestrictedViewSettingsADM]] object ProjectRestrictedViewSettingsGetRequestADM @@ -39,7 +39,7 @@ object ProjectsResponderADMMock extends Mock[ProjectsResponderADM] { object ProjectCreateRequestADM extends Effect[(ProjectCreateRequest, UserADM, UUID), Throwable, ProjectOperationResponseADM] object ChangeBasicInformationRequestADM - extends Effect[(Iri.ProjectIri, ProjectUpdateRequest, UserADM, UUID), Throwable, ProjectOperationResponseADM] + extends Effect[(ProjectIri, ProjectUpdateRequest, UserADM, UUID), Throwable, ProjectOperationResponseADM] val compose: URLayer[mock.Proxy, ProjectsResponderADM] = ZLayer { @@ -64,7 +64,7 @@ object ProjectsResponderADMMock extends Mock[ProjectsResponderADM] { proxy(ProjectAdminMembersGetRequestADM, (id, user)) override def projectsKeywordsGetRequestADM(): Task[ProjectsKeywordsGetResponseADM] = proxy(ProjectsKeywordsGetRequestADM, ()) - override def projectKeywordsGetRequestADM(projectIri: Iri.ProjectIri): Task[ProjectKeywordsGetResponseADM] = + override def projectKeywordsGetRequestADM(projectIri: ProjectIri): Task[ProjectKeywordsGetResponseADM] = proxy(ProjectKeywordsGetRequestADM, projectIri) override def projectRestrictedViewSettingsGetADM( id: ProjectIdentifierADM @@ -81,7 +81,7 @@ object ProjectsResponderADMMock extends Mock[ProjectsResponderADM] { ): Task[ProjectOperationResponseADM] = proxy(ProjectCreateRequestADM, (createReq, requestingUser, apiRequestID)) override def changeBasicInformationRequestADM( - projectIri: Iri.ProjectIri, + projectIri: ProjectIri, updateReq: ProjectUpdateRequest, user: UserADM, apiRequestID: UUID diff --git a/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsServiceLiveSpec.scala b/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsServiceLiveSpec.scala index 97869a305d..b2e775049a 100644 --- a/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsServiceLiveSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/responders/admin/ProjectsServiceLiveSpec.scala @@ -147,7 +147,7 @@ object ProjectsServiceLiveSpec extends ZIOSpecDefault { // needs to have the StringFormatter in the environment because the [[ChangeProjectApiRequestADM]] needs it val deleteProjectSpec: Spec[StringFormatter, Throwable] = test("delete a project") { val iri = "http://rdfh.ch/projects/0001" - val projectIri = TestDataFactory.projectIri(iri) + val projectIri = ProjectIri.unsafeFrom(iri) val projectStatus = Some(Status.Inactive) val projectUpdatePayload = ProjectUpdateRequest(status = projectStatus) for { @@ -165,7 +165,7 @@ object ProjectsServiceLiveSpec extends ZIOSpecDefault { val updateProjectSpec: Spec[Any, Throwable] = test("update a project") { val iri = "http://rdfh.ch/projects/0001" - val projectIri = TestDataFactory.projectIri(iri) + val projectIri = ProjectIri.unsafeFrom(iri) val projectUpdatePayload = ProjectUpdateRequest( Some(Shortname.unsafeFrom("usn")), Some(Longname.unsafeFrom("updated project longname")), @@ -285,7 +285,7 @@ object ProjectsServiceLiveSpec extends ZIOSpecDefault { val getKeywordsByProjectIri: Spec[Any, Throwable] = test("get keywords of a single project by project IRI") { val iri = "http://rdfh.ch/projects/0001" - val projectIri = TestDataFactory.projectIri(iri) + val projectIri = ProjectIri.unsafeFrom(iri) val mockResponder = ProjectsResponderADMMock.ProjectKeywordsGetRequestADM( assertion = Assertion.equalTo(projectIri), result = Expectation.value(ProjectKeywordsGetResponseADM(Seq.empty[String])) diff --git a/webapi/src/test/scala/dsp/valueobjects/KnoraProjectSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/model/KnoraProjectSpec.scala similarity index 78% rename from webapi/src/test/scala/dsp/valueobjects/KnoraProjectSpec.scala rename to webapi/src/test/scala/org/knora/webapi/slice/admin/domain/model/KnoraProjectSpec.scala index bba38795f9..127fa825cb 100644 --- a/webapi/src/test/scala/dsp/valueobjects/KnoraProjectSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/model/KnoraProjectSpec.scala @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package dsp.valueobjects +package org.knora.webapi.slice.admin.domain.model import zio.Scope import zio.prelude.Validation @@ -12,13 +12,16 @@ import zio.test.* import scala.util.Random import dsp.errors.ValidationException +import dsp.valueobjects.V2 import org.knora.webapi.slice.admin.domain.model.KnoraProject.* /** - * This spec is used to test the [[KnoraProject]] value objects creation. + * This spec is used to test the [[org.knora.webapi.slice.admin.domain.model.KnoraProject]] value objects creation. */ object KnoraProjectSpec extends ZIOSpecDefault { - def spec: Spec[TestEnvironment & Scope, Nothing] = suite("ProjectSpec")( + + def spec: Spec[TestEnvironment & Scope, Nothing] = suite("KnoraProjectSpec")( + projectIriSuite, shortcodeTest, shortnameTest, longnameTest, @@ -29,6 +32,39 @@ object KnoraProjectSpec extends ZIOSpecDefault { projectSelfJoinTest ) + private val projectIriSuite = suite("ProjectIri")( + test("pass an empty value and return an error") { + assertTrue( + ProjectIri.from("") == Validation.fail(ValidationException("Project IRI cannot be empty.")) + ) + }, + test("pass an invalid value and return an error") { + assertTrue( + ProjectIri.from("not an iri") == Validation.fail(ValidationException("Project IRI is invalid.")) + ) + }, + test("pass an invalid IRI containing unsupported UUID version and return an error") { + val projectIriWithUUIDVersion3 = "http://rdfh.ch/projects/tZjZhGSZMeCLA5VeUmwAmg" + assertTrue( + ProjectIri.from(projectIriWithUUIDVersion3) == Validation.fail( + ValidationException("Invalid UUID used to create IRI. Only versions 4 and 5 are supported.") + ) + ) + }, + test("pass a valid project IRI and successfully create value object") { + val validIris = + Gen.fromIterable( + Seq( + "http://rdfh.ch/projects/0001", + "http://rdfh.ch/projects/CwQ8hXF9Qlm1gl2QE6pTpg", + "http://www.knora.org/ontology/knora-admin#SystemProject", + "http://www.knora.org/ontology/knora-admin#DefaultSharedOntologiesProject" + ) + ) + check(validIris)(iri => assertTrue(ProjectIri.unsafeFrom(iri).value == iri)) + } + ) + private val shortcodeTest = suite("Shortcode")( test("pass an empty value and return an error") { assertTrue( diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/repo/KnoraProjectRepoInMemory.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/repo/KnoraProjectRepoInMemory.scala index 1670baf4d6..de96f0777d 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/repo/KnoraProjectRepoInMemory.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/repo/KnoraProjectRepoInMemory.scala @@ -16,12 +16,12 @@ import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentif import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.ShortcodeIdentifier import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM.ShortnameIdentifier import org.knora.webapi.slice.admin.domain.model.KnoraProject +import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri import org.knora.webapi.slice.admin.domain.service.KnoraProjectRepo import org.knora.webapi.slice.common.repo.AbstractInMemoryCrudRepository -import org.knora.webapi.slice.resourceinfo.domain.InternalIri final case class KnoraProjectRepoInMemory(projects: Ref[List[KnoraProject]]) - extends AbstractInMemoryCrudRepository[KnoraProject, InternalIri](projects, _.id) + extends AbstractInMemoryCrudRepository[KnoraProject, ProjectIri](projects, _.id) with KnoraProjectRepo { override def findById(id: ProjectIdentifierADM): Task[Option[KnoraProject]] = projects.get.map( diff --git a/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/ProjectADMServiceSpec.scala b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/ProjectADMServiceSpec.scala index c399912f4d..f861329975 100644 --- a/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/ProjectADMServiceSpec.scala +++ b/webapi/src/test/scala/org/knora/webapi/slice/admin/domain/service/ProjectADMServiceSpec.scala @@ -14,7 +14,6 @@ import dsp.valueobjects.V2.StringLiteralV2 import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectADM import org.knora.webapi.slice.admin.domain.model.KnoraProject import org.knora.webapi.slice.admin.domain.model.KnoraProject.* -import org.knora.webapi.slice.resourceinfo.domain.InternalIri import org.knora.webapi.slice.resourceinfo.domain.IriTestConstants object ProjectADMServiceSpec extends ZIOSpecDefault { @@ -44,7 +43,7 @@ object ProjectADMServiceSpec extends ZIOSpecDefault { val shortcode = "0002" val shortname = "someOtherProject" val p: KnoraProject = KnoraProject( - id = InternalIri(IriTestConstants.Project.TestProject), + id = ProjectIri.unsafeFrom(IriTestConstants.Project.TestProject), shortname = Shortname.unsafeFrom(shortname), shortcode = Shortcode.unsafeFrom(shortcode), longname = None,