Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix(cardinality): Check cardinality with multiple inherited classes (D…
…EV-1189) (#2164)

* check that always the stricter cardinality is taken

* add unit tests

* remove replaced method

* add comments

* introduce value objects for cardinalities

* check cardinality compatibility in case of multiple super classes

* fix error message

* clean up docstrings

* fix broken docs link

* remove unused project

* fix error in docs

* add testdata and tests

* fix build.sbt

* simplify test data

* improve comments

* cleanup imports

* fix bad references for coverage

* improve comments

* reintroduce return

* wrap case objects

* simplify test

* fix missing import

* implement feedback from review

* run scalafmt

Co-authored-by: Ivan Subotic <400790+subotic@users.noreply.github.com>
  • Loading branch information
irinaschubert and subotic committed Aug 23, 2022
1 parent 79b88d2 commit f183d7d
Show file tree
Hide file tree
Showing 46 changed files with 672 additions and 426 deletions.
1 change: 0 additions & 1 deletion Makefile
Expand Up @@ -209,7 +209,6 @@ test: build ## runs all tests
sbt -v coverage "userHandler/test"
sbt -v coverage "userInterface/test"
sbt -v coverage "userRepo/test"
sbt -v coverage "valueObjects/test"
sbt -v coverage "webapi/test"
sbt coverageAggregate

Expand Down
25 changes: 14 additions & 11 deletions build.sbt
Expand Up @@ -27,7 +27,20 @@ lazy val buildSettings = Seq(
lazy val rootBaseDir = ThisBuild / baseDirectory

lazy val root: Project = Project(id = "root", file("."))
.aggregate(webapi, sipi, shared, valueObjects, userCore, userHandler, userRepo, userInterface)
.aggregate(
webapi,
sipi,
shared,
userCore,
userHandler,
userRepo,
userInterface,
roleCore,
roleRepo,
roleHandler,
roleInterface,
schemaCore
)
.enablePlugins(GitVersioning, GitBranchPrompt)
.settings(
// values set for all sub-projects
Expand Down Expand Up @@ -231,16 +244,6 @@ lazy val webapiJavaTestOptions = Seq(
// DSP's new codebase
//////////////////////////////////////

// Value Objects project

lazy val valueObjects = project
.in(file("dsp-value-objects"))
.settings(
name := "valueObjects",
libraryDependencies ++= Dependencies.valueObjectsLibraryDependencies,
testFrameworks := Seq(new TestFramework("zio.test.sbt.ZTestFramework"))
)

// Role projects

lazy val roleInterface = project
Expand Down
2 changes: 1 addition & 1 deletion docs/02-knora-ontologies/knora-base.md
Expand Up @@ -32,7 +32,7 @@ In Knora, each item of data belongs to some particular project. Each project usi

- `projectShortname` (1): A short name that can be used to identify the project in configuration files and the like.

- `projectLongname` (1): The full name of the project.
- `projectLongname` (0-1): The full name of the project.

- `projectShortcode` (1): A hexadecimal code that uniquely identifies the project. These codes are assigned to projects
by the [DaSCH](http://dasch.swiss/).
Expand Down
2 changes: 1 addition & 1 deletion docs/Readme.md
Expand Up @@ -3,7 +3,7 @@
## MkDocs Documentation

This folder contains the sources to the DSP-API documentation website published
under <http://docs-api.dasch.swiss>
under <https://docs.dasch.swiss/>

The `src` folder contains the following documentation sources:

Expand Down
Expand Up @@ -138,11 +138,10 @@ final case class RoleHandler(repo: RoleRepo) {
* Companion object providing the layer with an initialized implementation
*/
object RoleHandler {
val layer: ZLayer[RoleRepo, Nothing, RoleHandler] = {
val layer: ZLayer[RoleRepo, Nothing, RoleHandler] =
ZLayer {
for {
repo <- ZIO.service[RoleRepo]
} yield RoleHandler(repo)
}.tap(_ => ZIO.logInfo(">>> Role Handler initilaized <<<"))
}
}
Expand Up @@ -63,11 +63,10 @@ final case class RoleRepoLive(
* Companion object providing the layer with an initialized implementation of [[RoleRepo]]
*/
object RoleRepoLive {
val layer: ZLayer[Any, Nothing, RoleRepo] = {
val layer: ZLayer[Any, Nothing, RoleRepo] =
ZLayer {
for {
roles <- TMap.empty[UUID, Role].commit
} yield RoleRepoLive(roles)
}.tap(_ => ZIO.logInfo(">>> Role repository initialized <<<"))
}
}
Expand Up @@ -63,11 +63,10 @@ final case class RoleRepoMock(
* Companion object providing the layer with an initialized implementation of [[RoleRepo]]
*/
object RoleRepoMock {
val layer: ZLayer[Any, Nothing, RoleRepo] = {
val layer: ZLayer[Any, Nothing, RoleRepo] =
ZLayer {
for {
roles <- TMap.empty[UUID, Role].commit
} yield RoleRepoMock(roles)
}.tap(_ => ZIO.logInfo(">>> In-memory role repository initialized <<<"))
}
}
@@ -0,0 +1,68 @@
/*
* Copyright © 2021 - 2022 Data and Service Center for the Humanities and/or DaSCH Service Platform contributors.
* SPDX-License-Identifier: Apache-2.0
*/

package dsp.schema.domain

import Cardinality._

/**
* Represents a cardinality value object.
*/
sealed trait Cardinality { self =>

/**
* The string representation of the cardinality
*/
val value: String

/**
* Checks whether a cardinality is stricter than another one.
*
* @param that the cardinality to be compared against
* @return `true` if the present cardinality is stricter than `that`, `false` otherwise
*/
def isStricterThan(that: Cardinality): Boolean =
if (self == that) {
false
} else {
self match {
case MustHaveOne => true
case MustHaveSome => that == MayHaveMany
case MayHaveOne => that == MayHaveMany
case MayHaveMany => false
}
}
}

object Cardinality {

/**
* The cardinality of a property that must have one value.
*/
final case object MustHaveOne extends Cardinality {
override val value: String = "1"
}

/**
* The cardinality of a property that must have at least one value.
*/
final case object MustHaveSome extends Cardinality {
override val value: String = "1-n"
}

/**
* The cardinality of a property that may have one value.
*/
final case object MayHaveOne extends Cardinality {
override val value: String = "0-1"
}

/**
* The cardinality of a property that may have one or more values.
*/
final case object MayHaveMany extends Cardinality {
override val value: String = "0-n"
}
}
Expand Up @@ -210,7 +210,7 @@ object SchemaDomain extends App {
implicit def isSubtypeOf2[A, B >: A]: NotSubtypeOf[A, B] = new NotSubtypeOf[A, B] {}
}

//trying it out
// trying it out
val ontoInfo = OntologyInfo("test", "http://example.org/test", "Test", "Test")

val classOne = OntologyClass("ClassOne", "Class One", "Class One")
Expand All @@ -230,8 +230,8 @@ object SchemaDomain extends App {
.withClass(classOne)
.withClass(classTwo)
.withProperty(propertyOne)
//.withProperty(propertyOne)
//.withProperty(propertyTwo)
// .withProperty(propertyOne)
// .withProperty(propertyTwo)
.withCardinality(cardOne)
.withCardinality(cardOne)

Expand Down
@@ -1,5 +1,3 @@
package dsp.schema.handler

object CreateSchema {

}
object CreateSchema {}
Expand Up @@ -3,7 +3,6 @@ package dsp.schema.repo
import zio._
import dsp.schema.domain.SchemaDomain.{UserID, UserProfile}


trait SchemaRepo {
def lookup(id: UserID): Task[UserProfile]
def update(id: UserID, profile: UserProfile): Task[Unit]
Expand Down
Expand Up @@ -32,7 +32,7 @@ object SchemaCommandsSpec extends ZIOSpecDefault {
comment = Some(commentLangString)
guiAttribute <- Schema.GuiAttribute.make("hlist=<http://rdfh.ch/lists/082F/PbRLUy66TsK10qNP1mBwzA>")
guiElement <- Schema.GuiElement.make(SalsahGui.List)
guiObject <- Schema.GuiObject.make(guiAttributes = List(guiAttribute), guiElement = Some(guiElement))
guiObject <- Schema.GuiObject.make(guiAttributes = Set(guiAttribute), guiElement = Some(guiElement))
command = CreatePropertyCommand.make(
ontologyIri = ontologyIri,
lastModificationDate = lastModificationDate,
Expand Down
51 changes: 51 additions & 0 deletions test_data/ontologies/freetest-onto.ttl
Expand Up @@ -99,6 +99,57 @@
salsah-gui:guiElement salsah-gui:List ;
salsah-gui:guiAttribute "hlist=<http://rdfh.ch/lists/0001/free-test-list>" .

:hasPublicationDate
rdf:type owl:ObjectProperty ;
rdfs:subPropertyOf knora-base:hasValue ;
rdfs:label "Publikationsdatum"@de,
"Publication date"@en ;
knora-base:objectClassConstraint knora-base:TextValue ;
salsah-gui:guiElement salsah-gui:SimpleText ;
salsah-gui:guiAttribute "size=80",
"maxlength=255" .

:PubMayHaveMany
rdf:type owl:Class ;
rdfs:subClassOf knora-base:Resource,
[ rdf:type owl:Restriction ;
owl:onProperty :hasPublicationDate ;
owl:minCardinality "0"^^xsd:nonNegativeInteger ;
salsah-gui:guiOrder "1"^^xsd:nonNegativeInteger ];
rdfs:label "0-n"@en;
rdfs:comment """A comment"""@de .

:PubMayHaveOne
rdf:type owl:Class ;
rdfs:subClassOf knora-base:Resource,
[ rdf:type owl:Restriction ;
owl:onProperty :hasPublicationDate ;
owl:maxCardinality "1"^^xsd:nonNegativeInteger ;
salsah-gui:guiOrder "1"^^xsd:nonNegativeInteger ];
rdfs:label "0-1"@en;
rdfs:comment """A comment"""@de .

:PubMustHaveSome
rdf:type owl:Class ;
rdfs:subClassOf knora-base:Resource,
[ rdf:type owl:Restriction ;
owl:onProperty :hasPublicationDate ;
owl:minCardinality "1"^^xsd:nonNegativeInteger ;
salsah-gui:guiOrder "1"^^xsd:nonNegativeInteger ];
rdfs:label "1-n"@en;
rdfs:comment """A comment"""@de .

:PubMustHaveOne
rdf:type owl:Class ;
rdfs:subClassOf knora-base:Resource,
[ rdf:type owl:Restriction ;
owl:onProperty :hasPublicationDate ;
owl:cardinality "1"^^xsd:nonNegativeInteger ;
salsah-gui:guiOrder "1"^^xsd:nonNegativeInteger ];
rdfs:label "1"@en;
rdfs:comment """A comment"""@de .




:FreeTest
Expand Down
Expand Up @@ -5,10 +5,12 @@

package org.knora.webapi.messages.util

import dsp.schema.domain.Cardinality
import dsp.schema.domain.Cardinality._
import org.apache.commons.text.StringEscapeUtils
import org.knora.webapi.OntologySchema
import org.knora.webapi.messages.SmartIri
import org.knora.webapi.messages.v2.responder.ontologymessages.Cardinality
import org.knora.webapi.messages.v2.responder.ontologymessages.OwlCardinality

import java.time.Instant
import scala.reflect.runtime.{universe => ru}
Expand Down Expand Up @@ -58,23 +60,22 @@ object MessageUtil {
case instant: Instant => "Instant.parse(\"" + instant.toString + "\")"
case None => "None"

// Handle enumerations.

case cardinality: Cardinality.Value =>
// Handle value objects.
case cardinality: Cardinality =>
cardinality match {
case Cardinality.MayHaveOne => "Cardinality.MayHaveOne"
case Cardinality.MayHaveMany => "Cardinality.MayHaveMany"
case Cardinality.MustHaveOne => "Cardinality.MustHaveOne"
case Cardinality.MustHaveSome => "Cardinality.MustHaveSome"
case MayHaveOne => "MayHaveOne"
case MayHaveMany => "MayHaveMany"
case MustHaveOne => "MustHaveOne"
case MustHaveSome => "MustHaveSome"
}

// Handle enumerations.
case enumVal if enumVal.getClass.getName == "scala.Enumeration$Val" => enumVal.toString

case ontologySchema: OntologySchema =>
ontologySchema.getClass.getSimpleName.stripSuffix("$")

// Handle collections.

case Nil => "Nil"

case list: Seq[Any @unchecked] =>
Expand Down
Expand Up @@ -6,11 +6,12 @@
package org.knora.webapi.messages.util.standoff

import akka.actor.ActorRef
import com.typesafe.scalalogging.Logger
import akka.pattern.ask
import akka.util.Timeout
import org.knora.webapi.IRI
import com.typesafe.scalalogging.Logger
import dsp.errors._
import dsp.schema.domain.Cardinality._
import org.knora.webapi.IRI
import org.knora.webapi.messages.IriConversions._
import org.knora.webapi.messages.OntologyConstants
import org.knora.webapi.messages.SmartIri
Expand All @@ -23,7 +24,7 @@ import org.knora.webapi.messages.v1.responder.valuemessages.JulianDayNumberValue
import org.knora.webapi.messages.v1.responder.valuemessages.KnoraCalendarV1
import org.knora.webapi.messages.v1.responder.valuemessages.KnoraPrecisionV1
import org.knora.webapi.messages.v1.responder.valuemessages.UpdateValueV1
import org.knora.webapi.messages.v2.responder.ontologymessages.Cardinality.KnoraCardinalityInfo
import org.knora.webapi.messages.v2.responder.ontologymessages.OwlCardinality._
import org.knora.webapi.messages.v2.responder.ontologymessages._
import org.knora.webapi.messages.v2.responder.standoffmessages._
import org.knora.webapi.settings.KnoraSettingsImpl
Expand Down Expand Up @@ -208,7 +209,7 @@ object StandoffTagUtilV2 {

// filter all the required props
val mustExistOnce: Set[SmartIri] = classSpecificProps.filter { case (propIri, card) =>
card.cardinality == Cardinality.MustHaveOne || card.cardinality == Cardinality.MustHaveSome
card.cardinality == MustHaveOne || card.cardinality == MustHaveSome
}.keySet

// check if all the min cardinalities are respected
Expand All @@ -225,7 +226,7 @@ object StandoffTagUtilV2 {

// filter all the props that have a limited occurrence
val mayExistOnce = classSpecificProps.filter { case (propIri, card) =>
card.cardinality == Cardinality.MustHaveOne || card.cardinality == Cardinality.MayHaveOne
card.cardinality == MustHaveOne || card.cardinality == MayHaveOne
}.keySet

// check if all the max cardinalities are respected
Expand Down
Expand Up @@ -6,16 +6,17 @@
package org.knora.webapi.messages.v1.responder.ontologymessages

import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import dsp.schema.domain.Cardinality
import org.knora.webapi._
import org.knora.webapi.messages.IriConversions._
import org.knora.webapi.messages.ResponderRequest.KnoraRequestV1
import org.knora.webapi.messages.SmartIri
import org.knora.webapi.messages.StringFormatter
import org.knora.webapi.messages.admin.responder.usersmessages.UserADM
import org.knora.webapi.messages.store.triplestoremessages.StringLiteralV2
import org.knora.webapi.messages.ResponderRequest.KnoraRequestV1
import org.knora.webapi.messages.v1.responder.KnoraResponseV1
import org.knora.webapi.messages.v2.responder.ontologymessages.Cardinality.KnoraCardinalityInfo
import org.knora.webapi.messages.v2.responder.ontologymessages.EntityInfoContentV2
import org.knora.webapi.messages.v2.responder.ontologymessages.OwlCardinality.KnoraCardinalityInfo
import org.knora.webapi.messages.v2.responder.ontologymessages.ReadClassInfoV2
import org.knora.webapi.messages.v2.responder.ontologymessages._
import org.knora.webapi.messages.v2.responder.standoffmessages.StandoffDataTypeClasses
Expand Down Expand Up @@ -614,7 +615,6 @@ case class PropertyTypeV1(id: IRI, label: String) {
* A spray-json protocol for generating Knora API v1 JSON providing data about resources and their properties.
*/
object ResourceTypeV1JsonProtocol extends SprayJsonSupport with DefaultJsonProtocol with NullOptions {

implicit val propertyDefinitionV1Format: JsonFormat[PropertyDefinitionV1] = jsonFormat10(PropertyDefinitionV1)
implicit val propertyDefinitionInNamedGraphV1Format: JsonFormat[PropertyDefinitionInNamedGraphV1] = jsonFormat8(
PropertyDefinitionInNamedGraphV1
Expand Down

0 comments on commit f183d7d

Please sign in to comment.