Limit the forms of input on RDF.Description functions

This commit is contained in:
Marcel Otto 2020-07-27 23:09:23 +02:00
parent 3f6393a191
commit e9432ef556
9 changed files with 611 additions and 573 deletions

View file

@ -233,8 +233,6 @@ defmodule RDF do
defdelegate quad(tuple), to: Quad, as: :new
defdelegate description(arg), to: Description, as: :new
defdelegate description(arg1, arg2), to: Description, as: :new
defdelegate description(arg1, arg2, arg3), to: Description, as: :new
defdelegate graph(), to: Graph, as: :new
defdelegate graph(arg), to: Graph, as: :new

View file

@ -15,294 +15,154 @@ defmodule RDF.Description do
import RDF.Statement
alias RDF.{Statement, Triple}
@type predications :: %{Statement.predicate() => %{Statement.object() => nil}}
@type statements ::
{Statement.coercible_predicate(),
Statement.coercible_object() | [Statement.coercible_predicate()]}
| Statement.t()
| predications
| t
@enforce_keys [:subject]
defstruct subject: nil, predications: %{}
@type t :: %__MODULE__{
subject: Statement.subject(),
predications: predications
}
@enforce_keys [:subject]
defstruct subject: nil, predications: %{}
@type predications :: %{Statement.predicate() => %{Statement.object() => nil}}
@type input ::
Triple.coercible_t()
| {Statement.coercible_predicate(),
Statement.coercible_object() | [Statement.coercible_object()]}
| %{
Statement.coercible_predicate() =>
Statement.coercible_object() | [Statement.coercible_object()]
}
| [
Triple.coercible_t()
| {Statement.coercible_predicate(),
Statement.coercible_object() | [Statement.coercible_object()]}
| t
]
| t
@doc """
Creates a new `RDF.Description` about the given subject with optional initial statements.
When given a list of statements, the first one must contain a subject.
Creates an empty `RDF.Description` about the given subject.
"""
@spec new(Statement.coercible_subject() | statements | [statements]) :: t
@spec new(Statement.coercible_subject() | Triple.coercible_t() | t) :: t
def new(subject)
def new({subject, predicate, object}),
do: new(subject) |> add(predicate, object)
def new([statement | more_statements]),
do: new(statement) |> add(more_statements)
def new(%__MODULE__{} = description),
do: description
def new(subject),
do: %__MODULE__{subject: coerce_subject(subject)}
def new(%__MODULE__{} = description), do: new(description.subject)
def new({subject, predicate, object}), do: new(subject) |> add({predicate, object})
def new(subject), do: %__MODULE__{subject: coerce_subject(subject)}
@doc """
Creates a new `RDF.Description` about the given subject with optional initial statements.
"""
@spec new(Statement.coercible_subject(), statements | [statements]) :: t
def new(subject, {predicate, objects}),
do: new(subject) |> add(predicate, objects)
def new(subject, statements) when is_list(statements),
do: new(subject) |> add(statements)
def new(subject, %__MODULE__{predications: predications}),
do: %__MODULE__{new(subject) | predications: predications}
def new(subject, predications = %{}),
do: new(subject) |> add(predications)
@doc """
Creates a new `RDF.Description` about the given subject with optional initial statements.
"""
@spec new(
Statement.coercible_subject() | statements | [statements],
Statement.coercible_predicate(),
Statement.coercible_object() | [Statement.coercible_object()]
) :: t
def new(%__MODULE__{} = description, predicate, objects),
do: add(description, predicate, objects)
def new(subject, predicate, objects),
do: new(subject) |> add(predicate, objects)
@doc """
Add objects to a predicate of a `RDF.Description`.
## Examples
iex> RDF.Description.add(RDF.Description.new({EX.S, EX.P1, EX.O1}), EX.P2, EX.O2)
RDF.Description.new([{EX.S, EX.P1, EX.O1}, {EX.S, EX.P2, EX.O2}])
iex> RDF.Description.add(RDF.Description.new({EX.S, EX.P, EX.O1}), EX.P, [EX.O2, EX.O3])
RDF.Description.new([{EX.S, EX.P, EX.O1}, {EX.S, EX.P, EX.O2}, {EX.S, EX.P, EX.O3}])
"""
@spec add(
t,
Statement.coercible_predicate(),
Statement.coercible_object() | [Statement.coercible_object()]
) :: t
def add(description, predicate, objects)
def add(description, predicate, objects) when is_list(objects) do
Enum.reduce(objects, description, fn object, description ->
add(description, predicate, object)
end)
end
def add(%__MODULE__{subject: subject, predications: predications}, predicate, object) do
with triple_predicate = coerce_predicate(predicate),
triple_object = coerce_object(object),
new_predications =
Map.update(predications, triple_predicate, %{triple_object => nil}, fn objects ->
Map.put_new(objects, triple_object, nil)
end) do
%__MODULE__{subject: subject, predications: new_predications}
end
end
@doc """
Adds statements to a `RDF.Description`.
Add statements to a `RDF.Description`.
Note: When the statements to be added are given as another `RDF.Description`,
the subject must not match subject of the description to which the statements
are added. As opposed to that `RDF.Data.merge/2` will produce a `RDF.Graph`
containing both descriptions.
"""
@spec add(t, statements | [statements]) :: t
def add(description, statements)
def add(description, {predicate, object}),
do: add(description, predicate, object)
def add(description = %__MODULE__{}, {subject, predicate, object}) do
if coerce_subject(subject) == description.subject,
do: add(description, predicate, object),
else: description
end
def add(description, {subject, predicate, object, _}),
do: add(description, {subject, predicate, object})
def add(description, statements) when is_list(statements) do
Enum.reduce(statements, description, fn statement, description ->
add(description, statement)
end)
end
def add(
%__MODULE__{subject: subject, predications: predications},
%__MODULE__{predications: other_predications}
) do
merged_predications =
Map.merge(predications, other_predications, fn _, objects, other_objects ->
Map.merge(objects, other_objects)
end)
%__MODULE__{subject: subject, predications: merged_predications}
end
def add(description = %__MODULE__{}, predications = %{}) do
Enum.reduce(predications, description, fn {predicate, objects}, description ->
add(description, predicate, objects)
end)
end
@doc """
Puts objects to a predicate of a `RDF.Description`, overwriting all existing objects.
## Examples
iex> RDF.Description.put(RDF.Description.new({EX.S, EX.P, EX.O1}), EX.P, EX.O2)
RDF.Description.new([{EX.S, EX.P, EX.O2}])
iex> RDF.Description.put(RDF.Description.new({EX.S, EX.P1, EX.O1}), EX.P2, EX.O2)
RDF.Description.new([{EX.S, EX.P1, EX.O1}, {EX.S, EX.P2, EX.O2}])
"""
@spec put(
t,
Statement.coercible_predicate(),
Statement.coercible_object() | [Statement.coercible_object()]
) :: t
def put(description, predicate, objects)
iex> RDF.Description.new({EX.S, EX.P1, EX.O1}) |> RDF.Description.add({EX.P2, EX.O2})
RDF.Description.new(EX.S) |> RDF.Description.add([{EX.P1, EX.O1}, {EX.P2, EX.O2}])
iex> RDF.Description.new({EX.S, EX.P, EX.O1}) |> RDF.Description.add({EX.P, [EX.O2, EX.O3]})
RDF.Description.new(EX.S) |> RDF.Description.add([{EX.P, EX.O1}, {EX.P, EX.O2}, {EX.P, EX.O3}])
def put(%__MODULE__{subject: subject, predications: predications}, predicate, objects)
when is_list(objects) do
with triple_predicate = coerce_predicate(predicate),
triple_objects =
Enum.reduce(objects, %{}, fn object, acc ->
Map.put_new(acc, coerce_object(object), nil)
end),
do: %__MODULE__{
subject: subject,
predications: Map.put(predications, triple_predicate, triple_objects)
"""
@spec add(t, input, keyword) :: t
def add(description, input, opts \\ [])
# This implementation is actually unnecessary as the implementation with the is_map clause
# would work perfectly fine with RDF.Descriptions Enumerable implementation.
# It exists only for performance reasons, since this version is roughly twice as fast.
def add(%__MODULE__{} = description, %__MODULE__{} = input_description, _opts) do
%__MODULE__{
description
| predications:
Map.merge(
description.predications,
input_description.predications,
fn _predicate, objects, new_objects ->
Map.merge(objects, new_objects)
end
)
}
end
def put(%__MODULE__{} = description, predicate, object),
do: put(description, predicate, [object])
def add(description, predications, opts)
when is_map(predications) or is_list(predications) do
Enum.reduce(predications, description, fn
predications, description -> add(description, predications, opts)
end)
end
def add(%__MODULE__{} = description, {subject, predicate, objects}, opts) do
if coerce_subject(subject) == description.subject do
add(description, {predicate, objects}, opts)
else
description
end
end
def add(%__MODULE__{} = description, {predicate, objects}, _opts) do
normalized_objects =
objects
|> List.wrap()
|> Map.new(fn object -> {coerce_object(object), nil} end)
if Enum.empty?(normalized_objects) do
description
else
%__MODULE__{
description
| predications:
Map.update(
description.predications,
coerce_predicate(predicate),
normalized_objects,
fn objects ->
Map.merge(objects, normalized_objects)
end
)
}
end
end
@doc """
Adds statements to a `RDF.Description` and overwrites all existing statements with already used predicates.
Note: As it is a destructive function this function is more strict in its handling of
`RDF.Description`s than `add/3`. The subject of a `RDF.Description` to be put must
match.
## Examples
iex> RDF.Description.put(RDF.Description.new({EX.S, EX.P, EX.O1}), {EX.P, EX.O2})
RDF.Description.new([{EX.S, EX.P, EX.O2}])
iex> RDF.Description.new({EX.S, EX.P1, EX.O1}) |>
...> RDF.Description.put([{EX.P2, EX.O2}, {EX.S, EX.P2, EX.O3}, {EX.P1, EX.O4}])
RDF.Description.new([{EX.S, EX.P1, EX.O4}, {EX.S, EX.P2, EX.O2}, {EX.S, EX.P2, EX.O3}])
iex> RDF.Description.new({EX.S, EX.P, EX.O1}) |>
...> RDF.Description.put(RDF.Description.new(EX.S, EX.P, [EX.O1, EX.O2]))
RDF.Description.new([{EX.S, EX.P, EX.O1}, {EX.S, EX.P, EX.O2}])
iex> RDF.Description.new([{EX.S, EX.P1, EX.O1}, {EX.S, EX.P2, EX.O2}]) |>
...> RDF.Description.put(%{EX.P2 => [EX.O3, EX.O4]})
RDF.Description.new([{EX.S, EX.P1, EX.O1}, {EX.S, EX.P2, EX.O3}, {EX.S, EX.P2, EX.O4}])
RDF.Description.new({EX.S, EX.P, EX.O2})
"""
@spec put(t, statements | [statements]) :: t
def put(description, statements)
def put(%__MODULE__{} = description, {predicate, object}),
do: put(description, predicate, object)
def put(%__MODULE__{} = description, {subject, predicate, object}) do
if coerce_subject(subject) == description.subject,
do: put(description, predicate, object),
else: description
end
def put(description, {subject, predicate, object, _}),
do: put(description, {subject, predicate, object})
def put(%__MODULE__{subject: subject} = description, statements) when is_list(statements) do
statements
|> Stream.map(fn
{p, o} ->
{coerce_predicate(p), o}
{^subject, p, o} ->
{coerce_predicate(p), o}
{s, p, o} ->
if coerce_subject(s) == subject,
do: {coerce_predicate(p), o}
bad ->
raise ArgumentError, "#{inspect(bad)} is not a valid statement"
end)
# filter nil values
|> Stream.filter(& &1)
|> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
|> Enum.reduce(description, fn {predicate, objects}, description ->
put(description, predicate, objects)
end)
end
@spec put(t, input, keyword) :: t
def put(description, input, opts \\ [])
def put(
%__MODULE__{subject: subject, predications: predications},
%__MODULE__{predications: other_predications}
%__MODULE__{subject: subject} = description,
%__MODULE__{subject: subject} = input,
_opts
) do
merged_predications =
Map.merge(predications, other_predications, fn _, _, other_objects -> other_objects end)
%__MODULE__{subject: subject, predications: merged_predications}
end
def put(description = %__MODULE__{}, predications = %{}) do
Enum.reduce(predications, description, fn {predicate, objects}, description ->
put(description, predicate, objects)
end)
end
@doc """
Deletes statements from a `RDF.Description`.
"""
@spec delete(
t,
Statement.coercible_predicate(),
Statement.coercible_object() | [Statement.coercible_object()]
) :: t
def delete(description, predicate, objects)
def delete(description, predicate, objects) when is_list(objects) do
Enum.reduce(objects, description, fn object, description ->
delete(description, predicate, object)
end)
end
def delete(%__MODULE__{subject: subject, predications: predications} = descr, predicate, object) do
with triple_predicate = coerce_predicate(predicate),
triple_object = coerce_object(object) do
if (objects = predications[triple_predicate]) && Map.has_key?(objects, triple_object) do
%__MODULE__{
subject: subject,
predications:
if map_size(objects) == 1 do
Map.delete(predications, triple_predicate)
else
Map.update!(predications, triple_predicate, fn objects ->
Map.delete(objects, triple_object)
end)
description
| predications:
Enum.reduce(
input.predications,
description.predications,
fn {predicate, objects}, predications ->
Map.put(predications, predicate, objects)
end
)
}
else
descr
end
end
def put(%__MODULE__{} = description, %__MODULE__{}, _opts), do: description
def put(%__MODULE__{} = description, input, opts) do
put(description, description.subject |> new() |> add(input), opts)
end
@doc """
@ -313,37 +173,76 @@ defmodule RDF.Description do
are deleted. If you want to delete only a matching description subject, you can
use `RDF.Data.delete/2`.
"""
@spec delete(t, statements | [statements]) :: t
def delete(description, statements)
@spec delete(t, input, keyword) :: t
def delete(description, input, opts \\ [])
def delete(desc = %__MODULE__{}, {predicate, object}),
do: delete(desc, predicate, object)
# This implementation is actually unnecessary as the implementation with the is_map clause
# would work perfectly fine with RDF.Descriptions Enumerable implementation.
# It exists only for performance reasons.
def delete(%__MODULE__{} = description, %__MODULE__{} = input_description, _opts) do
predications = description.predications
def delete(description = %__MODULE__{}, {subject, predicate, object}) do
if coerce_subject(subject) == description.subject,
do: delete(description, predicate, object),
else: description
%__MODULE__{
description
| predications:
Enum.reduce(
input_description.predications,
predications,
fn {predicate, objects}, predications ->
if current_objects = Map.get(description.predications, predicate) do
rest = Map.drop(current_objects, Map.keys(objects))
if Enum.empty?(rest) do
Map.delete(predications, predicate)
else
Map.put(predications, predicate, rest)
end
else
predications
end
end
)
}
end
def delete(description, {subject, predicate, object, _}),
do: delete(description, {subject, predicate, object})
def delete(description, statements) when is_list(statements) do
Enum.reduce(statements, description, fn statement, description ->
delete(description, statement)
def delete(description, predications, opts)
when is_map(predications) or is_list(predications) do
Enum.reduce(predications, description, fn
predications, description -> delete(description, predications, opts)
end)
end
def delete(description = %__MODULE__{}, other_description = %__MODULE__{}) do
Enum.reduce(other_description, description, fn {_, predicate, object}, description ->
delete(description, predicate, object)
end)
def delete(%__MODULE__{} = description, {subject, predicate, objects}, opts) do
if coerce_subject(subject) == description.subject do
delete(description, {predicate, objects}, opts)
else
description
end
end
def delete(description = %__MODULE__{}, predications = %{}) do
Enum.reduce(predications, description, fn {predicate, objects}, description ->
delete(description, predicate, objects)
end)
def delete(%__MODULE__{} = description, {predicate, objects}, _opts) do
predicate = coerce_predicate(predicate)
if current_objects = Map.get(description.predications, predicate) do
normalized_objects =
objects
|> List.wrap()
|> Enum.map(&coerce_object/1)
rest = Map.drop(current_objects, normalized_objects)
%__MODULE__{
description
| predications:
if Enum.empty?(rest) do
Map.delete(description.predications, predicate)
else
Map.put(description.predications, predicate, rest)
end
}
else
description
end
end
@doc """
@ -360,9 +259,10 @@ defmodule RDF.Description do
end
def delete_predicates(%__MODULE__{subject: subject, predications: predications}, property) do
with property = coerce_predicate(property) do
%__MODULE__{subject: subject, predications: Map.delete(predications, property)}
end
%__MODULE__{
subject: subject,
predications: Map.delete(predications, coerce_predicate(property))
}
end
@doc """
@ -372,10 +272,10 @@ defmodule RDF.Description do
## Examples
iex> RDF.Description.fetch(RDF.Description.new({EX.S, EX.p, EX.O}), EX.p)
iex> RDF.Description.new({EX.S, EX.p, EX.O}) |> RDF.Description.fetch(EX.p)
{:ok, [RDF.iri(EX.O)]}
iex> RDF.Description.fetch(RDF.Description.new([{EX.S, EX.P, EX.O1},
...> {EX.S, EX.P, EX.O2}]), EX.P)
iex> RDF.Description.new(EX.S) |> RDF.Description.add([{EX.P, EX.O1}, {EX.P, EX.O2}]) |>
...> RDF.Description.fetch(EX.P)
{:ok, [RDF.iri(EX.O1), RDF.iri(EX.O2)]}
iex> RDF.Description.fetch(RDF.Description.new(EX.S), EX.foo)
:error
@ -445,7 +345,7 @@ defmodule RDF.Description do
iex> RDF.Description.new({EX.S, EX.p, EX.O}) |>
...> RDF.Description.update(EX.p, fn objects -> [EX.O2 | objects] end)
RDF.Description.new([{EX.S, EX.p, EX.O}, {EX.S, EX.p, EX.O2}])
RDF.Description.new(EX.S) |> RDF.Description.add([{EX.p, EX.O}, {EX.p, EX.O2}])
iex> RDF.Description.new(EX.S) |>
...> RDF.Description.update(EX.p, EX.O, fn _ -> EX.O2 end)
RDF.Description.new({EX.S, EX.p, EX.O})
@ -463,7 +363,7 @@ defmodule RDF.Description do
case get(description, predicate) do
nil ->
if initial do
put(description, predicate, initial)
put(description, {predicate, initial})
else
description
end
@ -474,7 +374,7 @@ defmodule RDF.Description do
|> List.wrap()
|> case do
[] -> delete_predicates(description, predicate)
objects -> put(description, predicate, objects)
objects -> put(description, {predicate, objects})
end
end
end
@ -500,7 +400,8 @@ defmodule RDF.Description do
...> {current_objects, EX.NEW}
...> end)
{[RDF.iri(EX.O)], RDF.Description.new({EX.S, EX.P, EX.NEW})}
iex> RDF.Description.new([{EX.S, EX.P1, EX.O1}, {EX.S, EX.P2, EX.O2}]) |>
iex> RDF.Graph.new([{EX.S, EX.P1, EX.O1}, {EX.S, EX.P2, EX.O2}]) |>
...> RDF.Graph.description(EX.S) |>
...> RDF.Description.get_and_update(EX.P1, fn _ -> :pop end)
{[RDF.iri(EX.O1)], RDF.Description.new({EX.S, EX.P2, EX.O2})}
"""
@ -511,16 +412,16 @@ defmodule RDF.Description do
([Statement.Object] -> {[Statement.Object], t} | :pop)
) :: {[Statement.Object], t}
def get_and_update(description = %__MODULE__{}, predicate, fun) do
with triple_predicate = coerce_predicate(predicate) do
triple_predicate = coerce_predicate(predicate)
case fun.(get(description, triple_predicate)) do
{objects_to_return, new_objects} ->
{objects_to_return, put(description, triple_predicate, new_objects)}
{objects_to_return, put(description, {triple_predicate, new_objects})}
:pop ->
pop(description, triple_predicate)
end
end
end
@doc """
Pops an arbitrary triple from a `RDF.Description`.
@ -572,8 +473,8 @@ defmodule RDF.Description do
## Examples
iex> RDF.Description.new([
...> {EX.S1, EX.p1, EX.O1},
iex> RDF.Description.new(EX.S1) |> RDF.Description.add([
...> {EX.p1, EX.O1},
...> {EX.p2, EX.O2},
...> {EX.p2, EX.O3}]) |>
...> RDF.Description.predicates
@ -590,8 +491,8 @@ defmodule RDF.Description do
## Examples
iex> RDF.Description.new([
...> {EX.S1, EX.p1, EX.O1},
iex> RDF.Description.new(EX.S1) |> RDF.Description.add([
...> {EX.p1, EX.O1},
...> {EX.p2, EX.O2},
...> {EX.p3, EX.O2},
...> {EX.p4, RDF.bnode(:bnode)},
@ -622,8 +523,8 @@ defmodule RDF.Description do
## Examples
iex> RDF.Description.new([
...> {EX.S1, EX.p1, EX.O1},
iex> RDF.Description.new(EX.S1) |> RDF.Description.add([
...> {EX.p1, EX.O1},
...> {EX.p2, EX.O2},
...> {EX.p1, EX.O2},
...> {EX.p2, RDF.bnode(:bnode)},
@ -657,46 +558,42 @@ defmodule RDF.Description do
@doc """
Checks if the given statement exists within a `RDF.Description`.
"""
@spec include?(t, statements) :: boolean
def include?(description, statement)
@spec include?(t, input) :: boolean
def include?(description, input)
def include?(
%__MODULE__{predications: predications},
{predicate, object}
) do
with triple_predicate = coerce_predicate(predicate),
triple_object = coerce_object(object) do
predications
|> Map.get(triple_predicate, %{})
|> Map.has_key?(triple_object)
end
def include?(description, predications) when is_map(predications) or is_list(predications) do
Enum.all?(predications, fn predication -> include?(description, predication) end)
end
def include?(
desc = %__MODULE__{subject: desc_subject},
{subject, predicate, object}
) do
coerce_subject(subject) == desc_subject &&
include?(desc, {predicate, object})
def include?(%__MODULE__{} = description, {subject, predicate, objects}) do
coerce_subject(subject) == description.subject &&
include?(description, {predicate, objects})
end
def include?(%__MODULE__{}, _), do: false
def include?(%__MODULE__{} = description, {predicate, objects}) do
if existing_objects = description.predications[coerce_predicate(predicate)] do
objects
|> List.wrap()
|> Enum.map(&coerce_object/1)
|> Enum.all?(fn object -> Map.has_key?(existing_objects, object) end)
else
false
end
end
@doc """
Checks if a `RDF.Description` has the given resource as subject.
## Examples
iex> RDF.Description.new(EX.S1, EX.p1, EX.O1) |> RDF.Description.describes?(EX.S1)
iex> RDF.Description.new({EX.S1, EX.p1, EX.O1}) |> RDF.Description.describes?(EX.S1)
true
iex> RDF.Description.new(EX.S1, EX.p1, EX.O1) |> RDF.Description.describes?(EX.S2)
iex> RDF.Description.new({EX.S1, EX.p1, EX.O1}) |> RDF.Description.describes?(EX.S2)
false
"""
@spec describes?(t, Statement.subject()) :: boolean
def describes?(%__MODULE__{subject: subject}, other_subject) do
with other_subject = coerce_subject(other_subject) do
subject == other_subject
end
subject == coerce_subject(other_subject)
end
@doc """

