586 lines
16 KiB
Elixir
586 lines
16 KiB
Elixir
defmodule JSON.LD.EncoderTest do
|
|
use ExUnit.Case, async: false
|
|
|
|
doctest JSON.LD.Encoder
|
|
|
|
alias RDF.{Dataset, Graph, Description}
|
|
alias RDF.NS
|
|
|
|
import RDF.Sigils
|
|
|
|
defmodule TestNS do
|
|
use RDF.Vocabulary.Namespace
|
|
defvocab EX, base_iri: "http://example.com/", terms: [], strict: false
|
|
defvocab S, base_iri: "http://schema.org/", terms: [], strict: false
|
|
end
|
|
|
|
alias TestNS.{EX, S}
|
|
|
|
@compile {:no_warn_undefined, JSON.LD.EncoderTest.TestNS.EX}
|
|
@compile {:no_warn_undefined, JSON.LD.EncoderTest.TestNS.S}
|
|
|
|
def gets_serialized_to(input, output, opts \\ []) do
|
|
data_structs = Keyword.get(opts, :data_structs, [Dataset, Graph])
|
|
|
|
Enum.each(data_structs, fn data_struct ->
|
|
assert JSON.LD.Encoder.from_rdf!(data_struct.new(input), opts) == output
|
|
end)
|
|
end
|
|
|
|
test "pretty printing" do
|
|
dataset = Dataset.new({~I<http://a/b>, ~I<http://a/c>, ~I<http://a/d>})
|
|
|
|
assert JSON.LD.Encoder.encode!(dataset) ==
|
|
"[{\"@id\":\"http://a/b\",\"http://a/c\":[{\"@id\":\"http://a/d\"}]}]"
|
|
|
|
assert JSON.LD.Encoder.encode!(dataset, pretty: true) ==
|
|
"""
|
|
[
|
|
{
|
|
"@id": "http://a/b",
|
|
"http://a/c": [
|
|
{
|
|
"@id": "http://a/d"
|
|
}
|
|
]
|
|
}
|
|
]
|
|
"""
|
|
|> String.trim()
|
|
end
|
|
|
|
test "an empty RDF.Dataset is serialized to an JSON array string" do
|
|
assert JSON.LD.Encoder.encode!(Dataset.new()) == "[]"
|
|
end
|
|
|
|
describe "simple tests" do
|
|
test "One subject IRI object" do
|
|
{~I<http://a/b>, ~I<http://a/c>, ~I<http://a/d>}
|
|
|> gets_serialized_to(
|
|
[
|
|
%{
|
|
"@id" => "http://a/b",
|
|
"http://a/c" => [%{"@id" => "http://a/d"}]
|
|
}
|
|
],
|
|
data_structs: [Dataset, Graph, Description]
|
|
)
|
|
end
|
|
|
|
test "should generate object list" do
|
|
[{EX.b(), EX.c(), EX.d()}, {EX.b(), EX.c(), EX.e()}]
|
|
|> gets_serialized_to(
|
|
[
|
|
%{
|
|
"@id" => "http://example.com/b",
|
|
"http://example.com/c" => [
|
|
%{"@id" => "http://example.com/d"},
|
|
%{"@id" => "http://example.com/e"}
|
|
]
|
|
}
|
|
],
|
|
data_structs: [Dataset, Graph, Description]
|
|
)
|
|
end
|
|
|
|
test "should generate property list" do
|
|
[{EX.b(), EX.c(), EX.d()}, {EX.b(), EX.e(), EX.f()}]
|
|
|> gets_serialized_to(
|
|
[
|
|
%{
|
|
"@id" => "http://example.com/b",
|
|
"http://example.com/c" => [%{"@id" => "http://example.com/d"}],
|
|
"http://example.com/e" => [%{"@id" => "http://example.com/f"}]
|
|
}
|
|
],
|
|
data_structs: [Dataset, Graph, Description]
|
|
)
|
|
end
|
|
|
|
test "serializes multiple subjects" do
|
|
[
|
|
{~I<http://test-cases/0001>, NS.RDF.type(),
|
|
~I<http://www.w3.org/2006/03/test-description#TestCase>},
|
|
{~I<http://test-cases/0002>, NS.RDF.type(),
|
|
~I<http://www.w3.org/2006/03/test-description#TestCase>}
|
|
]
|
|
|> gets_serialized_to([
|
|
%{
|
|
"@id" => "http://test-cases/0001",
|
|
"@type" => ["http://www.w3.org/2006/03/test-description#TestCase"]
|
|
},
|
|
%{
|
|
"@id" => "http://test-cases/0002",
|
|
"@type" => ["http://www.w3.org/2006/03/test-description#TestCase"]
|
|
}
|
|
])
|
|
end
|
|
end
|
|
|
|
describe "literal coercion" do
|
|
test "typed literal" do
|
|
{EX.a(), EX.b(), RDF.literal("foo", datatype: EX.d())}
|
|
|> gets_serialized_to(
|
|
[
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@value" => "foo", "@type" => "http://example.com/d"}]
|
|
}
|
|
],
|
|
data_structs: [Dataset, Graph, Description]
|
|
)
|
|
end
|
|
|
|
test "integer" do
|
|
{EX.a(), EX.b(), RDF.literal(1)}
|
|
|> gets_serialized_to(
|
|
[
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@value" => 1}]
|
|
}
|
|
],
|
|
use_native_types: true
|
|
)
|
|
end
|
|
|
|
test "integer (non-native)" do
|
|
{EX.a(), EX.b(), RDF.literal(1)}
|
|
|> gets_serialized_to(
|
|
[
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [
|
|
%{"@value" => "1", "@type" => "http://www.w3.org/2001/XMLSchema#integer"}
|
|
]
|
|
}
|
|
],
|
|
use_native_types: false
|
|
)
|
|
end
|
|
|
|
test "boolean" do
|
|
{EX.a(), EX.b(), RDF.literal(true)}
|
|
|> gets_serialized_to(
|
|
[
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@value" => true}]
|
|
}
|
|
],
|
|
use_native_types: true
|
|
)
|
|
end
|
|
|
|
test "boolean (non-native)" do
|
|
{EX.a(), EX.b(), RDF.literal(true)}
|
|
|> gets_serialized_to(
|
|
[
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [
|
|
%{"@value" => "true", "@type" => "http://www.w3.org/2001/XMLSchema#boolean"}
|
|
]
|
|
}
|
|
],
|
|
use_native_types: false
|
|
)
|
|
end
|
|
|
|
@tag skip:
|
|
"TODO: Is this spec conformant or RDF.rb specific? RDF.rb doesn't use the specified RDF to Object Conversion algorithm but reuses a generalized expand_value algorithm"
|
|
test "decimal" do
|
|
{EX.a(), EX.b(), RDF.literal(1.0)}
|
|
|> gets_serialized_to(
|
|
[
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [
|
|
%{"@value" => "1.0", "@type" => "http://www.w3.org/2001/XMLSchema#decimal"}
|
|
]
|
|
}
|
|
],
|
|
use_native_types: true
|
|
)
|
|
end
|
|
|
|
test "double" do
|
|
{EX.a(), EX.b(), RDF.literal(1.0e0)}
|
|
|> gets_serialized_to(
|
|
[
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@value" => 1.0e0}]
|
|
}
|
|
],
|
|
use_native_types: true
|
|
)
|
|
end
|
|
|
|
@tag skip:
|
|
"TODO: Is this spec conformant or RDF.rb specific? RDF.rb doesn't use the specified RDF to Object Conversion algorithm but reuses a generalized expand_value algorithm"
|
|
test "double (non-native)" do
|
|
{EX.a(), EX.b(), RDF.literal(1.0e0)}
|
|
|> gets_serialized_to(
|
|
[
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [
|
|
%{"@value" => "1.0E0", "@type" => "http://www.w3.org/2001/XMLSchema#double"}
|
|
]
|
|
}
|
|
],
|
|
use_native_types: false
|
|
)
|
|
end
|
|
end
|
|
|
|
describe "datatyped (non-native) literals" do
|
|
%{
|
|
integer: 1,
|
|
unsignedInt: 1,
|
|
nonNegativeInteger: 1,
|
|
float: 1.0,
|
|
nonPositiveInteger: -1,
|
|
negativeInteger: -1
|
|
}
|
|
|> Enum.each(fn {type, _} = data ->
|
|
@tag data: data
|
|
test "#{type}", %{data: {type, value}} do
|
|
{EX.a(), EX.b(), RDF.literal(value, datatype: apply(NS.XSD, type, []))}
|
|
|> gets_serialized_to(
|
|
[
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [
|
|
%{"@value" => "#{value}", "@type" => "http://www.w3.org/2001/XMLSchema##{type}"}
|
|
]
|
|
}
|
|
],
|
|
use_native_types: false
|
|
)
|
|
end
|
|
end)
|
|
|
|
test "when useNativeTypes" do
|
|
{EX.a(), EX.b(), RDF.literal("foo", datatype: EX.customType())}
|
|
|> gets_serialized_to(
|
|
[
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [
|
|
%{"@value" => "foo", "@type" => to_string(EX.customType())}
|
|
]
|
|
}
|
|
],
|
|
use_native_types: true
|
|
)
|
|
end
|
|
end
|
|
|
|
test "encodes language literal" do
|
|
{EX.a(), EX.b(), RDF.literal("foo", language: "en-us")}
|
|
|> gets_serialized_to([
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@value" => "foo", "@language" => "en-us"}]
|
|
}
|
|
])
|
|
end
|
|
|
|
describe "blank nodes" do
|
|
test "should generate blank nodes" do
|
|
{RDF.bnode(:a), EX.a(), EX.b()}
|
|
|> gets_serialized_to(
|
|
[
|
|
%{
|
|
"@id" => "_:a",
|
|
"http://example.com/a" => [%{"@id" => "http://example.com/b"}]
|
|
}
|
|
],
|
|
data_structs: [Dataset, Graph, Description]
|
|
)
|
|
end
|
|
|
|
test "should generate blank nodes as object" do
|
|
[
|
|
{EX.a(), EX.b(), RDF.bnode(:a)},
|
|
{RDF.bnode(:a), EX.c(), EX.d()}
|
|
]
|
|
|> gets_serialized_to([
|
|
%{
|
|
"@id" => "_:a",
|
|
"http://example.com/c" => [%{"@id" => "http://example.com/d"}]
|
|
},
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@id" => "_:a"}]
|
|
}
|
|
])
|
|
end
|
|
end
|
|
|
|
describe "lists" do
|
|
%{
|
|
"literal list" => {
|
|
[
|
|
{EX.a(), EX.b(), RDF.bnode(:e1)},
|
|
{RDF.bnode(:e1), NS.RDF.first(), ~L"apple"},
|
|
{RDF.bnode(:e1), NS.RDF.rest(), RDF.bnode(:e2)},
|
|
{RDF.bnode(:e2), NS.RDF.first(), ~L"banana"},
|
|
{RDF.bnode(:e2), NS.RDF.rest(), NS.RDF.nil()}
|
|
],
|
|
[
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [
|
|
%{
|
|
"@list" => [
|
|
%{"@value" => "apple"},
|
|
%{"@value" => "banana"}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
"iri list" => {
|
|
[
|
|
{EX.a(), EX.b(), RDF.bnode(:list)},
|
|
{RDF.bnode(:list), NS.RDF.first(), EX.c()},
|
|
{RDF.bnode(:list), NS.RDF.rest(), NS.RDF.nil()}
|
|
],
|
|
[
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [
|
|
%{
|
|
"@list" => [
|
|
%{"@id" => "http://example.com/c"}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
"empty list" => {
|
|
[
|
|
{EX.a(), EX.b(), NS.RDF.nil()}
|
|
],
|
|
[
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@list" => []}]
|
|
}
|
|
]
|
|
},
|
|
"single element list" => {
|
|
[
|
|
{EX.a(), EX.b(), RDF.bnode(:list)},
|
|
{RDF.bnode(:list), NS.RDF.first(), ~L"apple"},
|
|
{RDF.bnode(:list), NS.RDF.rest(), NS.RDF.nil()}
|
|
],
|
|
[
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@list" => [%{"@value" => "apple"}]}]
|
|
}
|
|
]
|
|
},
|
|
"single element list without @type" => {
|
|
[
|
|
{EX.a(), EX.b(), RDF.bnode(:list)},
|
|
{RDF.bnode(:list), NS.RDF.first(), RDF.bnode(:a)},
|
|
{RDF.bnode(:list), NS.RDF.rest(), NS.RDF.nil()},
|
|
{RDF.bnode(:a), EX.b(), ~L"foo"}
|
|
],
|
|
[
|
|
%{
|
|
"@id" => "_:a",
|
|
"http://example.com/b" => [%{"@value" => "foo"}]
|
|
},
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@list" => [%{"@id" => "_:a"}]}]
|
|
}
|
|
]
|
|
},
|
|
"multiple graphs with shared BNode" => {
|
|
[
|
|
{EX.z(), EX.q(), RDF.bnode(:z0), EX.G},
|
|
{RDF.bnode(:z0), NS.RDF.first(), ~L"cell-A", EX.G},
|
|
{RDF.bnode(:z0), NS.RDF.rest(), RDF.bnode(:z1), EX.G},
|
|
{RDF.bnode(:z1), NS.RDF.first(), ~L"cell-B", EX.G},
|
|
{RDF.bnode(:z1), NS.RDF.rest(), NS.RDF.nil(), EX.G},
|
|
{EX.x(), EX.p(), RDF.bnode(:z1), EX.G1}
|
|
],
|
|
[
|
|
%{
|
|
"@id" => "http://www.example.com/G",
|
|
"@graph" => [
|
|
%{
|
|
"@id" => "_:z0",
|
|
"http://www.w3.org/1999/02/22-rdf-syntax-ns#first" => [%{"@value" => "cell-A"}],
|
|
"http://www.w3.org/1999/02/22-rdf-syntax-ns#rest" => [%{"@id" => "_:z1"}]
|
|
},
|
|
%{
|
|
"@id" => "_:z1",
|
|
"http://www.w3.org/1999/02/22-rdf-syntax-ns#first" => [%{"@value" => "cell-B"}],
|
|
"http://www.w3.org/1999/02/22-rdf-syntax-ns#rest" => [%{"@list" => []}]
|
|
},
|
|
%{
|
|
"@id" => "http://www.example.com/z",
|
|
"http://www.example.com/q" => [%{"@id" => "_:z0"}]
|
|
}
|
|
]
|
|
},
|
|
%{
|
|
"@id" => "http://www.example.com/G1",
|
|
"@graph" => [
|
|
%{
|
|
"@id" => "http://www.example.com/x",
|
|
"http://www.example.com/p" => [%{"@id" => "_:z1"}]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|> Enum.each(fn {title, data} ->
|
|
if title == "multiple graphs with shared BNode" do
|
|
@tag skip: "TODO: https://github.com/json-ld/json-ld.org/issues/357"
|
|
end
|
|
|
|
@tag data: data
|
|
test title, %{data: {input, output}} do
|
|
input |> gets_serialized_to(output)
|
|
end
|
|
end)
|
|
end
|
|
|
|
describe "quads" do
|
|
%{
|
|
"simple named graph" => %{
|
|
input: {EX.a(), EX.b(), EX.c(), EX.U},
|
|
output: [
|
|
%{
|
|
"@id" => "http://example.com/U",
|
|
"@graph" => [
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@id" => "http://example.com/c"}]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
"with properties" => %{
|
|
input: [
|
|
{EX.a(), EX.b(), EX.c(), EX.U},
|
|
{EX.U, EX.d(), EX.e()}
|
|
],
|
|
output: [
|
|
%{
|
|
"@id" => "http://example.com/U",
|
|
"@graph" => [
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@id" => "http://example.com/c"}]
|
|
}
|
|
],
|
|
"http://example.com/d" => [%{"@id" => "http://example.com/e"}]
|
|
}
|
|
]
|
|
},
|
|
"with lists" => %{
|
|
input: [
|
|
{EX.a(), EX.b(), RDF.bnode(:a), EX.U},
|
|
{RDF.bnode(:a), NS.RDF.first(), EX.c(), EX.U},
|
|
{RDF.bnode(:a), NS.RDF.rest(), NS.RDF.nil(), EX.U},
|
|
{EX.U, EX.d(), RDF.bnode(:b)},
|
|
{RDF.bnode(:b), NS.RDF.first(), EX.e()},
|
|
{RDF.bnode(:b), NS.RDF.rest(), NS.RDF.nil()}
|
|
],
|
|
output: [
|
|
%{
|
|
"@id" => "http://example.com/U",
|
|
"@graph" => [
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [%{"@list" => [%{"@id" => "http://example.com/c"}]}]
|
|
}
|
|
],
|
|
"http://example.com/d" => [%{"@list" => [%{"@id" => "http://example.com/e"}]}]
|
|
}
|
|
]
|
|
},
|
|
"Two Graphs with same subject and lists" => %{
|
|
input: [
|
|
{EX.a(), EX.b(), RDF.bnode(:a), EX.U},
|
|
{RDF.bnode(:a), NS.RDF.first(), EX.c(), EX.U},
|
|
{RDF.bnode(:a), NS.RDF.rest(), NS.RDF.nil(), EX.U},
|
|
{EX.a(), EX.b(), RDF.bnode(:b), EX.V},
|
|
{RDF.bnode(:b), NS.RDF.first(), EX.e(), EX.V},
|
|
{RDF.bnode(:b), NS.RDF.rest(), NS.RDF.nil(), EX.V}
|
|
],
|
|
output: [
|
|
%{
|
|
"@id" => "http://example.com/U",
|
|
"@graph" => [
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [
|
|
%{
|
|
"@list" => [%{"@id" => "http://example.com/c"}]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
},
|
|
%{
|
|
"@id" => "http://example.com/V",
|
|
"@graph" => [
|
|
%{
|
|
"@id" => "http://example.com/a",
|
|
"http://example.com/b" => [
|
|
%{
|
|
"@list" => [%{"@id" => "http://example.com/e"}]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|> Enum.each(fn {title, data} ->
|
|
@tag data: data
|
|
test title, %{data: %{input: input, output: output}} do
|
|
input |> gets_serialized_to(output, data_structs: [Dataset])
|
|
end
|
|
end)
|
|
end
|
|
|
|
describe "problems" do
|
|
%{
|
|
"xsd:boolean as value" => {
|
|
{~I<http://data.wikia.com/terms#playable>, NS.RDFS.range(), NS.XSD.boolean()},
|
|
[
|
|
%{
|
|
"@id" => "http://data.wikia.com/terms#playable",
|
|
"http://www.w3.org/2000/01/rdf-schema#range" => [
|
|
%{"@id" => "http://www.w3.org/2001/XMLSchema#boolean"}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
}
|
|
|> Enum.each(fn {title, data} ->
|
|
@tag data: data
|
|
test title, %{data: {input, output}} do
|
|
input |> gets_serialized_to(output)
|
|
end
|
|
end)
|
|
end
|
|
end
|