diff --git a/lib/rdf/dataset.ex b/lib/rdf/dataset.ex index de5052f..fd7e855 100644 --- a/lib/rdf/dataset.ex +++ b/lib/rdf/dataset.ex @@ -403,6 +403,7 @@ defmodule RDF.Dataset do iex> RDF.Dataset.fetch(dataset, EX.Foo) :error """ + @impl Access def fetch(%RDF.Dataset{graphs: graphs}, graph_name) do Access.fetch(graphs, coerce_graph_name(graph_name)) end @@ -473,6 +474,7 @@ defmodule RDF.Dataset do ...> end) {RDF.Graph.new(EX.Graph, {EX.S, EX.P, EX.O}), RDF.Dataset.new({EX.S, EX.P, EX.NEW, EX.Graph})} """ + @impl Access def get_and_update(%RDF.Dataset{} = dataset, graph_name, fun) do with graph_context = coerce_graph_name(graph_name) do case fun.(get(dataset, graph_context)) do @@ -522,6 +524,7 @@ defmodule RDF.Dataset do iex> RDF.Dataset.pop(dataset, EX.Foo) {nil, dataset} """ + @impl Access def pop(%RDF.Dataset{name: name, graphs: graphs} = dataset, graph_name) do case Access.pop(graphs, coerce_graph_name(graph_name)) do {nil, _} -> diff --git a/lib/rdf/datatype.ex b/lib/rdf/datatype.ex index f3b844c..27bc40d 100644 --- a/lib/rdf/datatype.ex +++ b/lib/rdf/datatype.ex @@ -125,6 +125,7 @@ defmodule RDF.Datatype do alias RDF.Datatype.NS.XSD @id unquote(id) + @impl unquote(__MODULE__) def id, do: @id @@ -182,6 +183,9 @@ defmodule RDF.Datatype do def convert(value, _), do: nil + @impl unquote(__MODULE__) + def lexical(literal) + def lexical(%RDF.Literal{value: value, uncanonical_lexical: nil}) do canonical_lexical(value) end @@ -191,21 +195,30 @@ defmodule RDF.Datatype do end + @impl unquote(__MODULE__) def canonical_lexical(value), do: to_string(value) + @impl unquote(__MODULE__) def invalid_lexical(value), do: to_string(value) + @impl unquote(__MODULE__) + def canonical(literal) def canonical(%Literal{value: nil} = literal), do: literal def canonical(%Literal{uncanonical_lexical: nil} = literal), do: literal def canonical(%Literal{} = literal) do %Literal{literal | uncanonical_lexical: nil} end + @impl unquote(__MODULE__) + def valid?(literal) def valid?(%Literal{value: nil}), do: false def valid?(%Literal{datatype: @id}), do: true def valid?(_), do: false + @impl unquote(__MODULE__) + def equal_value?(literal1, literal2) + def equal_value?(%Literal{uncanonical_lexical: lexical1, datatype: @id, value: nil}, %Literal{uncanonical_lexical: lexical2, datatype: @id}) do lexical1 == lexical2 diff --git a/lib/rdf/datatypes/boolean.ex b/lib/rdf/datatypes/boolean.ex index 0b150c5..e28415f 100644 --- a/lib/rdf/datatypes/boolean.ex +++ b/lib/rdf/datatypes/boolean.ex @@ -8,6 +8,9 @@ defmodule RDF.Boolean do import RDF.Literal.Guards + @impl RDF.Datatype + def convert(value, opts) + def convert(value, _) when is_boolean(value), do: value def convert(value, opts) when is_binary(value) do @@ -27,6 +30,9 @@ defmodule RDF.Boolean do def convert(value, opts), do: super(value, opts) + @impl RDF.Datatype + def cast(literal) + def cast(%RDF.Literal{datatype: datatype} = literal) do cond do not RDF.Literal.valid?(literal) -> diff --git a/lib/rdf/datatypes/date.ex b/lib/rdf/datatypes/date.ex index 7fcc07c..e36c40d 100644 --- a/lib/rdf/datatypes/date.ex +++ b/lib/rdf/datatypes/date.ex @@ -10,6 +10,9 @@ defmodule RDF.Date do @grammar ~r/\A(-?\d{4}-\d{2}-\d{2})((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z/ + @impl RDF.Datatype + def convert(value, opts) + def convert(%Date{} = value, %{tz: "+00:00"} = opts) do {convert(value, Map.delete(opts, :tz)), "Z"} end @@ -47,6 +50,9 @@ defmodule RDF.Date do end + @impl RDF.Datatype + def canonical_lexical(value) + def canonical_lexical(%Date{} = value) do Date.to_iso8601(value) end @@ -56,6 +62,9 @@ defmodule RDF.Date do end + @impl RDF.Datatype + def cast(literal) + def cast(%RDF.Literal{datatype: datatype} = literal) do cond do not RDF.Literal.valid?(literal) -> diff --git a/lib/rdf/datatypes/date_time.ex b/lib/rdf/datatypes/date_time.ex index b1f28a7..953f6e8 100644 --- a/lib/rdf/datatypes/date_time.ex +++ b/lib/rdf/datatypes/date_time.ex @@ -8,9 +8,8 @@ defmodule RDF.DateTime do import RDF.Literal.Guards - def now() do - new(DateTime.utc_now()) - end + @impl RDF.Datatype + def convert(value, opts) # Special case for date and dateTime, for which 0 is not a valid year def convert(%DateTime{year: 0} = value, opts), do: super(value, opts) @@ -52,6 +51,9 @@ defmodule RDF.DateTime do def convert(value, opts), do: super(value, opts) + @impl RDF.Datatype + def canonical_lexical(value) + def canonical_lexical(%DateTime{} = value) do DateTime.to_iso8601(value) end @@ -61,6 +63,9 @@ defmodule RDF.DateTime do end + @impl RDF.Datatype + def cast(literal) + def cast(%RDF.Literal{datatype: datatype} = literal) do cond do not RDF.Literal.valid?(literal) -> @@ -91,6 +96,19 @@ defmodule RDF.DateTime do def cast(_), do: nil + @doc """ + Builds a `RDF.DateTime` literal for current moment in time. + """ + def now() do + new(DateTime.utc_now()) + end + + + @doc """ + Extracts the timezone string from a `RDF.DateTime` literal. + """ + def tz(literal) + def tz(%Literal{value: %NaiveDateTime{}}), do: "" def tz(date_time_literal) do @@ -101,6 +119,7 @@ defmodule RDF.DateTime do end end + @doc """ Converts a datetime literal to a canonical string, preserving the zone information. """ diff --git a/lib/rdf/datatypes/decimal.ex b/lib/rdf/datatypes/decimal.ex index 22f9ca3..87416cb 100644 --- a/lib/rdf/datatypes/decimal.ex +++ b/lib/rdf/datatypes/decimal.ex @@ -9,6 +9,9 @@ defmodule RDF.Decimal do alias Elixir.Decimal, as: D + @impl RDF.Datatype + def convert(value, opts) + def convert(%D{coef: coef} = value, opts) when coef in ~w[qNaN sNaN inf]a, do: super(value, opts) @@ -35,6 +38,9 @@ defmodule RDF.Decimal do def convert(value, opts), do: super(value, opts) + @impl RDF.Datatype + def canonical_lexical(value) + def canonical_lexical(%D{sign: sign, coef: :qNaN}), do: if sign == 1, do: "NaN", else: "-NaN" @@ -66,6 +72,8 @@ defmodule RDF.Decimal do do: canonical_decimal(%{decimal | coef: Kernel.div(coef, 10), exp: exp + 1}) + @impl RDF.Datatype + def cast(literal) def cast(%RDF.Literal{datatype: datatype} = literal) do cond do @@ -99,6 +107,7 @@ defmodule RDF.Decimal do def cast(_), do: nil + @impl RDF.Datatype def equal_value?(left, right), do: RDF.Numeric.equal_value?(left, right) end diff --git a/lib/rdf/datatypes/double.ex b/lib/rdf/datatypes/double.ex index 516a889..9a07ada 100644 --- a/lib/rdf/datatypes/double.ex +++ b/lib/rdf/datatypes/double.ex @@ -19,6 +19,8 @@ defmodule RDF.Double do end end + @impl RDF.Datatype + def convert(value, opts) def convert(value, _) when is_float(value), do: value @@ -53,6 +55,9 @@ defmodule RDF.Double do def convert(value, opts), do: super(value, opts) + @impl RDF.Datatype + def canonical_lexical(value) + def canonical_lexical(:nan), do: "NaN" def canonical_lexical(:positive_infinity), do: "INF" def canonical_lexical(:negative_infinity), do: "-INF" @@ -94,6 +99,8 @@ defmodule RDF.Double do end + @impl RDF.Datatype + def cast(literal) def cast(%RDF.Literal{datatype: datatype} = literal) do cond do @@ -131,6 +138,7 @@ defmodule RDF.Double do def cast(_), do: nil + @impl RDF.Datatype def equal_value?(left, right), do: RDF.Numeric.equal_value?(left, right) end diff --git a/lib/rdf/datatypes/integer.ex b/lib/rdf/datatypes/integer.ex index e5bb870..476fb97 100644 --- a/lib/rdf/datatypes/integer.ex +++ b/lib/rdf/datatypes/integer.ex @@ -8,6 +8,9 @@ defmodule RDF.Integer do import RDF.Literal.Guards + @impl RDF.Datatype + def convert(value, opts) + def convert(value, _) when is_integer(value), do: value def convert(value, opts) when is_binary(value) do @@ -21,6 +24,9 @@ defmodule RDF.Integer do def convert(value, opts), do: super(value, opts) + @impl RDF.Datatype + def cast(literal) + def cast(%RDF.Literal{datatype: datatype} = literal) do cond do not RDF.Literal.valid?(literal) -> @@ -61,6 +67,7 @@ defmodule RDF.Integer do def cast(_), do: nil + @impl RDF.Datatype def equal_value?(left, right), do: RDF.Numeric.equal_value?(left, right) end diff --git a/lib/rdf/datatypes/lang_string.ex b/lib/rdf/datatypes/lang_string.ex index afd907e..de987e9 100644 --- a/lib/rdf/datatypes/lang_string.ex +++ b/lib/rdf/datatypes/lang_string.ex @@ -16,13 +16,22 @@ defmodule RDF.LangString do end + @impl RDF.Datatype def convert(value, _), do: to_string(value) + @impl RDF.Datatype + def valid?(literal) def valid?(%Literal{language: nil}), do: false def valid?(literal), do: super(literal) + @impl RDF.Datatype + def cast(_) do + nil + end + + @doc """ Checks if a language tagged string literal or language tag matches a language range. @@ -53,9 +62,4 @@ defmodule RDF.LangString do end end - - def cast(_) do - nil - end - end diff --git a/lib/rdf/datatypes/string.ex b/lib/rdf/datatypes/string.ex index 3d21400..3b87fbe 100644 --- a/lib/rdf/datatypes/string.ex +++ b/lib/rdf/datatypes/string.ex @@ -28,9 +28,13 @@ defmodule RDF.String do end + @impl RDF.Datatype def convert(value, _), do: to_string(value) + @impl RDF.Datatype + def cast(literal) + def cast(%RDF.IRI{value: value}), do: new(value) def cast(%RDF.Literal{datatype: datatype} = literal) do diff --git a/lib/rdf/datatypes/time.ex b/lib/rdf/datatypes/time.ex index c445d1c..3f8c334 100644 --- a/lib/rdf/datatypes/time.ex +++ b/lib/rdf/datatypes/time.ex @@ -11,6 +11,9 @@ defmodule RDF.Time do @tz_grammar ~r/\A(?:([\+\-])(\d{2}):(\d{2}))\Z/ + @impl RDF.Datatype + def convert(value, opts) + def convert(%Time{} = value, %{tz: tz} = opts) do {convert(value, Map.delete(opts, :tz)), tz} end @@ -62,6 +65,9 @@ defmodule RDF.Time do end + @impl RDF.Datatype + def canonical_lexical(value) + def canonical_lexical(%Time{} = value) do Time.to_iso8601(value) end @@ -70,36 +76,9 @@ defmodule RDF.Time do canonical_lexical(value) <> "Z" end - def tz(time_literal) do - if valid?(time_literal) do - time_literal - |> lexical() - |> RDF.DateTimeUtils.tz() - end - end - - @doc """ - Converts a time literal to a canonical string, preserving the zone information. - """ - def canonical_lexical_with_zone(%Literal{datatype: datatype} = literal) - when is_xsd_time(datatype) do - case tz(literal) do - nil -> - nil - - zone when zone in ["Z", "", "+00:00"] -> - canonical_lexical(literal.value) - - zone -> - literal - |> lexical() - |> String.replace_trailing(zone, "") - |> Time.from_iso8601!() - |> canonical_lexical() - |> Kernel.<>(zone) - end - end + @impl RDF.Datatype + def cast(literal) def cast(%RDF.Literal{datatype: datatype} = literal) do cond do @@ -135,4 +114,39 @@ defmodule RDF.Time do def cast(_), do: nil + + @doc """ + Extracts the timezone string from a `RDF.Time` literal. + """ + def tz(time_literal) do + if valid?(time_literal) do + time_literal + |> lexical() + |> RDF.DateTimeUtils.tz() + end + end + + + @doc """ + Converts a time literal to a canonical string, preserving the zone information. + """ + def canonical_lexical_with_zone(%Literal{datatype: datatype} = literal) + when is_xsd_time(datatype) do + case tz(literal) do + nil -> + nil + + zone when zone in ["Z", "", "+00:00"] -> + canonical_lexical(literal.value) + + zone -> + literal + |> lexical() + |> String.replace_trailing(zone, "") + |> Time.from_iso8601!() + |> canonical_lexical() + |> Kernel.<>(zone) + end + end + end diff --git a/lib/rdf/description.ex b/lib/rdf/description.ex index 3f1adc6..e93f3b9 100644 --- a/lib/rdf/description.ex +++ b/lib/rdf/description.ex @@ -321,6 +321,7 @@ defmodule RDF.Description do iex> RDF.Description.fetch(RDF.Description.new(EX.S), EX.foo) :error """ + @impl Access def fetch(%RDF.Description{predications: predications}, predicate) do with {:ok, objects} <- Access.fetch(predications, coerce_predicate(predicate)) do {:ok, Map.keys(objects)} @@ -391,6 +392,7 @@ defmodule RDF.Description do ...> RDF.Description.get_and_update(EX.P1, fn _ -> :pop end) {[RDF.iri(EX.O1)], RDF.Description.new({EX.S, EX.P2, EX.O2})} """ + @impl Access def get_and_update(description = %RDF.Description{}, predicate, fun) do with triple_predicate = coerce_predicate(predicate) do case fun.(get(description, triple_predicate)) do @@ -435,6 +437,7 @@ defmodule RDF.Description do iex> RDF.Description.pop(RDF.Description.new({EX.S, EX.P, EX.O}), EX.Missing) {nil, RDF.Description.new({EX.S, EX.P, EX.O})} """ + @impl Access def pop(description = %RDF.Description{subject: subject, predications: predications}, predicate) do case Access.pop(predications, coerce_predicate(predicate)) do {nil, _} -> diff --git a/lib/rdf/graph.ex b/lib/rdf/graph.ex index 8d6a1dc..d8ab417 100644 --- a/lib/rdf/graph.ex +++ b/lib/rdf/graph.ex @@ -319,6 +319,7 @@ defmodule RDF.Graph do iex> RDF.Graph.fetch(RDF.Graph.new, EX.foo) :error """ + @impl Access def fetch(%RDF.Graph{descriptions: descriptions}, subject) do Access.fetch(descriptions, coerce_subject(subject)) end @@ -380,6 +381,7 @@ defmodule RDF.Graph do ...> end) {RDF.Description.new(EX.S, EX.P, EX.O), RDF.Graph.new(EX.S, EX.P, EX.NEW)} """ + @impl Access def get_and_update(%RDF.Graph{} = graph, subject, fun) do with subject = coerce_subject(subject) do case fun.(get(graph, subject)) do @@ -426,6 +428,7 @@ defmodule RDF.Graph do iex> RDF.Graph.pop(RDF.Graph.new({EX.S, EX.P, EX.O}), EX.Missing) {nil, RDF.Graph.new({EX.S, EX.P, EX.O})} """ + @impl Access def pop(%RDF.Graph{name: name, descriptions: descriptions} = graph, subject) do case Access.pop(descriptions, coerce_subject(subject)) do {nil, _} -> diff --git a/lib/rdf/serialization/decoder.ex b/lib/rdf/serialization/decoder.ex index 3f68a56..404eca5 100644 --- a/lib/rdf/serialization/decoder.ex +++ b/lib/rdf/serialization/decoder.ex @@ -28,6 +28,7 @@ defmodule RDF.Serialization.Decoder do quote bind_quoted: [], unquote: true do @behaviour unquote(__MODULE__) + @impl unquote(__MODULE__) def decode!(content, opts \\ []) do case decode(content, opts) do {:ok, data} -> data diff --git a/lib/rdf/serialization/encoder.ex b/lib/rdf/serialization/encoder.ex index 8b6ce90..4b5e02f 100644 --- a/lib/rdf/serialization/encoder.ex +++ b/lib/rdf/serialization/encoder.ex @@ -30,6 +30,7 @@ defmodule RDF.Serialization.Encoder do import RDF.Literal.Guards + @impl unquote(__MODULE__) def encode!(data, opts \\ []) do case encode(data, opts) do {:ok, data} -> data diff --git a/lib/rdf/serialization/format.ex b/lib/rdf/serialization/format.ex index 5abe639..7b7c504 100644 --- a/lib/rdf/serialization/format.ex +++ b/lib/rdf/serialization/format.ex @@ -71,9 +71,13 @@ defmodule RDF.Serialization.Format do @decoder __MODULE__.Decoder @encoder __MODULE__.Encoder + @impl unquote(__MODULE__) def decoder, do: @decoder + + @impl unquote(__MODULE__) def encoder, do: @encoder + @impl unquote(__MODULE__) def options, do: %{} defoverridable [decoder: 0, encoder: 0, options: 0] @@ -104,18 +108,22 @@ defmodule RDF.Serialization.Format do quote do if !Module.defines?(__MODULE__, {:id, 0}) && Module.get_attribute(__MODULE__, :id) do + @impl unquote(__MODULE__) def id, do: @id end if !Module.defines?(__MODULE__, {:name, 0}) && Module.get_attribute(__MODULE__, :name) do + @impl unquote(__MODULE__) def name, do: @name end if !Module.defines?(__MODULE__, {:extension, 0}) && Module.get_attribute(__MODULE__, :extension) do + @impl unquote(__MODULE__) def extension, do: @extension end if !Module.defines?(__MODULE__, {:media_type, 0}) && Module.get_attribute(__MODULE__, :media_type) do + @impl unquote(__MODULE__) def media_type, do: @media_type end end diff --git a/lib/rdf/serializations/nquads_decoder.ex b/lib/rdf/serializations/nquads_decoder.ex index 2d5f678..36aa4d4 100644 --- a/lib/rdf/serializations/nquads_decoder.ex +++ b/lib/rdf/serializations/nquads_decoder.ex @@ -3,6 +3,7 @@ defmodule RDF.NQuads.Decoder do use RDF.Serialization.Decoder + @impl RDF.Serialization.Decoder def decode(content, _opts \\ []) do with {:ok, tokens, _} <- tokenize(content), {:ok, ast} <- parse(tokens) do diff --git a/lib/rdf/serializations/nquads_encoder.ex b/lib/rdf/serializations/nquads_encoder.ex index e257c36..062fbfc 100644 --- a/lib/rdf/serializations/nquads_encoder.ex +++ b/lib/rdf/serializations/nquads_encoder.ex @@ -3,6 +3,7 @@ defmodule RDF.NQuads.Encoder do use RDF.Serialization.Encoder + @impl RDF.Serialization.Encoder def encode(data, _opts \\ []) do result = data diff --git a/lib/rdf/serializations/ntriples_decoder.ex b/lib/rdf/serializations/ntriples_decoder.ex index 1d8c67c..b997ce6 100644 --- a/lib/rdf/serializations/ntriples_decoder.ex +++ b/lib/rdf/serializations/ntriples_decoder.ex @@ -3,6 +3,7 @@ defmodule RDF.NTriples.Decoder do use RDF.Serialization.Decoder + @impl RDF.Serialization.Decoder def decode(content, _opts \\ []) do with {:ok, tokens, _} <- tokenize(content), {:ok, ast} <- parse(tokens) do diff --git a/lib/rdf/serializations/ntriples_encoder.ex b/lib/rdf/serializations/ntriples_encoder.ex index e079245..0d77f04 100644 --- a/lib/rdf/serializations/ntriples_encoder.ex +++ b/lib/rdf/serializations/ntriples_encoder.ex @@ -6,6 +6,7 @@ defmodule RDF.NTriples.Encoder do alias RDF.{IRI, Literal, BlankNode} + @impl RDF.Serialization.Encoder def encode(data, _opts \\ []) do result = data diff --git a/lib/rdf/serializations/turtle_decoder.ex b/lib/rdf/serializations/turtle_decoder.ex index 07b5567..cfabde9 100644 --- a/lib/rdf/serializations/turtle_decoder.ex +++ b/lib/rdf/serializations/turtle_decoder.ex @@ -22,6 +22,7 @@ defmodule RDF.Turtle.Decoder do end end + @impl RDF.Serialization.Decoder def decode(content, opts \\ %{}) def decode(content, opts) when is_list(opts), diff --git a/lib/rdf/serializations/turtle_encoder.ex b/lib/rdf/serializations/turtle_encoder.ex index b0c29f2..3dc49a2 100644 --- a/lib/rdf/serializations/turtle_encoder.ex +++ b/lib/rdf/serializations/turtle_encoder.ex @@ -26,6 +26,7 @@ defmodule RDF.Turtle.Encoder do @ordered_properties MapSet.new(@predicate_order) + @impl RDF.Serialization.Encoder def encode(data, opts \\ []) do with base = Keyword.get(opts, :base) |> init_base(), prefixes = Keyword.get(opts, :prefixes, %{}) |> init_prefixes(), diff --git a/lib/rdf/vocabulary_namespace.ex b/lib/rdf/vocabulary_namespace.ex index e766540..a939f3e 100644 --- a/lib/rdf/vocabulary_namespace.ex +++ b/lib/rdf/vocabulary_namespace.ex @@ -66,6 +66,7 @@ defmodule RDF.Vocabulary.Namespace do def __strict__, do: @strict @terms unquote(Macro.escape(terms)) + @impl Elixir.RDF.Namespace def __terms__, do: @terms |> Map.keys @ignored_terms unquote(Macro.escape(ignored_terms)) @@ -84,6 +85,7 @@ defmodule RDF.Vocabulary.Namespace do define_vocab_terms unquote(lowercased_terms), unquote(base_iri) + @impl Elixir.RDF.Namespace def __resolve_term__(term) do case @terms[term] do nil ->