Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: Move ProjectIri to KnoraProject #2944

Merged
merged 11 commits into from
Nov 20, 2023
Expand Up @@ -5,7 +5,6 @@

package org.knora.webapi

import dsp.valueobjects.Iri._
import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM._

/**
Expand All @@ -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."))
}
Expand Up @@ -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'.
Expand Down Expand Up @@ -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"))))
Expand Down
Expand Up @@ -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
Expand Down Expand Up @@ -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)
),
Expand Down Expand Up @@ -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.from(SharedTestDataADM.imagesProjectIri).fold(e => throw e.head, v => v),
seakayone marked this conversation as resolved.
Show resolved Hide resolved
status = GroupStatus.active,
selfjoin = GroupSelfJoin.make(false).fold(e => throw e.head, v => v)
),
Expand Down
Expand Up @@ -25,6 +25,7 @@ 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
Expand Down Expand Up @@ -155,7 +156,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"))))
Expand Down Expand Up @@ -195,7 +196,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.from(imagesProjectIri).fold(e => throw e.head, v => v),
name = Some(ListName.make(nameWithSpecialCharacter).fold(e => throw e.head, v => v)),
labels = Labels
.make(Seq(V2.StringLiteralV2(value = labelWithSpecialCharacter, language = Some("de"))))
Expand Down Expand Up @@ -235,7 +236,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.from(imagesProjectIri).fold(e => throw e.head, v => v),
name = Some(ListName.make("updated name").fold(e => throw e.head, v => v)),
labels = Some(
Labels
Expand Down Expand Up @@ -289,7 +290,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.from(imagesProjectIri).fold(e => throw e.head, v => v)
appActor ! NodeInfoChangeRequestADM(
listIri = newListIri.get,
changeNodeRequest = ListNodeChangePayloadADM(
Expand All @@ -313,7 +314,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.from(imagesProjectIri).fold(e => throw e.head, v => v),
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"))))
Expand Down Expand Up @@ -366,7 +367,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.from(imagesProjectIri).fold(e => throw e.head, v => v),
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
Expand Down Expand Up @@ -420,7 +421,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.from(imagesProjectIri).fold(e => throw e.head, v => v),
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"))))
Expand Down Expand Up @@ -474,7 +475,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.from(imagesProjectIri).fold(e => throw e.head, v => v),
name = Some(ListName.make("fourth").fold(e => throw e.head, v => v)),
position = givenPosition,
labels = Labels
Expand Down Expand Up @@ -832,7 +833,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.")))

}

Expand All @@ -845,7 +846,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."))
)

}
Expand All @@ -858,7 +859,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."))
)

}
Expand Down
Expand Up @@ -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))
Expand Down Expand Up @@ -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")
Expand All @@ -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]
Expand Down Expand Up @@ -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(
Expand All @@ -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 {
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)),
Expand Down Expand Up @@ -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.")))
}
Expand Down
38 changes: 2 additions & 36 deletions webapi/src/main/scala/dsp/valueobjects/Iri.scala
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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.
Expand Down
Expand Up @@ -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
Expand Down
Expand Up @@ -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
Expand Down
Expand Up @@ -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

Expand Down Expand Up @@ -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 =
Expand Down
Expand Up @@ -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
Expand Down Expand Up @@ -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 =>
Expand Down
Expand Up @@ -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.
Expand Down