From 21a2cbaef9484cab5187e7a3e2d57811b93d24c3 Mon Sep 17 00:00:00 2001 From: Michael Pollmeier Date: Sun, 24 Mar 2024 15:09:59 +0100 Subject: [PATCH] fail gracefully for unsupported property types (both in schema and at runtime) --- .../UnsupportedOperationException.java | 11 ++++++ .../scala/flatgraph/DiffGraphApplier.scala | 20 ++++++++-- core/src/main/scala/flatgraph/Schema.scala | 2 +- .../src/test/scala/flatgraph/GraphTests.scala | 39 +++++++++++++++++++ 4 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 core/src/main/java/flatgraph/UnsupportedOperationException.java diff --git a/core/src/main/java/flatgraph/UnsupportedOperationException.java b/core/src/main/java/flatgraph/UnsupportedOperationException.java new file mode 100644 index 00000000..fed487ec --- /dev/null +++ b/core/src/main/java/flatgraph/UnsupportedOperationException.java @@ -0,0 +1,11 @@ +package flatgraph; + +public class UnsupportedOperationException extends RuntimeException { + public UnsupportedOperationException(String message) { + super(message); + } + + public UnsupportedOperationException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/src/main/scala/flatgraph/DiffGraphApplier.scala b/core/src/main/scala/flatgraph/DiffGraphApplier.scala index 95d8dcba..d6f28c69 100644 --- a/core/src/main/scala/flatgraph/DiffGraphApplier.scala +++ b/core/src/main/scala/flatgraph/DiffGraphApplier.scala @@ -545,8 +545,14 @@ private[flatgraph] class DiffGraphApplier(graph: Graph, diff: DiffGraphBuilder) while (insertionCounter < insertions.size && insertions(insertionCounter).src.seq == insertionSeq) { val insertion = insertions(insertionCounter) newNeighbors(insertionBaseIndex + insertionCounter) = insertion.dst - if (newPropertyView != null && insertion.property != DefaultValue) - newPropertyView(insertionBaseIndex + insertionCounter) = insertion.property + if (newPropertyView != null && insertion.property != DefaultValue) { + try { + newPropertyView(insertionBaseIndex + insertionCounter) = insertion.property + } catch { + case _: ArrayStoreException => + throw new UnsupportedOperationException(s"unsupported property type: ${insertion.property.getClass}") + } + } insertionCounter += 1 } newQty(insertionSeq + 1) = insertionBaseIndex + insertionCounter @@ -617,8 +623,14 @@ private[flatgraph] class DiffGraphApplier(graph: Graph, diff: DiffGraphBuilder) } private def copyToArray[T](buf: mutable.ArrayBuffer[Any], dst: Array[T]): Unit = { - // this is a dirty hack in order to make scala type system shut up - buf.asInstanceOf[mutable.ArrayBuffer[T]].copyToArray(dst) + try { + // this is a dirty hack in order to make scala type system shut up + buf.asInstanceOf[mutable.ArrayBuffer[T]].copyToArray(dst) + } catch { + case _: ArrayStoreException => + val typeMaybe = buf.headOption.map(property => s": ${property.getClass}").getOrElse("") + throw new UnsupportedOperationException(s"unsupported property type$typeMaybe") + } } private def get(a: Array[Int], idx: Int): Int = if (idx < a.length) a(idx) else a.last diff --git a/core/src/main/scala/flatgraph/Schema.scala b/core/src/main/scala/flatgraph/Schema.scala index 59dd7763..60e39f8a 100644 --- a/core/src/main/scala/flatgraph/Schema.scala +++ b/core/src/main/scala/flatgraph/Schema.scala @@ -156,7 +156,7 @@ class FreeSchema( case a: Array[Double] => if (a.length == 0) FormalQtyType.DoubleType else FormalQtyType.DoubleTypeWithDefault(a(0)) case a: Array[String] => if (a.length == 0) FormalQtyType.StringType else FormalQtyType.StringTypeWithDefault(a(0)) case a: Array[GNode] => FormalQtyType.RefType - case _ => ??? + case other => throw new UnsupportedOperationException(s"unsupported property prototype: ${other.getClass}") } override def getNumberOfNodeKinds: Int = nodeLabels.length override def getNumberOfEdgeKinds: Int = edgeLabels.length diff --git a/core/src/test/scala/flatgraph/GraphTests.scala b/core/src/test/scala/flatgraph/GraphTests.scala index ce4a4f32..83767393 100644 --- a/core/src/test/scala/flatgraph/GraphTests.scala +++ b/core/src/test/scala/flatgraph/GraphTests.scala @@ -796,6 +796,45 @@ class GraphTests extends AnyWordSpec with Matchers { Accessors.getNodePropertyOptionCompat(v0, 2) shouldBe Some(Seq("c", "d")) } + "report error when trying to use unsupported property types" in { + class A(wrapped: String) // we don't support arbitrary classes as property types + + // ensure that we throw an exception when trying to define a schema with an arbitrary class as a node property type + intercept[UnsupportedOperationException] { + TestSchema.make(1, 1, nodePropertyPrototypes = Array(Array.empty[A])) + }.getMessage should include("unsupported property prototype") + + // same for edge properties + intercept[UnsupportedOperationException] { + TestSchema.make(1, 1, edgePropertyPrototypes = Array(Array.empty[A])) + }.getMessage should include("unsupported property prototype") + + // now let's define a valid schema and try to update a node/edge property to something illegal via a DiffGraph + val schema = TestSchema.make( + nodeKinds = 1, + edgeKinds = 1, + properties = 1, + edgePropertyPrototypes = Array(Array.empty[String]), + nodePropertyPrototypes = Array(Array.empty[String]), + formalQtys = Array(FormalQtyType.QtyOption, null, FormalQtyType.QtyMulti, null) + ) + + val g = new Graph(schema) + val v0 = new GenericDNode(0) + val v1 = new GenericDNode(0) + DiffGraphApplier.applyDiff(g, new DiffGraphBuilder(schema).addNode(v0).addNode(v1)) + + // try to update a node property to something unsupported via a DiffGraph + intercept[UnsupportedOperationException] { + DiffGraphApplier.applyDiff(g, new DiffGraphBuilder(schema)._setNodeProperty(v0.storedRef.get, 0, new A("should not work"))) + }.getMessage should include("unsupported property type") + + // same for edge properties + intercept[UnsupportedOperationException] { + DiffGraphApplier.applyDiff(g, new DiffGraphBuilder(schema)._addEdge(v0, v1, 0, new A("should not work"))) + }.getMessage should include("unsupported property type") + } + "Support custom domain classes for detached nodes" in { class CustomNode extends DNode { override type StoredNodeType = GNode