Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix(ontology): Don't accept list values without gui attribute (DEV-775)…
… (#2089)

* add test data

* fix bug and add test

* add missing import

* fix typo in test data

* add checks for all gui elements that need a gui attribute

* update freetest data

* fix typo in test data

* check more values

* add tests

* remove prints

* wip

* remove featureFactoryConfig from new tests

* refactor schema domain

* remove make() from option

* bump zio prelude version

* rename decision file

* rename file

* add value objects

* move salsah-gui constants to shared project

* rename SalsahGuiApiV2WithValueObjects

* improve value objects

* improve validation

* use val instead of string

* small improvements

* add more tests

* improve docs

* add r2r test

* update expected client test data

* Update expected-client-test-data.txt

* Update expected-client-test-data.txt

* make unit tests more readable

* add new error type for validation fails

* improve error messages
  • Loading branch information
irinaschubert committed Jul 18, 2022
1 parent c39b3d3 commit 74a14e1
Show file tree
Hide file tree
Showing 28 changed files with 1,273 additions and 418 deletions.
6 changes: 3 additions & 3 deletions docs/03-apis/api-v2/editing-resources.md
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
-->

# Editing Resources
# Creating and Editing Resources

## Creating a Resource

Expand All @@ -17,7 +17,7 @@ The body of the request is a JSON-LD document in the
[complex API schema](introduction.md#api-schema), specifying the type,`rdfs:label`, and its Knora resource properties
and their values. The representation of the resource is the same as when it is returned in a `GET` request, except that
its `knora-api:attachedToUser` is not given, and the resource IRI and those of its values can be optionally specified.
The format of the values submitted is described in [Editing Values](editing-values.md). If there are multiple values for
The format of the values submitted is described in [Creating and Editing Values](editing-values.md). If there are multiple values for
a property, these must be given in an array.

For example, here is a request to create a resource with various value types:
Expand Down Expand Up @@ -222,7 +222,7 @@ of the resource.

## Modifying a Resource's Values

See [Editing Values](editing-values.md).
See [Creating and Editing Values](editing-values.md).

## Modifying a Resource's Metadata

Expand Down
2 changes: 1 addition & 1 deletion docs/03-apis/api-v2/editing-values.md
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
-->

# Editing Values
# Creating and Editing Values

## Creating a Value

Expand Down
4 changes: 2 additions & 2 deletions docs/03-apis/api-v2/index.md
Expand Up @@ -13,8 +13,8 @@
- [Getting Lists](getting-lists.md)
- [XML to Standoff Mapping](xml-to-standoff-mapping.md)
- [Gravsearch: Virtual Graph Search](query-language.md)
- [Editing Resources](editing-resources.md)
- [Editing Values](editing-values.md)
- [Creating and Editing Resources](editing-resources.md)
- [Creating and Editing Values](editing-values.md)
- [Querying, Creating, and Updating Ontologies](ontology-information.md)
- [TEI/XML](tei-xml.md)
- [Permalinks](permalinks.md)
@@ -1,7 +1,6 @@
/*
* 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
Expand All @@ -11,11 +10,11 @@ import zio.prelude.Validation
object SchemaDomain extends App {
// implicitly["".type =:= "".type]

type IRI = String
type UserID = String
type IRI = String
type UserID = String
type UserProfile = String
type SchemaID = String
type Schema = String
type SchemaID = String
type Schema = String

final case class OntologyInfo(name: String, projectIri: IRI, label: String, comment: String)
final case class OntologyClass[A <: Singleton with String](name: A, label: String, comment: String) { self =>
Expand Down Expand Up @@ -62,19 +61,19 @@ object SchemaDomain extends App {
ct: CardinalityType
): WithTags[oc.Tag, op.Tag] =
new Cardinality {
type ClassTag = oc.Tag
type ClassTag = oc.Tag
type PropertyTag = op.Tag

val ontologyClass = oc
val ontologyClass = oc
val ontologyProperty = op
val cardinalityType = ct
val cardinalityType = ct
}
}

sealed trait CardinalityType
object CardinalityType {
case object MaxCardinalityOne extends CardinalityType
case object MinCardinalityOne extends CardinalityType
case object MaxCardinalityOne extends CardinalityType
case object MinCardinalityOne extends CardinalityType
case object MinCardinalityZero extends CardinalityType
}

Expand Down Expand Up @@ -214,16 +213,16 @@ object SchemaDomain extends App {
//trying it out
val ontoInfo = OntologyInfo("test", "http://example.org/test", "Test", "Test")

val classOne = OntologyClass("ClassOne", "Class One", "Class One")
val classOne = OntologyClass("ClassOne", "Class One", "Class One")
val propertyOne = OntologyProperty("PropertyOne", "Property One", "Property One", "http://example.org/test")

val classTwo = OntologyClass("ClassTwo", "Class Two", "Class Two")
val classTwo = OntologyClass("ClassTwo", "Class Two", "Class Two")
val propertyTwo = OntologyProperty("PropertyTwo", "Property Two", "Property Two", "http://example.org/test")

val cardOne = Cardinality(classOne, propertyOne, CardinalityType.MinCardinalityOne)
val cardTwo = Cardinality(classTwo, propertyTwo, CardinalityType.MinCardinalityOne)
val cardOne = Cardinality(classOne, propertyOne, CardinalityType.MinCardinalityOne)
val cardTwo = Cardinality(classTwo, propertyTwo, CardinalityType.MinCardinalityOne)
val cardThree = Cardinality(classOne, propertyTwo, CardinalityType.MinCardinalityOne)
val cardFour = Cardinality(classTwo, propertyOne, CardinalityType.MinCardinalityOne)
val cardFour = Cardinality(classTwo, propertyOne, CardinalityType.MinCardinalityOne)

val exampleOnto: Ontology[Any with "ClassOne" with "ClassTwo", Any with "PropertyOne"] =
Ontology
Expand Down
119 changes: 119 additions & 0 deletions dsp-shared/src/main/scala/dsp/constants/SalsahGui.scala
@@ -0,0 +1,119 @@
/*
* 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.constants

import dsp.errors._

/**
* Contains string constants for IRIs from ontologies used by the application.
*/
object SalsahGui {

/**
* `IRI` is a synonym for `String`, used to improve code readability.
*/
type IRI = String

val InternalOntologyStart = "http://www.knora.org/ontology"

val SalsahGuiOntologyLabel: String = "salsah-gui"
val SalsahGuiOntologyIri: IRI = InternalOntologyStart + "/" + SalsahGuiOntologyLabel
val SalsahGuiPrefixExpansion: IRI = SalsahGuiOntologyIri + "#"

val GuiAttribute: IRI = SalsahGuiPrefixExpansion + "guiAttribute"
val GuiAttributeDefinition: IRI = SalsahGuiPrefixExpansion + "guiAttributeDefinition"
val GuiOrder: IRI = SalsahGuiPrefixExpansion + "guiOrder"
val GuiElementProp: IRI = SalsahGuiPrefixExpansion + "guiElement"
val GuiElementClass: IRI = SalsahGuiPrefixExpansion + "Guielement"

val SimpleText: IRI = SalsahGuiPrefixExpansion + "SimpleText"
val Textarea: IRI = SalsahGuiPrefixExpansion + "Textarea"
val Pulldown: IRI = SalsahGuiPrefixExpansion + "Pulldown"
val Slider: IRI = SalsahGuiPrefixExpansion + "Slider"
val Spinbox: IRI = SalsahGuiPrefixExpansion + "Spinbox"
val Searchbox: IRI = SalsahGuiPrefixExpansion + "Searchbox"
val Date: IRI = SalsahGuiPrefixExpansion + "Date"
val Geometry: IRI = SalsahGuiPrefixExpansion + "Geometry"
val Colorpicker: IRI = SalsahGuiPrefixExpansion + "Colorpicker"
val List: IRI = SalsahGuiPrefixExpansion + "List"
val Radio: IRI = SalsahGuiPrefixExpansion + "Radio"
val Checkbox: IRI = SalsahGuiPrefixExpansion + "Checkbox"
val Richtext: IRI = SalsahGuiPrefixExpansion + "Richtext"
val Interval: IRI = SalsahGuiPrefixExpansion + "Interval"
val TimeStamp: IRI = SalsahGuiPrefixExpansion + "TimeStamp"
val Geonames: IRI = SalsahGuiPrefixExpansion + "Geonames"
val Fileupload: IRI = SalsahGuiPrefixExpansion + "Fileupload"

val GuiElements = scala.collection.immutable.List(
SimpleText,
Textarea,
Pulldown,
Slider,
Spinbox,
Searchbox,
Date,
Geometry,
Colorpicker,
List,
Radio,
Checkbox,
Richtext,
Interval,
TimeStamp,
Geonames,
Fileupload
)

val GuiAttributes = scala.collection.immutable.List(
"ncolors",
"hlist",
"numprops",
"size",
"maxlength",
"min",
"max",
"cols",
"rows",
"width",
"wrap"
)

object SalsahGuiAttributeType extends Enumeration {

val Integer: Value = Value(0, "integer")
val Percent: Value = Value(1, "percent")
val Decimal: Value = Value(2, "decimal")
val Str: Value = Value(3, "string")
val Iri: Value = Value(4, "iri")

val valueMap: Map[String, Value] = values.map(v => (v.toString, v)).toMap

def lookup(name: String): Value =
valueMap.get(name) match {
case Some(value) => value
case None => throw InconsistentRepositoryDataException(s"salsah-gui attribute type not found: $name")
}
}

object External {
// external representation of salsah-gui entities of the form: http://api.knora.org/ontology/salsah-gui/v2#...
val ApiOntologyHostname: String = "http://api.knora.org"
val ApiOntologyStart: String = ApiOntologyHostname + "/ontology/"
val VersionSegment = "/v2"
val SalsahGuiOntologyIri: IRI = ApiOntologyStart + SalsahGui.SalsahGuiOntologyLabel + VersionSegment
val SalsahGuiPrefixExpansion: IRI = SalsahGuiOntologyIri + "#"

val GuiAttribute: IRI = SalsahGuiPrefixExpansion + "guiAttribute"
val GuiOrder: IRI = SalsahGuiPrefixExpansion + "guiOrder"
val GuiElementProp: IRI = SalsahGuiPrefixExpansion + "guiElement"
val GuiAttributeDefinition: IRI = SalsahGuiPrefixExpansion + "guiAttributeDefinition"
val GuiElementClass: IRI = SalsahGuiPrefixExpansion + "Guielement"
val Geometry: IRI = SalsahGuiPrefixExpansion + "Geometry"
val Colorpicker: IRI = SalsahGuiPrefixExpansion + "Colorpicker"
val Fileupload: IRI = SalsahGuiPrefixExpansion + "Fileupload"
val Richtext: IRI = SalsahGuiPrefixExpansion + "Richtext"
}
}
8 changes: 8 additions & 0 deletions dsp-shared/src/main/scala/dsp/errors/Errors.scala
Expand Up @@ -168,6 +168,14 @@ case class InvalidJsonLDException(msg: String, cause: Throwable = null) extends
*/
case class InvalidRdfException(msg: String, cause: Throwable = null) extends RequestRejectedException(msg, cause)

/**
* An exception indication that the validation of one or more values submitted to the API v2 failed.
*
* @param msg a description of the error.
* @param cause the cause for the error
*/
case class ValidationException(msg: String, cause: Throwable = null) extends RequestRejectedException(msg, cause)

/**
* An abstract class for exceptions indicating that something went wrong and it's not the client's fault.
*
Expand Down
22 changes: 22 additions & 0 deletions dsp-shared/src/main/scala/dsp/valueobjects/Iri.scala
Expand Up @@ -137,6 +137,27 @@ object Iri {
}
}
}

/**
* PropertyIri value object.
*/
sealed abstract case class PropertyIri private (value: String) extends Iri
object PropertyIri {
def make(value: String): Validation[Throwable, PropertyIri] =
if (value.isEmpty) {
Validation.fail(BadRequestException(IriErrorMessages.PropertyIriMissing))
} else {
// TODO all the following needs to be checked when validating a property iri (see string formatter for the implementations of these methods)
// if (
// !(propertyIri.isKnoraApiV2EntityIri &&
// propertyIri.getOntologySchema.contains(ApiV2Complex) &&
// propertyIri.getOntologyFromEntity == externalOntologyIri)
// ) {
// throw BadRequestException(s"Invalid property IRI: $propertyIri")
// }
Validation.succeed(new PropertyIri(value) {})
}
}
}

object IriErrorMessages {
Expand All @@ -149,4 +170,5 @@ object IriErrorMessages {
val UserIriMissing = "User IRI cannot be empty."
val UserIriInvalid = "User IRI is invalid."
val UuidVersionInvalid = "Invalid UUID used to create IRI. Only versions 4 and 5 are supported."
val PropertyIriMissing = "Property IRI cannot be empty."
}

0 comments on commit 74a14e1

Please sign in to comment.