View file

@ -47,7 +47,7 @@ defmodule RDF.Diff do
## Examples
iex> RDF.Diff.diff(
...> RDF.description(EX.S1, EX.p1, [EX.O1, EX.O2]),
...> RDF.description({EX.S1, EX.p1, [EX.O1, EX.O2]}),
...> RDF.graph([
...> {EX.S1, EX.p1, [EX.O2, EX.O3]},
...> {EX.S2, EX.p2, EX.O4}
@ -81,7 +81,7 @@ defmodule RDF.Diff do
nil ->
{
additions,
Description.add(deletions, property, original_objects)
Description.add(deletions, {property, original_objects})
}
new_objects ->
@ -96,8 +96,8 @@ defmodule RDF.Diff do
end)
{
Description.delete(additions, property, unchanged_objects),
Description.add(deletions, property, deleted_objects)
Description.delete(additions, {property, unchanged_objects}),
Description.add(deletions, {property, deleted_objects})
}
end
end

View file

@ -202,9 +202,14 @@ defmodule RDF.Graph do
%__MODULE__{
graph
| descriptions:
Map.update(descriptions, subject, Description.new(statements), fn description ->
Map.update(
descriptions,
subject,
Description.new(subject) |> Description.add(statements),
fn description ->
Description.add(description, statements)
end)
end
)
}
end
@ -277,7 +282,7 @@ defmodule RDF.Graph do
Map.update(
descriptions,
subject,
Description.new(subject, predications),
Description.new(subject) |> Description.add(predications),
fn current ->
Description.put(current, predications)
end
@ -293,9 +298,14 @@ defmodule RDF.Graph do
%__MODULE__{
graph
| descriptions:
Map.update(descriptions, subject, Description.new(statements), fn current ->
Map.update(
descriptions,
subject,
Description.new(subject) |> Description.add(statements),
fn current ->
Description.put(current, statements)
end)
end
)
}
end
@ -420,15 +430,15 @@ defmodule RDF.Graph do
iex> RDF.Graph.new({EX.S, EX.p, EX.O}) |>
...> RDF.Graph.update(EX.S,
...> fn description -> Description.add(description, EX.p, EX.O2) end)
...> fn description -> Description.add(description, {EX.p, EX.O2}) end)
RDF.Graph.new([{EX.S, EX.p, EX.O}, {EX.S, EX.p, EX.O2}])
iex> RDF.Graph.new({EX.S, EX.p, EX.O}) |>
...> RDF.Graph.update(EX.S,
...> fn _ -> Description.new(EX.S2, EX.p2, EX.O2) end)
...> fn _ -> Description.new({EX.S2, EX.p2, EX.O2}) end)
RDF.Graph.new([{EX.S, EX.p2, EX.O2}])
iex> RDF.Graph.new() |>
...> RDF.Graph.update(EX.S, Description.new({EX.S, EX.p, EX.O}),
...> fn description -> Description.add(description, EX.p, EX.O2) end)
...> fn description -> Description.add(description, {EX.p, EX.O2}) end)
RDF.Graph.new([{EX.S, EX.p, EX.O}])
"""
@ -444,7 +454,7 @@ defmodule RDF.Graph do
case get(graph, subject) do
nil ->
if initial do
add(graph, Description.new(subject, initial))
add(graph, Description.new(subject) |> Description.add(initial))
else
graph
end
@ -459,7 +469,7 @@ defmodule RDF.Graph do
new_description ->
graph
|> delete_subjects(subject)
|> add(Description.new(subject, new_description))
|> add(Description.new(subject) |> Description.add(new_description))
end
end
end
@ -566,7 +576,7 @@ defmodule RDF.Graph do
...> RDF.Graph.get_and_update(EX.S, fn current_description ->
...> {current_description, {EX.P, EX.NEW}}
...> end)
{RDF.Description.new(EX.S, EX.P, EX.O), RDF.Graph.new(EX.S, EX.P, EX.NEW)}
{RDF.Description.new({EX.S, EX.P, EX.O}), RDF.Graph.new({EX.S, EX.P, EX.NEW})}
"""
@impl Access

