Fix handling of empty descriptions in Turtle encoder

This commit is contained in:
Marcel Otto 2022-04-08 14:20:51 +02:00
parent 5bb1264249
commit c143272f50
4 changed files with 65 additions and 16 deletions

View file

@ -31,6 +31,11 @@ This project adheres to [Semantic Versioning](http://semver.org/) and
- When triples with an empty object list where added to an `RDF.Graph`, it
included empty descriptions, which lead to inconsistent behaviour
(for example it would be counted in `RDF.Graph.subject_count/1`).
- When an `RDF.Graph` contained empty descriptions these were rendered by
the `RDF.Turtle.Encoder` to a subject without predicates and objects, i.e.
invalid Turtle. This actually shouldn't happen and is either caused by
misuse or a bug. So instead, a `RDF.Graph.EmptyDescriptionError` with a
detailed message will be raised now when this case is detected.
[Compare v0.11.0...HEAD](https://github.com/rdf-elixir/rdf-ex/compare/v0.11.0...HEAD)

View file

@ -22,14 +22,6 @@ defmodule RDF.Triple.InvalidPredicateError do
end
end
defmodule RDF.XSD.Datatype.Mismatch do
defexception [:value, :expected_type]
def message(%{value: value, expected_type: expected_type}) do
"'#{inspect(value)}' is not a #{expected_type}"
end
end
defmodule RDF.Quad.InvalidGraphContextError do
defexception [:graph_context]
@ -38,6 +30,31 @@ defmodule RDF.Quad.InvalidGraphContextError do
end
end
defmodule RDF.Graph.EmptyDescriptionError do
defexception [:subject]
def message(%{subject: subject}) do
"""
RDF.Graph with empty description about '#{inspect(subject)}' detected.
Empty descriptions in a graph lead to inconsistent behaviour. The RDF.Graph API
should ensure that this never happens. So this probably happened by changing the
contents of the RDF.Graph struct directly, which is strongly discouraged.
You should always use the RDF.Graph API to change the content of a graph.
If this happened while using the RDF.Graph API, this is a bug.
Please report this at https://github.com/rdf-elixir/rdf-ex/issues and describe the
circumstances how this happened.
"""
end
end
defmodule RDF.XSD.Datatype.Mismatch do
defexception [:value, :expected_type]
def message(%{value: value, expected_type: expected_type}) do
"'#{inspect(value)}' is not a #{expected_type}"
end
end
defmodule RDF.Namespace.InvalidVocabBaseIRIError do
defexception [:message]
end

View file

@ -232,6 +232,9 @@ defmodule RDF.Turtle.Encoder do
defp description_order(%{subject: s1}, %{subject: s2}), do: to_string(s1) < to_string(s2)
defp description_statements(description, state, nesting) do
if Description.empty?(description) do
raise Graph.EmptyDescriptionError, subject: description.subject
else
with %BlankNode{} <- description.subject,
ref_count when ref_count < 2 <- State.bnode_ref_counter(state, description.subject) do
unrefed_bnode_subject_term(description, ref_count, state, nesting)
@ -239,6 +242,7 @@ defmodule RDF.Turtle.Encoder do
_ -> full_description_statements(description, state, nesting)
end
end
end
defp full_description_statements(subject, description, state, nesting) do
nesting = nesting + @indentation

View file

@ -416,15 +416,38 @@ defmodule RDF.Turtle.EncoderTest do
] .
"""
end
test "serializing a pathological graph with an empty description" do
description = RDF.description(EX.S)
graph = %Graph{Graph.new() | descriptions: %{description.subject => description}}
assert_raise Graph.EmptyDescriptionError, fn ->
Turtle.Encoder.encode!(graph)
end
end
end
test "serializing a description" do
describe "serializing a description" do
test "a non-empty description" do
description = EX.S |> EX.p(EX.O)
assert Turtle.Encoder.encode!(description) ==
description |> Graph.new() |> Turtle.Encoder.encode!()
end
test "an empty description" do
description = RDF.description(EX.S)
assert Turtle.Encoder.encode!(description) |> String.trim() ==
"""
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
"""
|> String.trim()
end
end
describe "prefixed_name/2" do
setup do
{:ok,