Skip to content

Commit

Permalink
refactor: Refactor Group value objects (#3058)
Browse files Browse the repository at this point in the history
  • Loading branch information
mpro7 committed Feb 23, 2024
1 parent ddf97df commit f7ab488
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 152 deletions.
Expand Up @@ -56,20 +56,19 @@ class GroupsResponderADMSpec extends CoreSpec {
appActor ! GroupCreateRequestADM(
createRequest = GroupCreatePayloadADM(
id = None,
name = GroupName.make("NewGroup").fold(e => throw e.head, v => v),
name = GroupName.unsafeFrom("NewGroup"),
descriptions = GroupDescriptions
.make(
.unsafeFrom(
Seq(
V2.StringLiteralV2(
value = """NewGroupDescription with "quotes" and <html tag>""",
language = Some("en")
)
)
)
.fold(e => throw e.head, v => v),
),
project = ProjectIri.unsafeFrom(imagesProjectIri),
status = GroupStatus.active,
selfjoin = GroupSelfJoin.make(false).fold(e => throw e.head, v => v)
selfjoin = GroupSelfJoin.impossible
),
requestingUser = imagesUser01,
apiRequestID = UUID.randomUUID
Expand All @@ -94,13 +93,12 @@ class GroupsResponderADMSpec extends CoreSpec {
appActor ! GroupCreateRequestADM(
createRequest = GroupCreatePayloadADM(
id = Some(GroupIri.unsafeFrom(imagesReviewerGroup.id)),
name = GroupName.make("NewGroup").fold(e => throw e.head, v => v),
name = GroupName.unsafeFrom("NewGroup"),
descriptions = GroupDescriptions
.make(Seq(V2.StringLiteralV2(value = "NewGroupDescription", language = Some("en"))))
.fold(e => throw e.head, v => v),
.unsafeFrom(Seq(V2.StringLiteralV2(value = "NewGroupDescription", language = Some("en")))),
project = ProjectIri.unsafeFrom(imagesProjectIri),
status = GroupStatus.active,
selfjoin = GroupSelfJoin.make(false).fold(e => throw e.head, v => v)
selfjoin = GroupSelfJoin.impossible
),
requestingUser = imagesUser01,
apiRequestID = UUID.randomUUID
Expand All @@ -115,13 +113,12 @@ class GroupsResponderADMSpec extends CoreSpec {
appActor ! GroupChangeRequestADM(
groupIri = newGroupIri.get,
changeGroupRequest = GroupUpdatePayloadADM(
Some(GroupName.make("UpdatedGroupName").fold(e => throw e.head, v => v)),
Some(GroupName.unsafeFrom("UpdatedGroupName")),
Some(
GroupDescriptions
.make(
.unsafeFrom(
Seq(V2.StringLiteralV2(value = """UpdatedDescription with "quotes" and <html tag>""", Some("en")))
)
.fold(e => throw e.head, v => v)
)
),
requestingUser = imagesUser01,
Expand All @@ -144,11 +141,10 @@ class GroupsResponderADMSpec extends CoreSpec {
appActor ! GroupChangeRequestADM(
groupIri = "http://rdfh.ch/groups/notexisting",
GroupUpdatePayloadADM(
Some(GroupName.make("UpdatedGroupName").fold(e => throw e.head, v => v)),
Some(GroupName.unsafeFrom("UpdatedGroupName")),
Some(
GroupDescriptions
.make(Seq(V2.StringLiteralV2(value = "UpdatedDescription", language = Some("en"))))
.fold(e => throw e.head, v => v)
.unsafeFrom(Seq(V2.StringLiteralV2(value = "UpdatedDescription", language = Some("en"))))
)
),
requestingUser = imagesUser01,
Expand All @@ -164,11 +160,10 @@ class GroupsResponderADMSpec extends CoreSpec {
appActor ! GroupChangeRequestADM(
groupIri = newGroupIri.get,
changeGroupRequest = GroupUpdatePayloadADM(
Some(GroupName.make("Image reviewer").fold(e => throw e.head, v => v)),
Some(GroupName.unsafeFrom("Image reviewer")),
Some(
GroupDescriptions
.make(Seq(V2.StringLiteralV2(value = "UpdatedDescription", language = Some("en"))))
.fold(e => throw e.head, v => v)
.unsafeFrom(Seq(V2.StringLiteralV2(value = "UpdatedDescription", language = Some("en"))))
)
),
requestingUser = imagesUser01,
Expand Down
90 changes: 35 additions & 55 deletions webapi/src/main/scala/dsp/valueobjects/Group.scala
Expand Up @@ -5,78 +5,58 @@

package dsp.valueobjects

import zio.prelude.Validation

import dsp.errors.BadRequestException
import org.knora.webapi.slice.common.StringValueCompanion
import org.knora.webapi.slice.common.Value
import org.knora.webapi.slice.common.Value.BooleanValue
import org.knora.webapi.slice.common.Value.StringValue
import org.knora.webapi.slice.common.WithFrom

object Group {

/**
* GroupName value object.
*/
sealed abstract case class GroupName private (value: String)
object GroupName { self =>
def make(value: String): Validation[BadRequestException, GroupName] =
if (value.isEmpty) Validation.fail(BadRequestException(GroupErrorMessages.GroupNameMissing))
else
Validation
.fromOption(Iri.toSparqlEncodedString(value))
.mapError(_ => BadRequestException(GroupErrorMessages.GroupNameInvalid))
.map(new GroupName(_) {})

def make(value: Option[String]): Validation[Throwable, Option[GroupName]] =
final case class GroupName private (value: String) extends AnyVal with StringValue
object GroupName extends StringValueCompanion[GroupName] {
def from(value: String): Either[String, GroupName] =
value match {
case Some(v) => self.make(v).map(Some(_))
case None => Validation.succeed(None)
case _ if value.isEmpty => Left(GroupErrorMessages.GroupNameMissing)
case _ =>
Iri
.toSparqlEncodedString(value)
.toRight(GroupErrorMessages.GroupNameInvalid)
.map(GroupName.apply)
}
}

/**
* GroupDescriptions value object.
*/
sealed abstract case class GroupDescriptions private (value: Seq[V2.StringLiteralV2])
object GroupDescriptions { self =>
def make(value: Seq[V2.StringLiteralV2]): Validation[BadRequestException, GroupDescriptions] =
if (value.isEmpty) Validation.fail(BadRequestException(GroupErrorMessages.GroupDescriptionsMissing))
else {
val validatedDescriptions = value.map(d =>
Validation
.fromOption(Iri.toSparqlEncodedString(d.value))
.mapError(_ => BadRequestException(GroupErrorMessages.GroupDescriptionsInvalid))
.map(s => V2.StringLiteralV2(s, d.language))
)
Validation.validateAll(validatedDescriptions).map(new GroupDescriptions(_) {})
}

def make(value: Option[Seq[V2.StringLiteralV2]]): Validation[BadRequestException, Option[GroupDescriptions]] =
value match {
case Some(v) => self.make(v).map(Some(_))
case None => Validation.succeed(None)
}
final case class GroupDescriptions private (override val value: Seq[V2.StringLiteralV2])
extends AnyVal
with Value[Seq[V2.StringLiteralV2]]
object GroupDescriptions extends WithFrom[Seq[V2.StringLiteralV2], GroupDescriptions] {
def from(value: Seq[V2.StringLiteralV2]): Either[String, GroupDescriptions] =
if (value.isEmpty) Left(GroupErrorMessages.GroupDescriptionsMissing)
else
Iri
.toSparqlEncodedString(value.head.value)
.toRight(GroupErrorMessages.GroupDescriptionsInvalid)
.map(_ => GroupDescriptions(value))
}

/**
* GroupStatus value object.
*/
sealed abstract case class GroupStatus private (value: Boolean)
object GroupStatus { self =>
val active: GroupStatus = new GroupStatus(true) {}
val inactive: GroupStatus = new GroupStatus(false) {}
def make(value: Boolean): GroupStatus = if (value) active else inactive
final case class GroupStatus private (value: Boolean) extends AnyVal with BooleanValue
object GroupStatus {
val active: GroupStatus = new GroupStatus(true)
val inactive: GroupStatus = new GroupStatus(false)
def from(value: Boolean): GroupStatus = if (value) active else inactive
}

/**
* GroupSelfJoin value object.
*/
sealed abstract case class GroupSelfJoin private (value: Boolean)
object GroupSelfJoin { self =>
def make(value: Boolean): Validation[Throwable, GroupSelfJoin] =
Validation.succeed(new GroupSelfJoin(value) {})
def make(value: Option[Boolean]): Validation[Throwable, Option[GroupSelfJoin]] =
value match {
case Some(v) => self.make(v).map(Some(_))
case None => Validation.succeed(None)
}
final case class GroupSelfJoin private (value: Boolean) extends AnyVal with BooleanValue
object GroupSelfJoin {
val possible: GroupSelfJoin = new GroupSelfJoin(true)
val impossible: GroupSelfJoin = new GroupSelfJoin(false)
def from(value: Boolean): GroupSelfJoin = if (value) possible else impossible
}
}

Expand Down
Expand Up @@ -482,7 +482,7 @@ final case class GroupsResponderADMLive(
!userPermissions.isSystemAdmin
}

maybeStatus = changeGroupRequest.status.map(GroupStatus.make)
maybeStatus = changeGroupRequest.status.map(GroupStatus.from)

/* create the update request */
groupUpdatePayload = GroupUpdatePayloadADM(status = maybeStatus)
Expand Down
Expand Up @@ -25,6 +25,7 @@ import org.knora.webapi.routing.RouteUtilADM.*
import org.knora.webapi.routing.RouteUtilZ
import org.knora.webapi.slice.admin.domain.model.GroupIri
import org.knora.webapi.slice.admin.domain.model.KnoraProject.ProjectIri
import org.knora.webapi.slice.common.ToValidation.validateOptionWithFrom

/**
* Provides a routing function for API routes that deal with groups.
Expand Down Expand Up @@ -53,13 +54,15 @@ final case class GroupsRouteADM(
val id: Validation[Throwable, Option[GroupIri]] = apiRequest.id
.map(id => Validation.fromEither(GroupIri.from(id).map(Some(_))).mapError(BadRequestException(_)))
.getOrElse(Validation.succeed(None))
val name: Validation[Throwable, GroupName] = GroupName.make(apiRequest.name)
val descriptions: Validation[Throwable, GroupDescriptions] = GroupDescriptions.make(apiRequest.descriptions)
val name: Validation[Throwable, GroupName] =
Validation.fromEither(GroupName.from(apiRequest.name)).mapError(ValidationException.apply)
val descriptions: Validation[Throwable, GroupDescriptions] =
Validation.fromEither(GroupDescriptions.from(apiRequest.descriptions)).mapError(ValidationException.apply)
val project: Validation[Throwable, ProjectIri] = Validation
.fromEither(ProjectIri.from(apiRequest.project))
.mapError(ValidationException.apply)
val status: Validation[Throwable, GroupStatus] = Validation.succeed(GroupStatus.make(apiRequest.status))
val selfjoin: Validation[Throwable, GroupSelfJoin] = GroupSelfJoin.make(apiRequest.selfjoin)
val status: Validation[Throwable, GroupStatus] = Validation.succeed(GroupStatus.from(apiRequest.status))
val selfjoin: Validation[Throwable, GroupSelfJoin] = Validation.succeed(GroupSelfJoin.from(apiRequest.selfjoin))
val payloadValidation: Validation[Throwable, GroupCreatePayloadADM] =
Validation.validateWith(id, name, descriptions, project, status, selfjoin)(GroupCreatePayloadADM)

Expand Down Expand Up @@ -87,10 +90,11 @@ final case class GroupsRouteADM(
)
)
.when(apiRequest.status.nonEmpty)
name = GroupName.make(apiRequest.name)
descriptions = GroupDescriptions.make(apiRequest.descriptions)
status = Validation.succeed(apiRequest.status.map(GroupStatus.make))
selfjoin = GroupSelfJoin.make(apiRequest.selfjoin)
name = validateOptionWithFrom(apiRequest.name, GroupName.from, BadRequestException.apply)
descriptions =
validateOptionWithFrom(apiRequest.descriptions, GroupDescriptions.from, BadRequestException.apply)
status = Validation.succeed(apiRequest.status.map(GroupStatus.from))
selfjoin = Validation.succeed(apiRequest.selfjoin.map(GroupSelfJoin.from))
validatedPayload = Validation.validateWith(name, descriptions, status, selfjoin)(GroupUpdatePayloadADM)
iri <- Iri
.validateAndEscapeIri(value)
Expand Down
93 changes: 23 additions & 70 deletions webapi/src/test/scala/dsp/valueobjects/GroupSpec.scala
Expand Up @@ -5,107 +5,60 @@

package dsp.valueobjects

import zio.prelude.Validation
import zio.test.*

import dsp.errors.BadRequestException
import dsp.valueobjects.Group.*

/**
* This spec is used to test the [[Group]] value objects creation.
*/
object GroupSpec extends ZIOSpecDefault {

private val validName = "Valid group name"
private val invalidName = "Invalid group name\r"
private val validDescription = Seq(V2.StringLiteralV2(value = "Valid group description", language = Some("en")))
private val invalidDescription = Seq(
V2.StringLiteralV2(value = "Invalid group description \r", language = Some("en"))
)

def spec: Spec[Any, Any] = groupNameTest + groupDescriptionsTest + groupStatusTest + groupSelfJoinTest

private val groupNameTest = suite("GroupSpec - GroupName")(
test("pass an empty value and return an error") {
assertTrue(
GroupName.make("") == Validation.fail(BadRequestException(GroupErrorMessages.GroupNameMissing)),
GroupName.make(Some("")) == Validation.fail(BadRequestException(GroupErrorMessages.GroupNameMissing))
)
},
test("pass an invalid value and return an error") {
assertTrue(
GroupName.make(invalidName) == Validation.fail(BadRequestException(GroupErrorMessages.GroupNameInvalid)),
GroupName.make(Some(invalidName)) == Validation.fail(BadRequestException(GroupErrorMessages.GroupNameInvalid))
)
private val groupNameTest = suite("GroupName should")(
test("not be created from an empty value") {
assertTrue(GroupName.from("") == Left(GroupErrorMessages.GroupNameMissing))
},
test("pass a valid value and successfully create value object") {
assertTrue(
GroupName.make(validName).toOption.get.value == validName,
GroupName.make(Option(validName)).getOrElse(null).get.value == validName
)
test("not be created from an invalid value") {
assertTrue(GroupName.from("Invalid group name\r") == Left(GroupErrorMessages.GroupNameInvalid))
},
test("successfully validate passing None") {
assertTrue(
GroupName.make(None) == Validation.succeed(None)
)
test("be created from a valid value") {
assertTrue(GroupName.from("Valid group name").map(_.value) == Right("Valid group name"))
}
)

private val groupDescriptionsTest = suite("GroupSpec - GroupDescriptions")(
test("pass an empty object and return an error") {
assertTrue(
GroupDescriptions.make(Seq.empty) == Validation.fail(
BadRequestException(GroupErrorMessages.GroupDescriptionsMissing)
)
) &&
assertTrue(
GroupDescriptions.make(Some(Seq.empty)) == Validation.fail(
BadRequestException(GroupErrorMessages.GroupDescriptionsMissing)
)
)
},
test("pass an invalid object and return an error") {
assertTrue(
GroupDescriptions.make(invalidDescription) == Validation.fail(
BadRequestException(GroupErrorMessages.GroupDescriptionsInvalid)
)
) &&
assertTrue(
GroupDescriptions.make(Some(invalidDescription)) == Validation.fail(
BadRequestException(GroupErrorMessages.GroupDescriptionsInvalid)
)
)
private val groupDescriptionsTest = suite("GroupDescriptions should")(
test("not be created from an empty value") {
val emptyDescription = Seq.empty[V2.StringLiteralV2]
assertTrue(GroupDescriptions.from(emptyDescription) == Left(GroupErrorMessages.GroupDescriptionsMissing))
},
test("pass a valid object and successfully create value object") {
assertTrue(GroupDescriptions.make(validDescription).toOption.get.value == validDescription) &&
assertTrue(GroupDescriptions.make(Option(validDescription)).getOrElse(null).get.value == validDescription)
test("not be created from an invalid value") {
assertTrue(GroupDescriptions.from(invalidDescription) == Left(GroupErrorMessages.GroupDescriptionsInvalid))
},
test("successfully validate passing None") {
assertTrue(
GroupDescriptions.make(None) == Validation.succeed(None)
)
test("be created from a valid value") {
assertTrue(GroupDescriptions.from(validDescription).map(_.value) == Right(validDescription))
}
)

private val groupStatusTest = suite("GroupSpec - GroupStatus")(
test("creating a GroupStatus") {
private val groupStatusTest = suite("GroupStatus")(
test("should be created from a valid value") {
assertTrue(
!GroupStatus.inactive.value,
GroupStatus.active.value,
GroupStatus.make(true).value,
!GroupStatus.make(false).value
GroupStatus.from(true) == GroupStatus.active,
GroupStatus.from(false) == GroupStatus.inactive
)
}
)

private val groupSelfJoinTest = suite("GroupSpec - GroupSelfJoin")(
test("pass a valid object and successfully create value object") {
assertTrue(GroupSelfJoin.make(true).toOption.get.value == true) &&
assertTrue(GroupSelfJoin.make(Some(false)).getOrElse(null).get.value == false)
},
test("successfully validate passing None") {
private val groupSelfJoinTest = suite("GroupSelfJoin")(
test("should be created from a valid value") {
assertTrue(
GroupSelfJoin.make(None) == Validation.succeed(None)
GroupSelfJoin.from(true) == GroupSelfJoin.possible,
GroupSelfJoin.from(false) == GroupSelfJoin.impossible
)
}
)
Expand Down

0 comments on commit f7ab488

Please sign in to comment.