View file

@ -9,6 +9,7 @@ defmodule RDF.Vocabulary.Namespace do
the `RDF.NS` module.
"""
alias RDF.Description
alias RDF.Utils.ResourceClassifier
@vocabs_dir "priv/vocabs"
@ -123,10 +124,20 @@ defmodule RDF.Vocabulary.Namespace do
end
def unquote(:"$handle_undefined_function")(term, [subject | objects]) do
objects =
case objects do
[objects] when is_list(objects) -> objects
_ -> objects
end
if MapSet.member?(@ignored_terms, term) do
raise UndefinedFunctionError
else
RDF.Description.new(subject, term_to_iri(@base_iri, term), objects)
case subject do
%Description{} -> subject
_ -> Description.new(subject)
end
|> Description.add({term_to_iri(@base_iri, term), objects})
end
end
end
@ -153,8 +164,15 @@ defmodule RDF.Vocabulary.Namespace do
def unquote(term)(), do: unquote(Macro.escape(iri))
@doc "`RDF.Description` builder for `#{unquote(term)}/0`"
def unquote(term)(subject, object)
def unquote(term)(%Description{} = subject, object) do
Description.add(subject, {unquote(Macro.escape(iri)), object})
end
def unquote(term)(subject, object) do
RDF.Description.new(subject, unquote(Macro.escape(iri)), object)
Description.new(subject)
|> Description.add({unquote(Macro.escape(iri)), object})
end
# Is there a better way to support multiple objects via arguments?
@ -224,7 +242,7 @@ defmodule RDF.Vocabulary.Namespace do
end)
end
defp raw_rdf_data(%RDF.Description{} = rdf_data), do: rdf_data
defp raw_rdf_data(%Description{} = rdf_data), do: rdf_data
defp raw_rdf_data(%RDF.Graph{} = rdf_data), do: rdf_data
defp raw_rdf_data(%RDF.Dataset{} = rdf_data), do: rdf_data

View file

@ -325,7 +325,8 @@ defmodule RDF.DatasetTest do
ds =
Dataset.add(
dataset(),
Description.new(EX.Subject1, [
Description.new(EX.Subject1)
|> Description.add([
{EX.predicate1(), EX.Object1},
{EX.predicate2(), EX.Object2}
])
@ -339,7 +340,8 @@ defmodule RDF.DatasetTest do
ds =
Dataset.add(
dataset(),
Description.new(EX.Subject1, [
Description.new(EX.Subject1)
|> RDF.Description.add([
{EX.predicate1(), EX.Object1},
{EX.predicate2(), EX.Object2}
]),
@ -707,7 +709,10 @@ defmodule RDF.DatasetTest do
test "a Description" do
ds =
Dataset.new([{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}, {EX.S1, EX.P3, EX.O3}])
|> RDF.Dataset.put(Description.new(EX.S1, [{EX.P3, EX.O4}, {EX.P2, bnode(:foo)}]))
|> RDF.Dataset.put(
Description.new(EX.S1)
|> Description.add([{EX.P3, EX.O4}, {EX.P2, bnode(:foo)}])
)
assert Dataset.statement_count(ds) == 4
assert dataset_includes_statement?(ds, {EX.S1, EX.P1, EX.O1})
@ -823,15 +828,15 @@ defmodule RDF.DatasetTest do
test "multiple statements with a Description",
%{dataset1: dataset1, dataset2: dataset2} do
assert Dataset.delete(dataset1, Description.new(EX.S1, EX.p1(), EX.O1)) == Dataset.new()
assert Dataset.delete(dataset1, Description.new({EX.S1, EX.p1(), EX.O1})) == Dataset.new()
assert Dataset.delete(dataset1, Description.new(EX.S1, EX.p1(), EX.O1), EX.Graph) ==
assert Dataset.delete(dataset1, Description.new({EX.S1, EX.p1(), EX.O1}), EX.Graph) ==
dataset1
assert Dataset.delete(dataset2, Description.new(EX.S2, EX.p2(), EX.O2), EX.Graph) ==
assert Dataset.delete(dataset2, Description.new({EX.S2, EX.p2(), EX.O2}), EX.Graph) ==
dataset1
assert Dataset.delete(dataset2, Description.new(EX.S1, EX.p1(), EX.O1)) ==
assert Dataset.delete(dataset2, Description.new({EX.S1, EX.p1(), EX.O1})) ==
Dataset.new({EX.S2, EX.p2(), EX.O2, EX.Graph})
end

View file

@ -26,128 +26,61 @@ defmodule RDF.DescriptionTest do
assert description_of_subject(Description.new(bnode(:foo)), bnode(:foo))
end
test "with a single initial triple" do
desc = Description.new({EX.Subject, EX.predicate(), EX.Object})
assert description_of_subject(desc, iri(EX.Subject))
assert description_includes_predication(desc, {EX.predicate(), iri(EX.Object)})
desc = Description.new(EX.Subject, EX.predicate(), 42)
assert description_of_subject(desc, iri(EX.Subject))
assert description_includes_predication(desc, {EX.predicate(), literal(42)})
end
test "with a list of initial triples" do
desc =
Description.new([
{EX.Subject, EX.predicate1(), EX.Object1},
{EX.Subject, EX.predicate2(), EX.Object2}
])
assert description_of_subject(desc, iri(EX.Subject))
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object2)})
desc = Description.new(EX.Subject, EX.predicate(), [EX.Object, bnode(:foo), "bar"])
assert description_of_subject(desc, iri(EX.Subject))
assert description_includes_predication(desc, {EX.predicate(), iri(EX.Object)})
assert description_includes_predication(desc, {EX.predicate(), bnode(:foo)})
assert description_includes_predication(desc, {EX.predicate(), literal("bar")})
end
test "from another description" do
desc1 = Description.new({EX.Other, EX.predicate(), EX.Object})
desc2 = Description.new(EX.Subject, desc1)
assert description_of_subject(desc2, iri(EX.Subject))
assert description_includes_predication(desc2, {EX.predicate(), iri(EX.Object)})
end
test "from a map with coercible RDF term" do
desc = Description.new(EX.Subject, %{EX.Predicate => EX.Object})
assert description_of_subject(desc, iri(EX.Subject))
assert description_includes_predication(desc, {iri(EX.Predicate), iri(EX.Object)})
end
test "with another description as subject, it performs and add " do
desc = Description.new({EX.S, EX.p(), EX.O})
assert Description.new(desc, EX.p2(), EX.O2) ==
Description.add(desc, EX.p2(), EX.O2)
assert Description.new(desc, EX.p(), [EX.O1, EX.O2]) ==
Description.add(desc, EX.p(), [EX.O1, EX.O2])
test "with another description" do
existing_description = description({EX.Subject, EX.predicate(), EX.Object})
new_description = Description.new(existing_description)
assert description_of_subject(new_description, iri(EX.Subject))
refute description_includes_predication(new_description, {EX.predicate(), iri(EX.Object)})
end
end
describe "add" do
test "a predicate-object-pair of proper RDF terms" do
assert Description.add(description(), EX.predicate(), iri(EX.Object))
describe "add/3" do
test "with a triple" do
assert Description.add(description(), {iri(EX.Subject), EX.predicate(), iri(EX.Object)})
|> description_includes_predication({EX.predicate(), iri(EX.Object)})
assert Description.add(description(), {iri(EX.Subject), EX.predicate(), bnode(:foo)})
|> description_includes_predication({EX.predicate(), bnode(:foo)})
assert Description.add(description(), {iri(EX.Subject), EX.predicate(), literal(42)})
|> description_includes_predication({EX.predicate(), literal(42)})
end
test "with a predicate-object tuple" do
assert Description.add(description(), {EX.predicate(), iri(EX.Object)})
|> description_includes_predication({EX.predicate(), iri(EX.Object)})
end
test "a predicate-object-pair of coercible RDF terms" do
assert Description.add(description(), "http://example.com/predicate", iri(EX.Object))
|> description_includes_predication({EX.predicate(), iri(EX.Object)})
assert Description.add(
description(),
{"http://example.com/predicate", 42}
)
|> description_includes_predication({EX.predicate(), literal(42)})
assert Description.add(
description(),
{"http://example.com/predicate", true}
)
|> description_includes_predication({EX.predicate(), literal(true)})
assert Description.add(
description(),
{"http://example.com/predicate", bnode(:foo)}
)
|> description_includes_predication({EX.predicate(), bnode(:foo)})
test "with a predicate-object tuple and a list of objects" do
desc = Description.add(description(), {EX.p(), [iri(EX.O1), iri(EX.O2)]})
assert description_includes_predication(desc, {EX.p(), iri(EX.O1)})
assert description_includes_predication(desc, {EX.p(), iri(EX.O2)})
end
test "a proper triple" do
assert Description.add(
description(),
{iri(EX.Subject), EX.predicate(), iri(EX.Object)}
)
|> description_includes_predication({EX.predicate(), iri(EX.Object)})
assert Description.add(
description(),
{iri(EX.Subject), EX.predicate(), literal(42)}
)
|> description_includes_predication({EX.predicate(), literal(42)})
assert Description.add(
description(),
{iri(EX.Subject), EX.predicate(), bnode(:foo)}
)
|> description_includes_predication({EX.predicate(), bnode(:foo)})
end
test "add ignores triples not about the subject of the Description struct" do
assert empty_description(
Description.add(description(), {EX.Other, EX.predicate(), iri(EX.Object)})
)
end
test "a list of predicate-object-pairs" do
test "with a list of predicate-object tuples" do
desc =
Description.add(
description(),
[{EX.predicate(), EX.Object1}, {EX.predicate(), EX.Object2}]
)
Description.add(description(), [
{EX.predicate(), EX.Object1},
{EX.predicate(), EX.Object2}
])
assert description_includes_predication(desc, {EX.predicate(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate(), iri(EX.Object2)})
desc =
Description.add(description(), [
{EX.p1(), EX.O1},
{EX.p2(), [EX.O2, ~L"foo", "bar", 42]}
])
assert description_includes_predication(desc, {EX.p1(), iri(EX.O1)})
assert description_includes_predication(desc, {EX.p2(), iri(EX.O2)})
assert description_includes_predication(desc, {EX.p2(), ~L"foo"})
assert description_includes_predication(desc, {EX.p2(), ~L"bar"})
assert description_includes_predication(desc, {EX.p2(), RDF.literal(42)})
end
test "a list of triples" do
test "with a list of triples" do
desc =
Description.add(description(), [
{EX.Subject, EX.predicate1(), EX.Object1},
@ -156,6 +89,16 @@ defmodule RDF.DescriptionTest do
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object2)})
desc =
Description.add(description(), [
{EX.Subject, EX.predicate1(), EX.Object1},
{EX.Subject, EX.predicate2(), [EX.Object2, EX.Object3]}
])
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object2)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object3)})
end
test "a list of mixed triples and predicate-object-pairs" do
@ -172,24 +115,7 @@ defmodule RDF.DescriptionTest do
refute description_includes_predication(desc, {EX.predicate(), iri(EX.Object3)})
end
test "another description" do
desc =
description([{EX.predicate1(), EX.Object1}, {EX.predicate2(), EX.Object2}])
|> Description.add(Description.new({EX.Other, EX.predicate3(), EX.Object3}))
assert description_of_subject(desc, iri(EX.Subject))
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object2)})
assert description_includes_predication(desc, {EX.predicate3(), iri(EX.Object3)})
desc = Description.add(desc, Description.new({EX.Other, EX.predicate1(), EX.Object4}))
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object2)})
assert description_includes_predication(desc, {EX.predicate3(), iri(EX.Object3)})
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object4)})
end
test "a map of predications with coercible RDF terms" do
test "with a description map with coercible RDF terms" do
desc =
description([{EX.predicate1(), EX.Object1}, {EX.predicate2(), EX.Object2}])
|> Description.add(%{EX.predicate3() => EX.Object3})
@ -224,6 +150,38 @@ defmodule RDF.DescriptionTest do
end
end
test "with an empty map" do
assert Description.add(description(), %{}) == description()
end
test "with empty object lists" do
assert Description.add(description(), {EX.p(), []}) == description()
assert Description.add(description(), %{EX.p() => []}) == description()
end
test "with another description" do
desc =
description([{EX.predicate1(), EX.Object1}, {EX.predicate2(), EX.Object2}])
|> Description.add(Description.new({EX.Other, EX.predicate3(), EX.Object3}))
assert description_of_subject(desc, iri(EX.Subject))
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object2)})
assert description_includes_predication(desc, {EX.predicate3(), iri(EX.Object3)})
desc = Description.add(desc, Description.new({EX.Other, EX.predicate1(), EX.Object4}))
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object2)})
assert description_includes_predication(desc, {EX.predicate3(), iri(EX.Object3)})
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object4)})
end
test "triples with another subject are ignored" do
assert empty_description(
Description.add(description(), {EX.Other, EX.predicate(), iri(EX.Object)})
)
end
test "duplicates are ignored" do
desc = Description.add(description(), {EX.predicate(), EX.Object})
assert Description.add(desc, {EX.predicate(), EX.Object}) == desc
@ -233,6 +191,18 @@ defmodule RDF.DescriptionTest do
assert Description.add(desc, {EX.predicate(), literal(42)}) == desc
end
test "coercion" do
assert Description.add(description(), {EX.Subject, EX.P, EX.O})
|> description_includes_predication({iri(EX.P), iri(EX.O)})
assert Description.add(description(), {"http://example.com/predicate", EX.Object})
|> description_includes_predication({EX.predicate(), iri(EX.Object)})
desc = Description.add(description(), {"http://example.com/predicate", [42, true]})
assert description_includes_predication(desc, {EX.predicate(), literal(42)})
assert description_includes_predication(desc, {EX.predicate(), literal(true)})
end
test "non-coercible Triple elements are causing an error" do
assert_raise RDF.IRI.InvalidError, fn ->
Description.add(description(), {"not a IRI", iri(EX.Object)})
@ -244,33 +214,116 @@ defmodule RDF.DescriptionTest do
end
end
describe "put/3" do
test "with a triple" do
assert Description.put(description(), {iri(EX.Subject), EX.predicate(), iri(EX.Object)})
|> description_includes_predication({EX.predicate(), iri(EX.Object)})
end
test "with a predicate-object tuple" do
desc = Description.put(description(), {EX.p(), [iri(EX.O1), iri(EX.O2)]})
assert description_includes_predication(desc, {EX.p(), iri(EX.O1)})
assert description_includes_predication(desc, {EX.p(), iri(EX.O2)})
end
test "with a list of predicate-object tuples" do
desc =
Description.put(description(), [
{EX.p1(), EX.O1},
{EX.p2(), [EX.O2]},
{EX.p2(), [~L"foo", "bar", 42]}
])
assert description_includes_predication(desc, {EX.p1(), iri(EX.O1)})
assert description_includes_predication(desc, {EX.p2(), iri(EX.O2)})
assert description_includes_predication(desc, {EX.p2(), ~L"foo"})
assert description_includes_predication(desc, {EX.p2(), ~L"bar"})
assert description_includes_predication(desc, {EX.p2(), RDF.literal(42)})
end
test "with a list of triples" do
desc =
Description.put(description(), [
{EX.Subject, EX.predicate1(), EX.Object1},
{EX.Subject, EX.predicate2(), [EX.Object2, EX.Object3]},
{EX.Subject, EX.predicate2(), [EX.Object4]}
])
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object2)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object3)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object4)})
end
test "with a description map with coercible RDF terms" do
desc =
description([{EX.predicate1(), EX.Object1}, {EX.predicate2(), EX.Object2}])
|> Description.put(%{
EX.predicate2() => [EX.Object3, 42],
EX.predicate3() => bnode(:foo)
})
assert Description.count(desc) == 4
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object1)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object3)})
assert description_includes_predication(desc, {EX.predicate2(), literal(42)})
assert description_includes_predication(desc, {EX.predicate3(), bnode(:foo)})
refute description_includes_predication(desc, {EX.predicate2(), iri(EX.Object2)})
end
test "with an empty map" do
desc = description([{EX.predicate(), EX.Object}])
assert Description.put(desc, %{}) == desc
end
test "with a description on the same subject" do
desc =
description([{EX.predicate1(), EX.Object1}, {EX.predicate2(), EX.Object2}])
|> Description.put(
description([
{EX.predicate1(), EX.Object4},
{EX.predicate3(), EX.Object3}
])
)
assert Description.count(desc) == 3
assert description_includes_predication(desc, {EX.predicate1(), iri(EX.Object4)})
assert description_includes_predication(desc, {EX.predicate2(), iri(EX.Object2)})
assert description_includes_predication(desc, {EX.predicate3(), iri(EX.Object3)})
end
test "with a description on another subject" do
desc = description([{EX.predicate1(), EX.Object1}, {EX.predicate2(), EX.Object2}])
assert Description.put(
desc,
Description.new({EX.Other, EX.predicate(), iri(EX.Object)})
) == desc
end
test "triples with another subject are ignored" do
assert empty_description(
Description.put(description(), {EX.Other, EX.predicate(), iri(EX.Object)})
)
end
end
describe "delete" do
setup do
{:ok,
empty_description: Description.new(EX.S),
description1: Description.new(EX.S, EX.p(), EX.O),
description2: Description.new(EX.S, EX.p(), [EX.O1, EX.O2]),
description1: Description.new({EX.S, EX.p(), EX.O}),
description2: Description.new({EX.S, EX.p(), [EX.O1, EX.O2]}),
description3:
Description.new(EX.S, [
Description.new(EX.S)
|> Description.add([
{EX.p1(), [EX.O1, EX.O2]},
{EX.p2(), EX.O3},
{EX.p3(), [~B<foo>, ~L"bar"]}
])}
end
test "a single statement as a predicate object",
%{
empty_description: empty_description,
description1: description1,
description2: description2
} do
assert Description.delete(empty_description, EX.p(), EX.O) == empty_description
assert Description.delete(description1, EX.p(), EX.O) == empty_description
assert Description.delete(description2, EX.p(), EX.O1) ==
Description.new(EX.S, EX.p(), EX.O2)
end
test "a single statement as a predicate-object tuple",
%{
empty_description: empty_description,
@ -281,7 +334,7 @@ defmodule RDF.DescriptionTest do
assert Description.delete(description1, {EX.p(), EX.O}) == empty_description
assert Description.delete(description2, {EX.p(), EX.O2}) ==
Description.new(EX.S, EX.p(), EX.O1)
Description.new({EX.S, EX.p(), EX.O1})
end
test "a single statement as a subject-predicate-object tuple and the proper description subject",
@ -294,7 +347,7 @@ defmodule RDF.DescriptionTest do
assert Description.delete(description1, {EX.S, EX.p(), EX.O}) == empty_description
assert Description.delete(description2, {EX.S, EX.p(), EX.O2}) ==
Description.new(EX.S, EX.p(), EX.O1)
Description.new({EX.S, EX.p(), EX.O1})
end
test "a single statement as a subject-predicate-object tuple and another description subject",
@ -328,7 +381,7 @@ defmodule RDF.DescriptionTest do
{EX.p1(), EX.O1},
{EX.p2(), [EX.O2, EX.O3]},
{EX.S, EX.p3(), [~B<foo>, ~L"bar"]}
]) == Description.new(EX.S, EX.p1(), EX.O2)
]) == Description.new({EX.S, EX.p1(), EX.O2})
end
test "multiple statements with a map of predications",
@ -339,7 +392,7 @@ defmodule RDF.DescriptionTest do
EX.p1() => EX.O1,
EX.p2() => [EX.O2, EX.O3],
EX.p3() => [~B<foo>, ~L"bar"]
}) == Description.new(EX.S, EX.p1(), EX.O2)
}) == Description.new({EX.S, EX.p1(), EX.O2})
end
test "multiple statements with another description",
@ -352,12 +405,13 @@ defmodule RDF.DescriptionTest do
assert Description.delete(
description3,
Description.new(EX.S, %{
Description.new(EX.S)
|> Description.add(%{
EX.p1() => EX.O1,
EX.p2() => [EX.O2, EX.O3],
EX.p3() => [~B<foo>, ~L"bar"]
})
) == Description.new(EX.S, EX.p1(), EX.O2)
) == Description.new({EX.S, EX.p1(), EX.O2})
end
end
@ -365,9 +419,10 @@ defmodule RDF.DescriptionTest do
setup do
{:ok,
empty_description: Description.new(EX.S),
description1: Description.new(EX.S, EX.p(), [EX.O1, EX.O2]),
description1: Description.new({EX.S, EX.p(), [EX.O1, EX.O2]}),
description2:
Description.new(EX.S, [
Description.new(EX.S)
|> Description.add([
{EX.P1, [EX.O1, EX.O2]},
{EX.p2(), [~B<foo>, ~L"bar"]}
])}
@ -382,7 +437,7 @@ defmodule RDF.DescriptionTest do
assert Description.delete_predicates(description1, EX.p()) == empty_description
assert Description.delete_predicates(description2, EX.P1) ==
Description.new(EX.S, EX.p2(), [~B<foo>, ~L"bar"])
Description.new({EX.S, EX.p2(), [~B<foo>, ~L"bar"]})
end
test "a list of properties",
@ -400,18 +455,18 @@ defmodule RDF.DescriptionTest do
describe "update/4" do
test "list values returned from the update function become new coerced objects of the predicate" do
assert Description.new(EX.S, EX.P, [EX.O1, EX.O2])
assert Description.new({EX.S, EX.P, [EX.O1, EX.O2]})
|> Description.update(
EX.P,
fn [_object | other] -> [EX.O3 | other] end
) ==
Description.new(EX.S, EX.P, [EX.O3, EX.O2])
Description.new({EX.S, EX.P, [EX.O3, EX.O2]})
end
test "single values returned from the update function becomes new object of the predicate" do
assert Description.new(EX.S, EX.P, [EX.O1, EX.O2])
assert Description.new({EX.S, EX.P, [EX.O1, EX.O2]})
|> Description.update(EX.P, fn _ -> EX.O3 end) ==
Description.new(EX.S, EX.P, EX.O3)
Description.new({EX.S, EX.P, EX.O3})
end
test "returning an empty list or nil from the update function causes a removal of the predications" do
@ -421,11 +476,11 @@ defmodule RDF.DescriptionTest do
assert description
|> Description.update(EX.p(), fn _ -> [] end) ==
Description.new(EX.S, {EX.p(), []})
Description.new(EX.S)
assert description
|> Description.update(EX.p(), fn _ -> nil end) ==
Description.new(EX.S, {EX.p(), []})
Description.new(EX.S)
end
test "when the property is not present the initial object value is added for the predicate and the update function not called" do
@ -433,7 +488,7 @@ defmodule RDF.DescriptionTest do
assert Description.new(EX.S)
|> Description.update(EX.P, EX.O, fun) ==
Description.new(EX.S, EX.P, EX.O)
Description.new({EX.S, EX.P, EX.O})
assert Description.new(EX.S)
|> Description.update(EX.P, fun) ==
@ -449,20 +504,47 @@ defmodule RDF.DescriptionTest do
assert Enum.count(desc.predications) == 0
{{subject, predicate, _}, desc} =
Description.new([{EX.S, EX.p(), EX.O1}, {EX.S, EX.p(), EX.O2}])
Description.new(EX.S)
|> Description.add([{EX.S, EX.p(), EX.O1}, {EX.S, EX.p(), EX.O2}])
|> Description.pop()
assert {subject, predicate} == {iri(EX.S), iri(EX.p())}
assert Enum.count(desc.predications) == 1
{{subject, _, _}, desc} =
Description.new([{EX.S, EX.p1(), EX.O1}, {EX.S, EX.p2(), EX.O2}])
Description.new(EX.S)
|> Description.add([{EX.S, EX.p1(), EX.O1}, {EX.S, EX.p2(), EX.O2}])
|> Description.pop()
assert subject == iri(EX.S)
assert Enum.count(desc.predications) == 1
end
test "include?/2" do
desc =
Description.new(EX.S)
|> Description.add([
{EX.p1(), [EX.O1, EX.O2]},
{EX.p2(), EX.O3},
{EX.p3(), [~B<foo>, ~L"bar"]}
])
assert Description.include?(desc, {EX.p1(), EX.O2})
assert Description.include?(desc, {EX.p1(), [EX.O1, EX.O2]})
assert Description.include?(desc, [{EX.p1(), [EX.O1]}, {EX.p2(), EX.O3}])
assert Description.include?(desc, %{EX.p1() => [EX.O1, EX.O2], EX.p2() => EX.O3})
assert Description.include?(
desc,
Description.new(EX.S)
|> Description.add(%{EX.p1() => [EX.O1, EX.O2], EX.p2() => EX.O3})
)
refute Description.include?(desc, {EX.p4(), EX.O1})
refute Description.include?(desc, {EX.p1(), EX.O3})
refute Description.include?(desc, {EX.p1(), [EX.O1, EX.O3]})
end
test "values/1" do
assert Description.new(EX.s()) |> Description.values() == %{}
@ -487,20 +569,24 @@ defmodule RDF.DescriptionTest do
describe "take/2" do
test "with a non-empty property list" do
assert Description.new([{EX.S, EX.p1(), EX.O1}, {EX.S, EX.p2(), EX.O2}])
assert Description.new(EX.S)
|> Description.add([{EX.S, EX.p1(), EX.O1}, {EX.S, EX.p2(), EX.O2}])
|> Description.take([EX.p2(), EX.p3()]) ==
Description.new({EX.S, EX.p2(), EX.O2})
end
test "with an empty property list" do
assert Description.new([{EX.S, EX.p1(), EX.O1}, {EX.S, EX.p2(), EX.O2}])
assert Description.new(EX.S)
|> Description.add([{EX.S, EX.p1(), EX.O1}, {EX.S, EX.p2(), EX.O2}])
|> Description.take([]) == Description.new(EX.S)
end
test "with nil" do
assert Description.new([{EX.S, EX.p1(), EX.O1}, {EX.S, EX.p2(), EX.O2}])
assert Description.new(EX.S)
|> Description.add([{EX.S, EX.p1(), EX.O1}, {EX.S, EX.p2(), EX.O2}])
|> Description.take(nil) ==
Description.new([{EX.S, EX.p1(), EX.O1}, {EX.S, EX.p2(), EX.O2}])
Description.new(EX.S)
|> Description.add([{EX.S, EX.p1(), EX.O1}, {EX.S, EX.p2(), EX.O2}])
end
end
@ -516,7 +602,11 @@ defmodule RDF.DescriptionTest do
test "Enum.count" do
assert Enum.count(Description.new(EX.foo())) == 0
assert Enum.count(Description.new({EX.S, EX.p(), EX.O})) == 1
assert Enum.count(Description.new([{EX.S, EX.p(), EX.O1}, {EX.S, EX.p(), EX.O2}])) == 2
assert Enum.count(
Description.new(EX.S)
|> Description.add([{EX.S, EX.p(), EX.O1}, {EX.S, EX.p(), EX.O2}])
) == 2
end
test "Enum.member?" do
@ -524,7 +614,8 @@ defmodule RDF.DescriptionTest do
assert Enum.member?(Description.new({EX.S, EX.p(), EX.O}), {EX.S, EX.p(), EX.O})
desc =
Description.new([
Description.new(EX.Subject)
|> Description.add([
{EX.Subject, EX.predicate1(), EX.Object1},
{EX.Subject, EX.predicate2(), EX.Object2},
{EX.predicate2(), EX.Object3}
@ -538,7 +629,8 @@ defmodule RDF.DescriptionTest do
test "Enum.reduce" do
desc =
Description.new([
Description.new(EX.Subject)
|> Description.add([
{EX.Subject, EX.predicate1(), EX.Object1},
{EX.Subject, EX.predicate2(), EX.Object2},
{EX.predicate2(), EX.Object3}
@ -558,7 +650,8 @@ defmodule RDF.DescriptionTest do
EX.predicate2() => EX.Object2
}
assert Enum.into(map, Description.new(EX.Subject)) == Description.new(EX.Subject, map)
assert Enum.into(map, Description.new(EX.Subject)) ==
Description.new(EX.Subject) |> Description.add(map)
end
test "with a list of triples" do
@ -567,7 +660,9 @@ defmodule RDF.DescriptionTest do
{EX.Subject, EX.predicate2(), EX.Object2}
]
assert Enum.into(triples, Description.new(EX.Subject)) == Description.new(triples)
assert Enum.into(triples, Description.new(EX.Subject)) ==
Description.new(EX.Subject)
|> Description.add(triples)
end
test "with a list of predicate-object pairs" do
@ -576,7 +671,9 @@ defmodule RDF.DescriptionTest do
{EX.predicate2(), EX.Object2}
]
assert Enum.into(pairs, Description.new(EX.Subject)) == Description.new(EX.Subject, pairs)
assert Enum.into(pairs, Description.new(EX.Subject)) ==
Description.new(EX.Subject)
|> Description.add(pairs)
end
test "with a list of lists" do
@ -586,7 +683,8 @@ defmodule RDF.DescriptionTest do
]
assert Enum.into(lists, Description.new(EX.Subject)) ==
Description.new(Enum.map(lists, &List.to_tuple/1))
Description.new(EX.Subject)
|> Description.add(Enum.map(lists, &List.to_tuple/1))
end
end
@ -594,23 +692,24 @@ defmodule RDF.DescriptionTest do
test "access with the [] operator" do
assert Description.new(EX.Subject)[EX.predicate()] == nil
assert Description.new(EX.Subject, EX.predicate(), EX.Object)[EX.predicate()] == [
assert Description.new({EX.Subject, EX.predicate(), EX.Object})[EX.predicate()] == [
iri(EX.Object)
]
assert Description.new(EX.Subject, EX.Predicate, EX.Object)[EX.Predicate] == [
assert Description.new({EX.Subject, EX.Predicate, EX.Object})[EX.Predicate] == [
iri(EX.Object)
]
assert Description.new(EX.Subject, EX.predicate(), EX.Object)[
assert Description.new({EX.Subject, EX.predicate(), EX.Object})[
"http://example.com/predicate"
] == [iri(EX.Object)]
assert Description.new([
assert (Description.new(EX.Subject)
|> Description.add([
{EX.Subject, EX.predicate1(), EX.Object1},
{EX.Subject, EX.predicate1(), EX.Object2},
{EX.Subject, EX.predicate2(), EX.Object3}
])[EX.predicate1()] ==
]))[EX.predicate1()] ==
[iri(EX.Object1), iri(EX.Object2)]
end
end

View file

@ -216,7 +216,8 @@ defmodule RDF.GraphTest do
g =
Graph.add(
graph(),
Description.new(EX.Subject1, [
Description.new(EX.Subject1)
|> Description.add([
{EX.predicate1(), EX.Object1},
{EX.predicate2(), EX.Object2}
])
@ -346,7 +347,10 @@ defmodule RDF.GraphTest do
test "a Description" do
g =
Graph.new([{EX.S1, EX.P1, EX.O1}, {EX.S2, EX.P2, EX.O2}, {EX.S1, EX.P3, EX.O3}])
|> RDF.Graph.put(Description.new(EX.S1, [{EX.P3, EX.O4}, {EX.P2, bnode(:foo)}]))
|> RDF.Graph.put(
Description.new(EX.S1)
|> Description.add([{EX.P3, EX.O4}, {EX.P2, bnode(:foo)}])
)
assert Graph.triple_count(g) == 4
assert graph_includes_statement?(g, {EX.S1, EX.P1, EX.O1})
@ -454,16 +458,14 @@ defmodule RDF.GraphTest do
%{graph1: graph1, graph2: graph2, graph3: graph3} do
assert Graph.delete(
graph1,
Description.new(
EX.S,
[{EX.p(), EX.O}, {EX.p2(), EX.O2}]
)
Description.new(EX.S)
|> Description.add([{EX.p(), EX.O}, {EX.p2(), EX.O2}])
) == Graph.new()
assert Graph.delete(graph2, Description.new(EX.S, EX.p(), [EX.O1, EX.O2])) ==
assert Graph.delete(graph2, Description.new({EX.S, EX.p(), [EX.O1, EX.O2]})) ==
Graph.new(name: EX.Graph)
assert Graph.delete(graph3, Description.new(EX.S3, EX.p3(), ~B<foo>)) ==
assert Graph.delete(graph3, Description.new({EX.S3, EX.p3(), ~B<foo>})) ==
Graph.new([
{EX.S1, EX.p1(), [EX.O1, EX.O2]},
{EX.S2, EX.p2(), EX.O3},
@ -553,7 +555,9 @@ defmodule RDF.GraphTest do
])
|> Graph.update(
EX.S2,
fn ^old_description -> Description.new(EX.S3, new_description) end
fn ^old_description ->
Description.new(EX.S3) |> Description.add(new_description)
end
) ==
Graph.new([
{EX.S1, EX.p1(), [EX.O1, EX.O2]},
@ -575,7 +579,8 @@ defmodule RDF.GraphTest do
) ==
Graph.new([
{EX.S1, EX.p1(), [EX.O1, EX.O2]},
Description.new(EX.S2, new_description)
Description.new(EX.S2)
|> Description.add(new_description)
])
end

View file

@ -282,21 +282,21 @@ defmodule RDF.Vocabulary.NamespaceTest do
assert Example.__ENV__() == ~I<http://example.com/ex#__ENV__>
assert Example.__CALLER__() == ~I<http://example.com/ex#__CALLER__>
assert Example.nil(EX.S, 1) == RDF.description(EX.S, Example.nil(), 1)
assert Example.true(EX.S, 1) == RDF.description(EX.S, Example.true(), 1)
assert Example.false(EX.S, 1) == RDF.description(EX.S, Example.false(), 1)
assert Example.do(EX.S, 1) == RDF.description(EX.S, Example.do(), 1)
assert Example.end(EX.S, 1) == RDF.description(EX.S, Example.end(), 1)
assert Example.else(EX.S, 1) == RDF.description(EX.S, Example.else(), 1)
assert Example.try(EX.S, 1) == RDF.description(EX.S, Example.try(), 1)
assert Example.rescue(EX.S, 1) == RDF.description(EX.S, Example.rescue(), 1)
assert Example.catch(EX.S, 1) == RDF.description(EX.S, Example.catch(), 1)
assert Example.after(EX.S, 1) == RDF.description(EX.S, Example.after(), 1)
assert Example.not(EX.S, 1) == RDF.description(EX.S, Example.not(), 1)
assert Example.cond(EX.S, 1) == RDF.description(EX.S, Example.cond(), 1)
assert Example.inbits(EX.S, 1) == RDF.description(EX.S, Example.inbits(), 1)
assert Example.inlist(EX.S, 1) == RDF.description(EX.S, Example.inlist(), 1)
assert Example.receive(EX.S, 1) == RDF.description(EX.S, Example.receive(), 1)
assert Example.nil(EX.S, 1) == RDF.description({EX.S, Example.nil(), 1})
assert Example.true(EX.S, 1) == RDF.description({EX.S, Example.true(), 1})
assert Example.false(EX.S, 1) == RDF.description({EX.S, Example.false(), 1})
assert Example.do(EX.S, 1) == RDF.description({EX.S, Example.do(), 1})
assert Example.end(EX.S, 1) == RDF.description({EX.S, Example.end(), 1})
assert Example.else(EX.S, 1) == RDF.description({EX.S, Example.else(), 1})
assert Example.try(EX.S, 1) == RDF.description({EX.S, Example.try(), 1})
assert Example.rescue(EX.S, 1) == RDF.description({EX.S, Example.rescue(), 1})
assert Example.catch(EX.S, 1) == RDF.description({EX.S, Example.catch(), 1})
assert Example.after(EX.S, 1) == RDF.description({EX.S, Example.after(), 1})
assert Example.not(EX.S, 1) == RDF.description({EX.S, Example.not(), 1})
assert Example.cond(EX.S, 1) == RDF.description({EX.S, Example.cond(), 1})
assert Example.inbits(EX.S, 1) == RDF.description({EX.S, Example.inbits(), 1})
assert Example.inlist(EX.S, 1) == RDF.description({EX.S, Example.inlist(), 1})
assert Example.receive(EX.S, 1) == RDF.description({EX.S, Example.receive(), 1})
end
describe "defvocab with invalid terms" do
@ -885,7 +885,7 @@ defmodule RDF.Vocabulary.NamespaceTest do
alias TestNS.EX
assert Example._foo() == ~I<http://example.com/ex#_foo>
assert Example._foo(EX.S, 1) == RDF.description(EX.S, Example._foo(), 1)
assert Example._foo(EX.S, 1) == RDF.description({EX.S, Example._foo(), 1})
end
end
@ -966,7 +966,7 @@ defmodule RDF.Vocabulary.NamespaceTest do
alias TestNS.{EX, EXS}
test "one statement with a strict property term" do
assert EXS.foo(EX.S, EX.O) == Description.new(EX.S, EXS.foo(), EX.O)
assert EXS.foo(EX.S, EX.O) == Description.new({EX.S, EXS.foo(), EX.O})
end
test "multiple statements with strict property terms and one object" do
@ -975,7 +975,8 @@ defmodule RDF.Vocabulary.NamespaceTest do
|> EXS.foo(EX.O1)
|> EXS.bar(EX.O2)
assert description == Description.new(EX.S, [{EXS.foo(), EX.O1}, {EXS.bar(), EX.O2}])
assert description ==
Description.new(EX.S) |> Description.add([{EXS.foo(), EX.O1}, {EXS.bar(), EX.O2}])
end
test "multiple statements with strict property terms and multiple objects in a list" do
@ -985,7 +986,8 @@ defmodule RDF.Vocabulary.NamespaceTest do
|> EXS.bar([EX.O3, EX.O4])
assert description ==
Description.new(EX.S, [
Description.new(EX.S)
|> Description.add([
{EXS.foo(), EX.O1},
{EXS.foo(), EX.O2},
{EXS.bar(), EX.O3},
@ -1000,7 +1002,8 @@ defmodule RDF.Vocabulary.NamespaceTest do
|> EXS.bar(EX.O3, EX.O4, EX.O5)
assert description ==
Description.new(EX.S, [
Description.new(EX.S)
|> Description.add([
{EXS.foo(), EX.O1},
{EXS.foo(), EX.O2},
{EXS.bar(), EX.O3},
@ -1010,7 +1013,7 @@ defmodule RDF.Vocabulary.NamespaceTest do
end
test "one statement with a non-strict property term" do
assert EX.p(EX.S, EX.O) == Description.new(EX.S, EX.p(), EX.O)
assert EX.p(EX.S, EX.O) == Description.new({EX.S, EX.p(), EX.O})
end
test "multiple statements with non-strict property terms and one object" do
@ -1019,7 +1022,8 @@ defmodule RDF.Vocabulary.NamespaceTest do
|> EX.p1(EX.O1)
|> EX.p2(EX.O2)
assert description == Description.new(EX.S, [{EX.p1(), EX.O1}, {EX.p2(), EX.O2}])
assert description ==
Description.new(EX.S) |> Description.add([{EX.p1(), EX.O1}, {EX.p2(), EX.O2}])
end
test "multiple statements with non-strict property terms and multiple objects in a list" do
@ -1029,7 +1033,8 @@ defmodule RDF.Vocabulary.NamespaceTest do
|> EX.p2([EX.O3, EX.O4])
assert description ==
Description.new(EX.S, [
Description.new(EX.S)
|> Description.add([
{EX.p1(), EX.O1},
{EX.p1(), EX.O2},
{EX.p2(), EX.O3},
@ -1044,7 +1049,8 @@ defmodule RDF.Vocabulary.NamespaceTest do
|> EX.p2(EX.O3, EX.O4)
assert description ==
Description.new(EX.S, [
Description.new(EX.S)
|> Description.add([
{EX.p1(), EX.O1},
{EX.p1(), EX.O2},
{EX.p2(), EX.O3},