Skip to content

Commit

Permalink
refactor: Rename Shortcode everywhere and use its type in KnoraProjec…
Browse files Browse the repository at this point in the history
…t property (NO-TICKET) (#2724)
  • Loading branch information
seakayone committed Jul 3, 2023
1 parent 3893599 commit f01c319
Show file tree
Hide file tree
Showing 27 changed files with 173 additions and 181 deletions.
8 changes: 4 additions & 4 deletions webapi/src/it/scala/org/knora/webapi/ITTestDataFactory.scala
Expand Up @@ -29,10 +29,10 @@ object ITTestDataFactory {
.fromString(iri)
.getOrElse(throw new IllegalArgumentException(s"Invalid IriIdentifier $iri."))

def projectShortCode(shortCode: String): ShortCode =
ShortCode
.make(shortCode)
.getOrElse(throw new IllegalArgumentException(s"Invalid ShortCode $shortCode."))
def projectShortcode(shortcode: String): Shortcode =
Shortcode
.make(shortcode)
.getOrElse(throw new IllegalArgumentException(s"Invalid Shortcode $shortcode."))

def projectShortName(shortName: String): ShortName =
ShortName
Expand Down
Expand Up @@ -164,11 +164,11 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender {
val newProjectIri = new MutableTestIri

"CREATE a project and return the project info if the supplied shortname is unique" in {
val shortCode = "111c"
val shortcode = "111c"
appActor ! ProjectCreateRequestADM(
createRequest = ProjectCreatePayloadADM(
shortname = ShortName.make("newproject").fold(error => throw error.head, value => value),
shortcode = ShortCode.make(shortCode).fold(error => throw error.head, value => value), // lower case
shortcode = Shortcode.make(shortcode).fold(error => throw error.head, value => value), // lower case
longname = Name.make(Some("project longname")).fold(error => throw error.head, value => value),
description = ProjectDescription
.make(Seq(V2.StringLiteralV2(value = "project description", language = Some("en"))))
Expand All @@ -184,7 +184,7 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender {
val received: ProjectOperationResponseADM = expectMsgType[ProjectOperationResponseADM](timeout)

received.project.shortname should be("newproject")
received.project.shortcode should be(shortCode.toUpperCase) // upper case
received.project.shortcode should be(shortcode.toUpperCase) // upper case
received.project.longname should contain("project longname")
received.project.description should be(
Seq(V2.StringLiteralV2(value = "project description", language = Some("en")))
Expand Down Expand Up @@ -263,7 +263,7 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender {
appActor ! ProjectCreateRequestADM(
createRequest = ProjectCreatePayloadADM(
shortname = ShortName.make("newproject2").fold(error => throw error.head, value => value),
shortcode = ShortCode.make("1112").fold(error => throw error.head, value => value), // lower case
shortcode = Shortcode.make("1112").fold(error => throw error.head, value => value), // lower case
longname = Some(Name.make("project longname").fold(error => throw error.head, value => value)),
description = ProjectDescription
.make(Seq(V2.StringLiteralV2(value = "project description", language = Some("en"))))
Expand Down Expand Up @@ -295,7 +295,7 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender {
appActor ! ProjectCreateRequestADM(
createRequest = ProjectCreatePayloadADM(
shortname = ShortName.make("project_with_character").fold(error => throw error.head, value => value),
shortcode = ShortCode.make("1312").fold(error => throw error.head, value => value), // lower case
shortcode = Shortcode.make("1312").fold(error => throw error.head, value => value), // lower case
longname = Name.make(Some(longnameWithSpecialCharacter)).fold(error => throw error.head, value => value),
description = ProjectDescription
.make(Seq(V2.StringLiteralV2(value = descriptionWithSpecialCharacter, language = Some("en"))))
Expand Down Expand Up @@ -327,7 +327,7 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender {
appActor ! ProjectCreateRequestADM(
createRequest = ProjectCreatePayloadADM(
shortname = ShortName.make("newproject").fold(error => throw error.head, value => value),
shortcode = ShortCode.make("111C").fold(error => throw error.head, value => value), // lower case
shortcode = Shortcode.make("111C").fold(error => throw error.head, value => value), // lower case
longname = Name.make(Some("project longname")).fold(error => throw error.head, value => value),
description = ProjectDescription
.make(Seq(V2.StringLiteralV2(value = "project description", language = Some("en"))))
Expand All @@ -347,7 +347,7 @@ class ProjectsResponderADMSpec extends CoreSpec with ImplicitSender {
appActor ! ProjectCreateRequestADM(
createRequest = ProjectCreatePayloadADM(
shortname = ShortName.make("newproject3").fold(error => throw error.head, value => value),
shortcode = ShortCode.make("111C").fold(error => throw error.head, value => value), // lower case
shortcode = Shortcode.make("111C").fold(error => throw error.head, value => value), // lower case
longname = Name.make(Some("project longname")).fold(error => throw error.head, value => value),
description = ProjectDescription
.make(Seq(V2.StringLiteralV2(value = "project description", language = Some("en"))))
Expand Down
Expand Up @@ -6,6 +6,7 @@
package org.knora.webapi.slice.admin.domain.service

import dsp.valueobjects.Project
import dsp.valueobjects.Project.Shortcode
import org.knora.webapi.config.{Fuseki, Triplestore}
import org.knora.webapi.testcontainers.FusekiTestContainer
import zio._
Expand All @@ -26,9 +27,9 @@ object ProjectImportServiceIT extends ZIOSpecDefault {

private val dspIngestClientLayer: ULayer[DspIngestClient] = ZLayer.succeed {
new DspIngestClient {
override def exportProject(shortCode: Project.ShortCode): ZIO[Scope, Throwable, Path] =
override def exportProject(shortcode: Shortcode): ZIO[Scope, Throwable, Path] =
ZIO.succeed(Path("unused"))
override def importProject(shortCode: Project.ShortCode, fileToImport: Path): Task[Path] =
override def importProject(shortcode: Shortcode, fileToImport: Path): Task[Path] =
ZIO.succeed(Path("unused"))
}
}
Expand Down
27 changes: 14 additions & 13 deletions webapi/src/main/scala/dsp/valueobjects/Id.scala
Expand Up @@ -14,6 +14,7 @@ import scala.util.Try

import dsp.errors.ValidationException
import dsp.valueobjects.Iri
import dsp.valueobjects.Project.Shortcode

sealed trait Id
object Id {
Expand Down Expand Up @@ -121,12 +122,12 @@ object Id {
*
* @param uuid the UUID of the project
* @param iri the IRI of the project
* @param shortCode the shortcode of the project
* @param shortcode the shortcode of the project
*/
abstract case class ProjectId private (
uuid: UUID,
iri: Iri.ProjectIri,
shortCode: Project.ShortCode
shortcode: Shortcode
)

/**
Expand All @@ -135,43 +136,43 @@ abstract case class ProjectId private (
object ProjectId {

/**
* Generates a ProjectId instance provided a Project IRI and a ShortCode. The UUID is extracted from the IRI.
* Generates a ProjectId instance provided a Project IRI and a Shortcode. The UUID is extracted from the IRI.
*
* @param iri the project IRI
* @param shortCode the project short code (as defined by the ARK resolver)
* @param shortcode the project short code (as defined by the ARK resolver)
* @return a new ProjectId instance
*/
def fromIri(iri: Iri.ProjectIri, shortCode: Project.ShortCode): Validation[ValidationException, ProjectId] = {
def fromIri(iri: Iri.ProjectIri, shortcode: Shortcode): Validation[ValidationException, ProjectId] = {
val uuid: Try[UUID] = Try(UUID.fromString(iri.value.split("/").last))
val projectId: Either[ValidationException, ProjectId] = uuid.toEither.fold(
_ => Left(new ValidationException(IdErrorMessages.IriDoesNotContainUuid(iri))),
uuid => Right(new ProjectId(uuid = uuid, iri = iri, shortCode = shortCode) {})
uuid => Right(new ProjectId(uuid = uuid, iri = iri, shortcode = shortcode) {})
)
Validation.fromEither(projectId)
}

/**
* Generates a ProjectId instance provided a UUID and a ShortCode. The Project IRI is generated on basis of the UUID.
* Generates a ProjectId instance provided a UUID and a Shortcode. The Project IRI is generated on basis of the UUID.
*
* @param uuid the project UUID
* @param shortCode the project short code (as defined by the ARK resolver)
* @param shortcode the project short code (as defined by the ARK resolver)
* @return a new ProjectId instance
*/
def fromUuid(uuid: UUID, shortCode: Project.ShortCode): Validation[ValidationException, ProjectId] = {
def fromUuid(uuid: UUID, shortcode: Shortcode): Validation[ValidationException, ProjectId] = {
val iri = Iri.ProjectIri.make(s"http://rdfh.ch/projects/${uuid}")
iri.map(iri => new ProjectId(uuid = uuid, iri = iri, shortCode = shortCode) {})
iri.map(iri => new ProjectId(uuid = uuid, iri = iri, shortcode = shortcode) {})
}

/**
* Generates a ProjectId instance with a new (random) UUID and an IRI which is created from a prefix and the UUID.
*
* @param shortCode the project short code (as defined by the ARK resolver)
* @param shortcode the project short code (as defined by the ARK resolver)
* @return a new ProjectId instance
*/
def make(shortCode: Project.ShortCode): Validation[ValidationException, ProjectId] = {
def make(shortcode: Shortcode): Validation[ValidationException, ProjectId] = {
val uuid = UUID.randomUUID()
val iri = Iri.ProjectIri.make(s"http://rdfh.ch/projects/${uuid}")
iri.map(iri => new ProjectId(uuid = uuid, iri = iri, shortCode = shortCode) {})
iri.map(iri => new ProjectId(uuid = uuid, iri = iri, shortcode = shortcode) {})
}
}

Expand Down
40 changes: 14 additions & 26 deletions webapi/src/main/scala/dsp/valueobjects/Project.scala
Expand Up @@ -20,18 +20,6 @@ object Project {
// A regex for matching a string containing the project ID.
private val ProjectIDRegex: Regex = ("^" + ProjectIDPattern + "$").r

/**
* Given the project shortcode, checks if it is in a valid format, and converts it to upper case.
*
* @param shortcode the project's shortcode.
* @return the shortcode in upper case.
*/
def validateProjectShortCode(shortCode: String, errorFun: => Nothing): String =
ProjectIDRegex.findFirstIn(shortCode.toUpperCase) match {
case Some(value) => value
case None => errorFun
}

// A regex sub-pattern for ontology prefix labels and local entity names. According to
// <https://www.w3.org/TR/turtle/#prefixed-name>, a prefix label in Turtle must be a valid XML NCName
// <https://www.w3.org/TR/1999/REC-xml-names-19990114/#NT-NCName>. Knora also requires a local entity name to
Expand Down Expand Up @@ -63,27 +51,27 @@ object Project {
// TODO-mpro: longname, description, keywords, logo are missing enhanced validation

/**
* Project ShortCode value object.
* Project Shortcode value object.
*/
sealed abstract case class ShortCode private (value: String)
object ShortCode { self =>
implicit val decoder: JsonDecoder[ShortCode] = JsonDecoder[String].mapOrFail { case value =>
ShortCode.make(value).toEitherWith(e => e.head.getMessage())
sealed abstract case class Shortcode private (value: String)
object Shortcode { self =>
implicit val decoder: JsonDecoder[Shortcode] = JsonDecoder[String].mapOrFail { case value =>
Shortcode.make(value).toEitherWith(e => e.head.getMessage())
}
implicit val encoder: JsonEncoder[ShortCode] =
JsonEncoder[String].contramap((shortCode: ShortCode) => shortCode.value)
implicit val encoder: JsonEncoder[Shortcode] =
JsonEncoder[String].contramap((shortcode: Shortcode) => shortcode.value)

def make(value: String): Validation[ValidationException, ShortCode] =
def make(value: String): Validation[ValidationException, Shortcode] =
if (value.isEmpty) {
Validation.fail(ValidationException(ProjectErrorMessages.ShortCodeMissing))
Validation.fail(ValidationException(ProjectErrorMessages.ShortcodeMissing))
} else {
ProjectIDRegex.matches(value.toUpperCase) match {
case false => Validation.fail(ValidationException(ProjectErrorMessages.ShortCodeInvalid(value)))
case true => Validation.succeed(new ShortCode(value.toUpperCase) {})
case false => Validation.fail(ValidationException(ProjectErrorMessages.ShortcodeInvalid(value)))
case true => Validation.succeed(new Shortcode(value.toUpperCase) {})
}
}

def make(value: Option[String]): Validation[ValidationException, Option[ShortCode]] =
def make(value: Option[String]): Validation[ValidationException, Option[Shortcode]] =
value match {
case Some(v) => self.make(v).map(Some(_))
case None => Validation.succeed(None)
Expand Down Expand Up @@ -264,8 +252,8 @@ object Project {
}

object ProjectErrorMessages {
val ShortCodeMissing = "ShortCode cannot be empty."
val ShortCodeInvalid = (v: String) => s"ShortCode is invalid: $v"
val ShortcodeMissing = "Shortcode cannot be empty."
val ShortcodeInvalid = (v: String) => s"Shortcode is invalid: $v"
val ShortNameMissing = "Shortname cannot be empty."
val ShortNameInvalid = (v: String) => s"Shortname is invalid: $v"
val NameMissing = "Name cannot be empty."
Expand Down
Expand Up @@ -513,12 +513,12 @@ object ProjectIdentifierADM {
/**
* Represents [[ShortcodeIdentifier]] identifier.
*
* @param value that constructs the identifier in the type of [[ShortCode]] value object.
* @param value that constructs the identifier in the type of [[Shortcode]] value object.
*/
final case class ShortcodeIdentifier(value: ShortCode) extends ProjectIdentifierADM
final case class ShortcodeIdentifier(value: Shortcode) extends ProjectIdentifierADM
object ShortcodeIdentifier {
def fromString(value: String): Validation[ValidationException, ShortcodeIdentifier] =
ShortCode.make(value).map {
Shortcode.make(value).map {
ShortcodeIdentifier(_)
}
}
Expand Down
Expand Up @@ -18,7 +18,7 @@ import dsp.valueobjects.Project._
final case class ProjectCreatePayloadADM(
id: Option[ProjectIri] = None,
shortname: ShortName,
shortcode: ShortCode,
shortcode: Shortcode,
longname: Option[Name] = None,
description: ProjectDescription,
keywords: Keywords,
Expand All @@ -34,7 +34,7 @@ object ProjectCreatePayloadADM {
def make(apiRequest: CreateProjectApiRequestADM): Validation[Throwable, ProjectCreatePayloadADM] = {
val id: Validation[Throwable, Option[ProjectIri]] = ProjectIri.make(apiRequest.id)
val shortname: Validation[Throwable, ShortName] = ShortName.make(apiRequest.shortname)
val shortcode: Validation[Throwable, ShortCode] = ShortCode.make(apiRequest.shortcode)
val shortcode: Validation[Throwable, Shortcode] = Shortcode.make(apiRequest.shortcode)
val longname: Validation[Throwable, Option[Name]] = Name.make(apiRequest.longname)
val description: Validation[Throwable, ProjectDescription] = ProjectDescription.make(apiRequest.description)
val keywords: Validation[Throwable, Keywords] = Keywords.make(apiRequest.keywords)
Expand Down
Expand Up @@ -150,8 +150,8 @@ final case class ProjectsRouteZ(
result <- projectsService.exportProject(projectIri, requestingUser)
} yield Response.json(result.toJson)

private def postImportProject(shortCode: String, requestingUser: UserADM): Task[Response] = for {
result <- projectsService.importProject(shortCode, requestingUser)
private def postImportProject(shortcode: String, requestingUser: UserADM): Task[Response] = for {
result <- projectsService.importProject(shortcode, requestingUser)
} yield Response.json(result.toJson)

private def getProjectExports(requestingUser: UserADM): Task[Response] =
Expand Down
Expand Up @@ -300,7 +300,7 @@ final case class ResourcesRouteV1()(
ZIO.serviceWithZIO[MessageRelay](
_.ask[GetFileMetadataResponse](GetFileMetadataRequest(tempFilePath, userADM))
)
fileValue <- makeFileValue(filename, fileMetadataResponse, project.shortcode)
fileValue <- makeFileValue(filename, fileMetadataResponse, project.shortcode.value)
} yield Some(fileValue)
case None => ZIO.none
}
Expand Down Expand Up @@ -356,7 +356,7 @@ final case class ResourcesRouteV1()(
_ <- ZIO.fail(BadRequestException(duplicatesErrorMsg)).when(duplicateClientIDs.nonEmpty)
project <- RouteUtilV1.getProjectByIri(projectIri)
resourcesToCreate <-
ZIO.foreach(resourceRequest)(createOneResourceRequestFromXmlImport(_, project.shortcode, userProfile))
ZIO.foreach(resourceRequest)(createOneResourceRequestFromXmlImport(_, project.shortcode.value, userProfile))
} yield MultipleResourceCreateRequestV1(resourcesToCreate, projectIri, userProfile, apiRequestID)
}

Expand Down
Expand Up @@ -12,7 +12,7 @@ import dsp.errors.BadRequestException
import dsp.errors.NotFoundException
import dsp.valueobjects.Iri.ProjectIri
import dsp.valueobjects.Project
import dsp.valueobjects.Project.ShortCode
import dsp.valueobjects.Project.Shortcode
import org.knora.webapi.IRI
import org.knora.webapi.messages.admin.responder.projectsmessages.ProjectIdentifierADM._
import org.knora.webapi.messages.admin.responder.projectsmessages._
Expand Down Expand Up @@ -260,7 +260,7 @@ final case class ProjectsADMRestServiceLive(
requestingUser: UserADM
): Task[ProjectImportResponse] = for {
_ <- permissionService.ensureSystemAdmin(requestingUser)
shortcode <- ShortCode.make(projectShortcode).toZIO
shortcode <- Shortcode.make(projectShortcode).toZIO
path <-
projectImportService
.importProject(shortcode, requestingUser)
Expand Down
Expand Up @@ -4,25 +4,21 @@
*/

package org.knora.webapi.slice.admin.domain.model

import zio.NonEmptyChunk

import dsp.valueobjects.Project.ShortCode
import dsp.valueobjects.Project.Shortcode
import dsp.valueobjects.V2.StringLiteralV2
import org.knora.webapi.slice.resourceinfo.domain.InternalIri

case class KnoraProject(
id: InternalIri,
shortname: String,
shortcode: String,
shortcode: Shortcode,
longname: Option[String],
description: NonEmptyChunk[StringLiteralV2],
keywords: List[String],
logo: Option[String],
status: Boolean,
selfjoin: Boolean
) {
def projectShortCode: ShortCode = ShortCode
.make(shortcode)
.toEither
.getOrElse(throw new IllegalStateException(s"This should not happen but shortcode ${shortcode} is not valid"))
}
)

0 comments on commit f01c319

Please sign in to comment.