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
refactor: move value objects to separate project (DEV-615) #2069
Changes from 13 commits
0ca212a
aea668a
c844fa0
e23f1e5
d3f4761
a5c1265
afc3830
4586b24
47da536
00f4cbc
2cebe77
6ec5708
430caed
20b65cf
6bd4eb4
43f4f46
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
/* | ||
* Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package dsp.valueobjects | ||
|
||
import zio.prelude.Validation | ||
|
||
sealed trait Group | ||
object Group { | ||
|
||
/** | ||
* GroupName value object. | ||
*/ | ||
sealed abstract case class GroupName private (value: String) | ||
object GroupName { self => | ||
def make(value: String): Validation[Throwable, GroupName] = | ||
if (value.isEmpty) { | ||
Validation.fail(V2.BadRequestException(GroupErrorMessages.GroupNameMissing)) | ||
} else { | ||
val validatedValue = Validation( | ||
V2IriValidation.toSparqlEncodedString( | ||
value, | ||
throw V2.BadRequestException(GroupErrorMessages.GroupNameInvalid) | ||
) | ||
) | ||
|
||
validatedValue.map(new GroupName(_) {}) | ||
} | ||
|
||
def make(value: Option[String]): Validation[Throwable, Option[GroupName]] = | ||
value match { | ||
case Some(v) => self.make(v).map(Some(_)) | ||
case None => Validation.succeed(None) | ||
} | ||
} | ||
|
||
/** | ||
* GroupDescriptions value object. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure anymore if we discussed the naming of this already. But I think that "description" should be singular. There is only one description per group (and all other entities), just possibly in multiple languages. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is indeed a little bit misleading. Since value objects are going to be refactored in next step, this can be a subject of discussion. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agree, only one description. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fine, but technically it is still the Seq of values, which means that the way how the translations/internationalisation is implemented is wrong. Nothing stops user from adding more than one description in the same language. |
||
*/ | ||
sealed abstract case class GroupDescriptions private (value: Seq[V2.StringLiteralV2]) | ||
object GroupDescriptions { self => | ||
def make(value: Seq[V2.StringLiteralV2]): Validation[Throwable, GroupDescriptions] = | ||
if (value.isEmpty) { | ||
Validation.fail(V2.BadRequestException(GroupErrorMessages.GroupDescriptionMissing)) | ||
} else { | ||
val validatedDescriptions = Validation(value.map { description => | ||
val validatedDescription = | ||
V2IriValidation.toSparqlEncodedString( | ||
description.value, | ||
throw V2.BadRequestException(GroupErrorMessages.GroupDescriptionInvalid) | ||
) | ||
V2.StringLiteralV2(value = validatedDescription, language = description.language) | ||
}) | ||
validatedDescriptions.map(new GroupDescriptions(_) {}) | ||
} | ||
|
||
def make(value: Option[Seq[V2.StringLiteralV2]]): Validation[Throwable, Option[GroupDescriptions]] = | ||
value match { | ||
case Some(v) => self.make(v).map(Some(_)) | ||
case None => Validation.succeed(None) | ||
} | ||
} | ||
|
||
/** | ||
* GroupStatus value object. | ||
*/ | ||
sealed abstract case class GroupStatus private (value: Boolean) | ||
object GroupStatus { self => | ||
def make(value: Boolean): Validation[Throwable, GroupStatus] = | ||
Validation.succeed(new GroupStatus(value) {}) | ||
def make(value: Option[Boolean]): Validation[Throwable, Option[GroupStatus]] = | ||
value match { | ||
case Some(v) => self.make(v).map(Some(_)) | ||
case None => Validation.succeed(None) | ||
} | ||
} | ||
|
||
/** | ||
* 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) | ||
} | ||
} | ||
} | ||
|
||
object GroupErrorMessages { | ||
val GroupNameMissing = "Group name cannot be empty." | ||
val GroupNameInvalid = "Group name is invalid." | ||
val GroupDescriptionMissing = "Group description cannot be empty." // make it plural | ||
val GroupDescriptionInvalid = "Group description is invalid." // make it plural | ||
mpro7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
/* | ||
* Copyright © 2021 - 2022 Swiss National Data and Service Center for the Humanities and/or DaSCH Service Platform contributors. | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package dsp.valueobjects | ||
|
||
import zio.prelude.Validation | ||
|
||
sealed trait Iri | ||
object Iri { | ||
|
||
/** | ||
* GroupIri value object. | ||
*/ | ||
sealed abstract case class GroupIri private (value: String) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. all IRI case classes need to extend the |
||
object GroupIri { self => | ||
def make(value: String): Validation[Throwable, GroupIri] = | ||
if (value.isEmpty) { | ||
Validation.fail(V2.BadRequestException(IriErrorMessages.GroupIriMissing)) | ||
} else { | ||
val isUUID: Boolean = V2UuidValidation.hasUUIDLength(value.split("/").last) | ||
mpro7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (!V2IriValidation.isKnoraGroupIriStr(value)) { | ||
Validation.fail(V2.BadRequestException(IriErrorMessages.GroupIriInvalid)) | ||
} else if (isUUID && !V2UuidValidation.isUUIDVersion4Or5(value)) { | ||
Validation.fail(V2.BadRequestException(IriErrorMessages.UuidInvalid)) | ||
} else { | ||
val validatedValue = Validation( | ||
V2IriValidation.validateAndEscapeIri(value, throw V2.BadRequestException(IriErrorMessages.GroupIriInvalid)) | ||
) | ||
|
||
validatedValue.map(new GroupIri(_) {}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. eventually it would be nice to be able to simplify these validations. It has nested conditions to the point where it gets hard to read the code. But that's for the validation epic, not for this task. Also, I general question I have: these validation functions (like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the comment. Value objects refactor it's planned in next steps. |
||
} | ||
} | ||
|
||
def make(value: Option[String]): Validation[Throwable, Option[GroupIri]] = | ||
value match { | ||
case Some(v) => self.make(v).map(Some(_)) | ||
case None => Validation.succeed(None) | ||
} | ||
} | ||
|
||
/** | ||
* ListIri value object. | ||
*/ | ||
sealed abstract case class ListIri private (value: String) | ||
object ListIri { self => | ||
def make(value: String): Validation[Throwable, ListIri] = | ||
if (value.isEmpty) { | ||
Validation.fail(V2.BadRequestException(IriErrorMessages.ListIriMissing)) | ||
} else { | ||
val isUUID: Boolean = V2UuidValidation.hasUUIDLength(value.split("/").last) | ||
|
||
if (!V2IriValidation.isKnoraListIriStr(value)) { | ||
Validation.fail(V2.BadRequestException(IriErrorMessages.ListIriInvalid)) | ||
} else if (isUUID && !V2UuidValidation.isUUIDVersion4Or5(value)) { | ||
Validation.fail(V2.BadRequestException(IriErrorMessages.UuidInvalid)) | ||
} else { | ||
val validatedValue = Validation( | ||
V2IriValidation.validateAndEscapeIri( | ||
value, | ||
throw V2.BadRequestException(IriErrorMessages.ListIriInvalid) | ||
) | ||
) | ||
|
||
validatedValue.map(new ListIri(_) {}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. And again, these different IRI types share a lot of the same logic, so there probably should be a shared method for checking these things. (in a later PR :) ) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the comment. Value objects refactor it's planned in next steps. |
||
} | ||
} | ||
|
||
def make(value: Option[String]): Validation[Throwable, Option[ListIri]] = | ||
value match { | ||
case Some(v) => self.make(v).map(Some(_)) | ||
case None => Validation.succeed(None) | ||
} | ||
} | ||
|
||
/** | ||
* ProjectIri value object. | ||
*/ | ||
sealed abstract case class ProjectIri private (value: String) | ||
object ProjectIri { self => | ||
def make(value: String): Validation[Throwable, ProjectIri] = | ||
if (value.isEmpty) { | ||
Validation.fail(V2.BadRequestException(IriErrorMessages.ProjectIriMissing)) | ||
} else { | ||
val isUUID: Boolean = V2UuidValidation.hasUUIDLength(value.split("/").last) | ||
|
||
if (!V2IriValidation.isKnoraProjectIriStr(value)) { | ||
Validation.fail(V2.BadRequestException(IriErrorMessages.ProjectIriInvalid)) | ||
} else if (isUUID && !V2UuidValidation.isUUIDVersion4Or5(value)) { | ||
Validation.fail(V2.BadRequestException(IriErrorMessages.UuidInvalid)) | ||
} else { | ||
val validatedValue = Validation( | ||
V2IriValidation.validateAndEscapeProjectIri( | ||
value, | ||
throw V2.BadRequestException(IriErrorMessages.ProjectIriInvalid) | ||
) | ||
) | ||
|
||
validatedValue.map(new ProjectIri(_) {}) | ||
} | ||
} | ||
|
||
def make(value: Option[String]): Validation[Throwable, Option[ProjectIri]] = | ||
value match { | ||
case Some(v) => self.make(v).map(Some(_)) | ||
case None => Validation.succeed(None) | ||
} | ||
} | ||
|
||
/** | ||
* UserIri value object. | ||
*/ | ||
sealed abstract case class UserIri private (value: String) extends Iri | ||
object UserIri { self => | ||
def make(value: String): Validation[Throwable, UserIri] = | ||
if (value.isEmpty) { | ||
Validation.fail(V2.BadRequestException(IriErrorMessages.UserIriMissing)) | ||
} else { | ||
val isUUID: Boolean = V2UuidValidation.hasUUIDLength(value.split("/").last) | ||
|
||
if (!V2IriValidation.isKnoraUserIriStr(value)) { | ||
Validation.fail(V2.BadRequestException(IriErrorMessages.UserIriInvalid)) | ||
} else if (isUUID && !V2UuidValidation.isUUIDVersion4Or5(value)) { | ||
Validation.fail(V2.BadRequestException(IriErrorMessages.UuidInvalid)) | ||
} else { | ||
val validatedValue = Validation( | ||
V2IriValidation.validateAndEscapeUserIri( | ||
value, | ||
throw V2.BadRequestException(IriErrorMessages.UserIriInvalid) | ||
) | ||
) | ||
|
||
validatedValue.map(new UserIri(_) {}) | ||
} | ||
} | ||
|
||
def make(value: Option[String]): Validation[Throwable, Option[UserIri]] = | ||
value match { | ||
case Some(v) => self.make(v).map(Some(_)) | ||
case None => Validation.succeed(None) | ||
} | ||
} | ||
} | ||
|
||
object IriErrorMessages { | ||
val GroupIriMissing = "Group IRI cannot be empty." | ||
val GroupIriInvalid = "Group IRI is invalid." | ||
val ListIriMissing = "List IRI cannot be empty." | ||
val ListIriInvalid = "List IRI cannot be empty." | ||
mpro7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
val ProjectIriMissing = "Project IRI cannot be empty." | ||
val ProjectIriInvalid = "Project IRI is invalid." | ||
val UserIriMissing = "User IRI cannot be empty." | ||
val UserIriInvalid = "User IRI is invalid." | ||
val UuidInvalid = "Invalid UUID used to create IRI. Only versions 4 and 5 are supported." | ||
mpro7 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we don't need to define a trait for Group. We only discussed it in the context of IRI.