From e9432ef556ed4013f99ddb114a283184b29b0e87 Mon Sep 17 00:00:00 2001 From: Marcel Otto Date: Mon, 27 Jul 2020 23:09:23 +0200 Subject: [PATCH] Limit the forms of input on RDF.Description functions --- lib/rdf.ex | 2 - lib/rdf/description.ex | 565 ++++++++++-------------- lib/rdf/diff.ex | 8 +- lib/rdf/graph.ex | 36 +- lib/rdf/vocabulary_namespace.ex | 24 +- test/unit/dataset_test.exs | 19 +- test/unit/description_test.exs | 451 +++++++++++-------- test/unit/graph_test.exs | 25 +- test/unit/vocabulary_namespace_test.exs | 54 ++- 9 files changed, 611 insertions(+), 573 deletions(-) diff --git a/lib/rdf.ex b/lib/rdf.ex index 65b375c..e7b9761 100644 --- a/lib/rdf.ex +++ b/lib/rdf.ex @@ -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 diff --git a/lib/rdf/description.ex b/lib/rdf/description.ex index 48baa69..2790e7f 100644 --- a/lib/rdf/description.ex +++ b/lib/rdf/description.ex @@ -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) + %__MODULE__{ + description + | predications: + Enum.reduce( + input.predications, + description.predications, + fn {predicate, objects}, predications -> + Map.put(predications, predicate, objects) end - } - else - descr - end - 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,14 +412,14 @@ 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 - case fun.(get(description, triple_predicate)) do - {objects_to_return, new_objects} -> - {objects_to_return, put(description, triple_predicate, new_objects)} + triple_predicate = coerce_predicate(predicate) - :pop -> - pop(description, triple_predicate) - end + case fun.(get(description, triple_predicate)) do + {objects_to_return, new_objects} -> + {objects_to_return, put(description, {triple_predicate, new_objects})} + + :pop -> + pop(description, triple_predicate) end end @@ -572,10 +473,10 @@ defmodule RDF.Description do ## Examples - iex> RDF.Description.new([ - ...> {EX.S1, EX.p1, EX.O1}, - ...> {EX.p2, EX.O2}, - ...> {EX.p2, EX.O3}]) |> + iex> RDF.Description.new(EX.S1) |> RDF.Description.add([ + ...> {EX.p1, EX.O1}, + ...> {EX.p2, EX.O2}, + ...> {EX.p2, EX.O3}]) |> ...> RDF.Description.predicates MapSet.new([EX.p1, EX.p2]) """ @@ -590,12 +491,12 @@ defmodule RDF.Description do ## Examples - iex> RDF.Description.new([ - ...> {EX.S1, EX.p1, EX.O1}, - ...> {EX.p2, EX.O2}, - ...> {EX.p3, EX.O2}, - ...> {EX.p4, RDF.bnode(:bnode)}, - ...> {EX.p3, "foo"} + 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)}, + ...> {EX.p3, "foo"} ...> ]) |> RDF.Description.objects MapSet.new([RDF.iri(EX.O1), RDF.iri(EX.O2), RDF.bnode(:bnode)]) """ @@ -622,12 +523,12 @@ defmodule RDF.Description do ## Examples - iex> RDF.Description.new([ - ...> {EX.S1, EX.p1, EX.O1}, - ...> {EX.p2, EX.O2}, - ...> {EX.p1, EX.O2}, - ...> {EX.p2, RDF.bnode(:bnode)}, - ...> {EX.p3, "foo"} + 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)}, + ...> {EX.p3, "foo"} ...> ]) |> RDF.Description.resources MapSet.new([RDF.iri(EX.O1), RDF.iri(EX.O2), RDF.bnode(:bnode), EX.p1, EX.p2, EX.p3]) """ @@ -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) + 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?(%__MODULE__{} = description, {subject, predicate, objects}) do + coerce_subject(subject) == description.subject && + include?(description, {predicate, objects}) + end + + 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 - def include?( - desc = %__MODULE__{subject: desc_subject}, - {subject, predicate, object} - ) do - coerce_subject(subject) == desc_subject && - include?(desc, {predicate, object}) - end - - def include?(%__MODULE__{}, _), do: false - @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 """ diff --git a/lib/rdf/diff.ex b/lib/rdf/diff.ex index 670eedc..ee27fb3 100644 --- a/lib/rdf/diff.ex +++ b/lib/rdf/diff.ex @@ -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 diff --git a/lib/rdf/graph.ex b/lib/rdf/graph.ex index 8f57154..656dffc 100644 --- a/lib/rdf/graph.ex +++ b/lib/rdf/graph.ex @@ -202,9 +202,14 @@ defmodule RDF.Graph do %__MODULE__{ graph | descriptions: - Map.update(descriptions, subject, Description.new(statements), fn description -> - Description.add(description, statements) - end) + Map.update( + descriptions, + subject, + Description.new(subject) |> Description.add(statements), + fn description -> + Description.add(description, statements) + 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 -> - Description.put(current, statements) - end) + Map.update( + descriptions, + subject, + Description.new(subject) |> Description.add(statements), + fn current -> + Description.put(current, statements) + 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 diff --git a/lib/rdf/vocabulary_namespace.ex b/lib/rdf/vocabulary_namespace.ex index 9db7c53..2cffa4e 100644 --- a/lib/rdf/vocabulary_namespace.ex +++ b/lib/rdf/vocabulary_namespace.ex @@ -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 diff --git a/test/unit/dataset_test.exs b/test/unit/dataset_test.exs index fd67e4c..bb75e57 100644 --- a/test/unit/dataset_test.exs +++ b/test/unit/dataset_test.exs @@ -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 diff --git a/test/unit/description_test.exs b/test/unit/description_test.exs index 742cfcf..1c31fa8 100644 --- a/test/unit/description_test.exs +++ b/test/unit/description_test.exs @@ -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, ~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, ~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, ~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, ~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, ~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, ~L"bar"]) + Description.new({EX.S, EX.p2(), [~B, ~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, ~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([ - {EX.Subject, EX.predicate1(), EX.Object1}, - {EX.Subject, EX.predicate1(), EX.Object2}, - {EX.Subject, EX.predicate2(), EX.Object3} - ])[EX.predicate1()] == + 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()] == [iri(EX.Object1), iri(EX.Object2)] end end diff --git a/test/unit/graph_test.exs b/test/unit/graph_test.exs index decf1bd..8fc5f3b 100644 --- a/test/unit/graph_test.exs +++ b/test/unit/graph_test.exs @@ -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)) == + assert Graph.delete(graph3, Description.new({EX.S3, EX.p3(), ~B})) == 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 diff --git a/test/unit/vocabulary_namespace_test.exs b/test/unit/vocabulary_namespace_test.exs index 6a9f612..5ebe3f6 100644 --- a/test/unit/vocabulary_namespace_test.exs +++ b/test/unit/vocabulary_namespace_test.exs @@ -282,21 +282,21 @@ defmodule RDF.Vocabulary.NamespaceTest do assert Example.__ENV__() == ~I assert Example.__CALLER__() == ~I - 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 - 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},