diff --git a/lib/rdf.ex b/lib/rdf.ex index 517cef2..56d1909 100644 --- a/lib/rdf.ex +++ b/lib/rdf.ex @@ -209,10 +209,7 @@ defmodule RDF do defdelegate literal(value), to: Literal, as: :new defdelegate literal(value, opts), to: Literal, as: :new - def literal?(%Literal{}), do: true - def literal?(%RDF.Literal.Generic{}), do: true - def literal?(%datatype{}), do: Literal.Datatype.Registry.datatype?(datatype) - def literal?(_), do: false + defdelegate literal?(literal), to: Literal.Datatype.Registry defdelegate triple(s, p, o), to: Triple, as: :new defdelegate triple(tuple), to: Triple, as: :new diff --git a/lib/rdf/literal.ex b/lib/rdf/literal.ex index e0ac7a3..f4960a2 100644 --- a/lib/rdf/literal.ex +++ b/lib/rdf/literal.ex @@ -92,12 +92,6 @@ defmodule RDF.Literal do def coerce(%__MODULE__{} = literal), do: literal - Enum.each(Datatype.Registry.datatypes(), fn datatype -> - def coerce(%unquote(datatype){} = literal) do - %__MODULE__{literal: literal} - end - end) - def coerce(value) when is_binary(value), do: RDF.XSD.String.new(value) def coerce(value) when is_boolean(value), do: RDF.XSD.Boolean.new(value) def coerce(value) when is_integer(value), do: RDF.XSD.Integer.new(value) @@ -108,7 +102,22 @@ defmodule RDF.Literal do def coerce(%DateTime{} = value), do: RDF.XSD.DateTime.new(value) def coerce(%NaiveDateTime{} = value), do: RDF.XSD.DateTime.new(value) def coerce(%URI{} = value), do: RDF.XSD.AnyURI.new(value) - def coerce(_), do: nil + + # Although the following catch-all-clause for all structs could handle the core datatypes + # we're generating dedicated clauses for them here, as they are approx. 15% faster + Enum.each(Datatype.Registry.core_datatypes(), fn datatype -> + def coerce(%unquote(datatype){} = datatype_literal) do + %__MODULE__{literal: datatype_literal} + end + end) + + def coerce(%_datatype{} = datatype_literal) do + if Datatype.Registry.literal?(datatype_literal) do + %__MODULE__{literal: datatype_literal} + end + end + + def coerce(_), do: nil @doc """ diff --git a/lib/rdf/literal/datatype.ex b/lib/rdf/literal/datatype.ex index da365e9..9fa7027 100644 --- a/lib/rdf/literal/datatype.ex +++ b/lib/rdf/literal/datatype.ex @@ -140,6 +140,17 @@ defmodule RDF.Literal.Datatype do defmacro __using__(opts) do name = Keyword.fetch!(opts, :name) id = Keyword.fetch!(opts, :id) + do_register = Keyword.get(opts, :register, not is_nil(id)) + datatype = __CALLER__.module + + # TODO: find an alternative to Code.eval_quoted - We want to support that id can be passed via a function call + unquoted_id = + if do_register do + id + |> Code.eval_quoted([], __ENV__) + |> elem(0) + |> to_string() + end quote do @behaviour unquote(__MODULE__) @@ -258,6 +269,15 @@ defmodule RDF.Literal.Datatype do literal.__struct__.lexical(literal) end end + + if unquote(do_register) do + import ProtocolEx + + defimpl_ex Registration, unquote(unquoted_id), + for: RDF.Literal.Datatype.Registry.Registration do + def datatype(id), do: unquote(datatype) + end + end end end end diff --git a/lib/rdf/literal/datatype/registry.ex b/lib/rdf/literal/datatype/registry.ex index bf2af7c..6fb00b2 100644 --- a/lib/rdf/literal/datatype/registry.ex +++ b/lib/rdf/literal/datatype/registry.ex @@ -1,33 +1,45 @@ -# TODO: This registry should be managed automatically/dynamically and be extendable, to allow user-defined datatypes ... defmodule RDF.Literal.Datatype.Registry do @moduledoc false alias RDF.{Literal, IRI, XSD} + alias RDF.Literal.Datatype.Registry.Registration - @datatypes [RDF.LangString | Enum.to_list(XSD.datatypes())] + import RDF.Guards - @mapping Map.new(@datatypes, fn datatype -> {IRI.new(datatype.id), datatype} end) + @core_datatypes [RDF.LangString | Enum.to_list(XSD.datatypes())] + @mapping Map.new(@core_datatypes, fn datatype -> {IRI.new(datatype.id), datatype} end) @doc """ - The mapping of IRIs of datatypes to their `RDF.Literal.Datatype`. + The IRIs of all core `RDF.Literal.Datatype`s. """ - @spec mapping :: %{IRI.t => Literal.Datatype.t} - def mapping, do: @mapping + @spec core_ids :: [IRI.t] + def core_ids, do: Map.keys(@mapping) @doc """ - The IRIs of all datatypes with a `RDF.Literal.Datatype` defined. + All core `RDF.Literal.Datatype` modules. """ - @spec ids :: [IRI.t] - def ids, do: Map.keys(@mapping) + @spec core_datatypes :: Enum.t + def core_datatypes, do: @core_datatypes @doc """ - All defined `RDF.Literal.Datatype` modules. + Checks if the given module is core datatype. """ - @spec datatypes :: Enum.t - def datatypes, do: @datatypes + @spec core_datatype?(module) :: boolean + def core_datatype?(module), do: module in @core_datatypes + @doc """ + Checks if the given module is a core datatype or a registered custom datatype implementing the `RDF.Literal.Datatype` behaviour. + """ @spec datatype?(module) :: boolean - def datatype?(module), do: module in @datatypes + def datatype?(module) do + core_datatype?(module) or implements_datatype_behaviour?(module) + end + + @spec literal?(module) :: boolean + def literal?(%Literal{}), do: true + def literal?(%Literal.Generic{}), do: true + def literal?(%datatype{}), do: datatype?(datatype) + def literal?(_), do: false @doc """ Returns the `RDF.Literal.Datatype` for a directly datatype IRI or the datatype IRI of a `RDF.Literal`. @@ -35,5 +47,19 @@ defmodule RDF.Literal.Datatype.Registry do @spec get(Literal.t | IRI.t | String.t) :: Literal.Datatype.t def get(%Literal{} = literal), do: Literal.datatype(literal) def get(id) when is_binary(id), do: id |> IRI.new() |> get() - def get(id), do: @mapping[id] + def get(id) when maybe_ns_term(id), do: id |> IRI.new() |> get() + def get(id), do: @mapping[id] || get_custom_datatype(id) + + defp get_custom_datatype(id) do + id + |> to_string() + |> Registration.datatype() + end + + defp implements_datatype_behaviour?(module) do + module.module_info[:attributes] + |> Keyword.get_values(:behaviour) + |> List.flatten() + |> Enum.member?(RDF.Literal.Datatype) + end end diff --git a/lib/rdf/literal/datatype/registry/protocol.ex b/lib/rdf/literal/datatype/registry/protocol.ex new file mode 100644 index 0000000..11b4061 --- /dev/null +++ b/lib/rdf/literal/datatype/registry/protocol.ex @@ -0,0 +1,5 @@ +import ProtocolEx + +defprotocol_ex RDF.Literal.Datatype.Registry.Registration do + def datatype(id), do: nil +end diff --git a/lib/rdf/literal/datatypes/lang_string.ex b/lib/rdf/literal/datatypes/lang_string.ex index 6759ca8..8d601b2 100644 --- a/lib/rdf/literal/datatypes/lang_string.ex +++ b/lib/rdf/literal/datatypes/lang_string.ex @@ -7,7 +7,8 @@ defmodule RDF.LangString do use RDF.Literal.Datatype, name: "langString", - id: RDF.Utils.Bootstrapping.rdf_iri("langString") + id: RDF.Utils.Bootstrapping.rdf_iri("langString"), + register: false # core datatypes don't need to be registered alias RDF.Literal.Datatype alias RDF.Literal diff --git a/lib/rdf/xsd/datatypes/any_uri.ex b/lib/rdf/xsd/datatypes/any_uri.ex index d1e15ff..3943cb0 100644 --- a/lib/rdf/xsd/datatypes/any_uri.ex +++ b/lib/rdf/xsd/datatypes/any_uri.ex @@ -11,7 +11,8 @@ defmodule RDF.XSD.AnyURI do use RDF.XSD.Datatype.Primitive, name: "anyURI", - id: RDF.Utils.Bootstrapping.xsd_iri("anyURI") + id: RDF.Utils.Bootstrapping.xsd_iri("anyURI"), + register: false # core datatypes don't need to be registered @impl RDF.XSD.Datatype @spec lexical_mapping(String.t(), Keyword.t()) :: valid_value diff --git a/lib/rdf/xsd/datatypes/boolean.ex b/lib/rdf/xsd/datatypes/boolean.ex index 6e012f1..740e6c3 100644 --- a/lib/rdf/xsd/datatypes/boolean.ex +++ b/lib/rdf/xsd/datatypes/boolean.ex @@ -8,7 +8,8 @@ defmodule RDF.XSD.Boolean do use RDF.XSD.Datatype.Primitive, name: "boolean", - id: RDF.Utils.Bootstrapping.xsd_iri("boolean") + id: RDF.Utils.Bootstrapping.xsd_iri("boolean"), + register: false # core datatypes don't need to be registered @impl RDF.XSD.Datatype def lexical_mapping(lexical, _) do diff --git a/lib/rdf/xsd/datatypes/byte.ex b/lib/rdf/xsd/datatypes/byte.ex index b1e4a8d..e63a46e 100644 --- a/lib/rdf/xsd/datatypes/byte.ex +++ b/lib/rdf/xsd/datatypes/byte.ex @@ -2,7 +2,8 @@ defmodule RDF.XSD.Byte do use RDF.XSD.Datatype.Restriction, name: "byte", id: RDF.Utils.Bootstrapping.xsd_iri("byte"), - base: RDF.XSD.Short + base: RDF.XSD.Short, + register: false # core datatypes don't need to be registered def_facet_constraint RDF.XSD.Facets.MinInclusive, -128 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 127 diff --git a/lib/rdf/xsd/datatypes/date.ex b/lib/rdf/xsd/datatypes/date.ex index 95b94d1..803931b 100644 --- a/lib/rdf/xsd/datatypes/date.ex +++ b/lib/rdf/xsd/datatypes/date.ex @@ -11,7 +11,8 @@ defmodule RDF.XSD.Date do use RDF.XSD.Datatype.Primitive, name: "date", - id: RDF.Utils.Bootstrapping.xsd_iri("date") + id: RDF.Utils.Bootstrapping.xsd_iri("date"), + register: false # core datatypes don't need to be registered # TODO: Are GMT/UTC actually allowed? Maybe because it is supported by Elixir's Datetime ... diff --git a/lib/rdf/xsd/datatypes/date_time.ex b/lib/rdf/xsd/datatypes/date_time.ex index e49f501..efe2a17 100644 --- a/lib/rdf/xsd/datatypes/date_time.ex +++ b/lib/rdf/xsd/datatypes/date_time.ex @@ -7,7 +7,8 @@ defmodule RDF.XSD.DateTime do use RDF.XSD.Datatype.Primitive, name: "dateTime", - id: RDF.Utils.Bootstrapping.xsd_iri("dateTime") + id: RDF.Utils.Bootstrapping.xsd_iri("dateTime"), + register: false # core datatypes don't need to be registered @impl RDF.XSD.Datatype def lexical_mapping(lexical, opts) do diff --git a/lib/rdf/xsd/datatypes/decimal.ex b/lib/rdf/xsd/datatypes/decimal.ex index 835ec07..60c1b5e 100644 --- a/lib/rdf/xsd/datatypes/decimal.ex +++ b/lib/rdf/xsd/datatypes/decimal.ex @@ -7,7 +7,8 @@ defmodule RDF.XSD.Decimal do use RDF.XSD.Datatype.Primitive, name: "decimal", - id: RDF.Utils.Bootstrapping.xsd_iri("decimal") + id: RDF.Utils.Bootstrapping.xsd_iri("decimal"), + register: false # core datatypes don't need to be registered alias Elixir.Decimal, as: D diff --git a/lib/rdf/xsd/datatypes/double.ex b/lib/rdf/xsd/datatypes/double.ex index b7861c7..f54f896 100644 --- a/lib/rdf/xsd/datatypes/double.ex +++ b/lib/rdf/xsd/datatypes/double.ex @@ -8,7 +8,8 @@ defmodule RDF.XSD.Double do use RDF.XSD.Datatype.Primitive, name: "double", - id: RDF.Utils.Bootstrapping.xsd_iri("double") + id: RDF.Utils.Bootstrapping.xsd_iri("double"), + register: false # core datatypes don't need to be registered @special_values ~W[positive_infinity negative_infinity nan]a diff --git a/lib/rdf/xsd/datatypes/float.ex b/lib/rdf/xsd/datatypes/float.ex index 6e3108a..7c0fcc0 100644 --- a/lib/rdf/xsd/datatypes/float.ex +++ b/lib/rdf/xsd/datatypes/float.ex @@ -9,5 +9,6 @@ defmodule RDF.XSD.Float do use RDF.XSD.Datatype.Restriction, name: "float", id: RDF.Utils.Bootstrapping.xsd_iri("float"), - base: RDF.XSD.Double + base: RDF.XSD.Double, + register: false # core datatypes don't need to be registered end diff --git a/lib/rdf/xsd/datatypes/int.ex b/lib/rdf/xsd/datatypes/int.ex index 1844084..6f3fbb5 100644 --- a/lib/rdf/xsd/datatypes/int.ex +++ b/lib/rdf/xsd/datatypes/int.ex @@ -2,7 +2,8 @@ defmodule RDF.XSD.Int do use RDF.XSD.Datatype.Restriction, name: "int", id: RDF.Utils.Bootstrapping.xsd_iri("int"), - base: RDF.XSD.Long + base: RDF.XSD.Long, + register: false # core datatypes don't need to be registered def_facet_constraint RDF.XSD.Facets.MinInclusive, -2_147_483_648 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 2_147_483_647 diff --git a/lib/rdf/xsd/datatypes/integer.ex b/lib/rdf/xsd/datatypes/integer.ex index 05e8135..4078bad 100644 --- a/lib/rdf/xsd/datatypes/integer.ex +++ b/lib/rdf/xsd/datatypes/integer.ex @@ -10,7 +10,8 @@ defmodule RDF.XSD.Integer do use RDF.XSD.Datatype.Primitive, name: "integer", - id: RDF.Utils.Bootstrapping.xsd_iri("integer") + id: RDF.Utils.Bootstrapping.xsd_iri("integer"), + register: false # core datatypes don't need to be registered def_applicable_facet RDF.XSD.Facets.MinInclusive def_applicable_facet RDF.XSD.Facets.MaxInclusive diff --git a/lib/rdf/xsd/datatypes/long.ex b/lib/rdf/xsd/datatypes/long.ex index fc6617b..f3094fa 100644 --- a/lib/rdf/xsd/datatypes/long.ex +++ b/lib/rdf/xsd/datatypes/long.ex @@ -2,7 +2,8 @@ defmodule RDF.XSD.Long do use RDF.XSD.Datatype.Restriction, name: "long", id: RDF.Utils.Bootstrapping.xsd_iri("long"), - base: RDF.XSD.Integer + base: RDF.XSD.Integer, + register: false # core datatypes don't need to be registered def_facet_constraint RDF.XSD.Facets.MinInclusive, -9_223_372_036_854_775_808 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 9_223_372_036_854_775_807 diff --git a/lib/rdf/xsd/datatypes/negative_integer.ex b/lib/rdf/xsd/datatypes/negative_integer.ex index 4bb0c4f..2f934a7 100644 --- a/lib/rdf/xsd/datatypes/negative_integer.ex +++ b/lib/rdf/xsd/datatypes/negative_integer.ex @@ -2,7 +2,8 @@ defmodule RDF.XSD.NegativeInteger do use RDF.XSD.Datatype.Restriction, name: "negativeInteger", id: RDF.Utils.Bootstrapping.xsd_iri("negativeInteger"), - base: RDF.XSD.NonPositiveInteger + base: RDF.XSD.NonPositiveInteger, + register: false # core datatypes don't need to be registered def_facet_constraint RDF.XSD.Facets.MaxInclusive, -1 end diff --git a/lib/rdf/xsd/datatypes/non_negative_integer.ex b/lib/rdf/xsd/datatypes/non_negative_integer.ex index 3129cdf..547ddca 100644 --- a/lib/rdf/xsd/datatypes/non_negative_integer.ex +++ b/lib/rdf/xsd/datatypes/non_negative_integer.ex @@ -2,7 +2,8 @@ defmodule RDF.XSD.NonNegativeInteger do use RDF.XSD.Datatype.Restriction, name: "nonNegativeInteger", id: RDF.Utils.Bootstrapping.xsd_iri("nonNegativeInteger"), - base: RDF.XSD.Integer + base: RDF.XSD.Integer, + register: false # core datatypes don't need to be registered def_facet_constraint RDF.XSD.Facets.MinInclusive, 0 end diff --git a/lib/rdf/xsd/datatypes/non_positive_integer.ex b/lib/rdf/xsd/datatypes/non_positive_integer.ex index 5048529..9206eb3 100644 --- a/lib/rdf/xsd/datatypes/non_positive_integer.ex +++ b/lib/rdf/xsd/datatypes/non_positive_integer.ex @@ -2,7 +2,8 @@ defmodule RDF.XSD.NonPositiveInteger do use RDF.XSD.Datatype.Restriction, name: "nonPositiveInteger", id: RDF.Utils.Bootstrapping.xsd_iri("nonPositiveInteger"), - base: RDF.XSD.Integer + base: RDF.XSD.Integer, + register: false # core datatypes don't need to be registered def_facet_constraint RDF.XSD.Facets.MaxInclusive, 0 end diff --git a/lib/rdf/xsd/datatypes/positive_integer.ex b/lib/rdf/xsd/datatypes/positive_integer.ex index 1b8117f..dcb5c18 100644 --- a/lib/rdf/xsd/datatypes/positive_integer.ex +++ b/lib/rdf/xsd/datatypes/positive_integer.ex @@ -2,7 +2,8 @@ defmodule RDF.XSD.PositiveInteger do use RDF.XSD.Datatype.Restriction, name: "positiveInteger", id: RDF.Utils.Bootstrapping.xsd_iri("positiveInteger"), - base: RDF.XSD.NonNegativeInteger + base: RDF.XSD.NonNegativeInteger, + register: false # core datatypes don't need to be registered def_facet_constraint RDF.XSD.Facets.MinInclusive, 1 end diff --git a/lib/rdf/xsd/datatypes/short.ex b/lib/rdf/xsd/datatypes/short.ex index 96df409..1ea8a15 100644 --- a/lib/rdf/xsd/datatypes/short.ex +++ b/lib/rdf/xsd/datatypes/short.ex @@ -2,7 +2,8 @@ defmodule RDF.XSD.Short do use RDF.XSD.Datatype.Restriction, name: "short", id: RDF.Utils.Bootstrapping.xsd_iri("short"), - base: RDF.XSD.Int + base: RDF.XSD.Int, + register: false # core datatypes don't need to be registered def_facet_constraint RDF.XSD.Facets.MinInclusive, -32768 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 32767 diff --git a/lib/rdf/xsd/datatypes/string.ex b/lib/rdf/xsd/datatypes/string.ex index 023ff40..1cc9bfc 100644 --- a/lib/rdf/xsd/datatypes/string.ex +++ b/lib/rdf/xsd/datatypes/string.ex @@ -7,7 +7,8 @@ defmodule RDF.XSD.String do use RDF.XSD.Datatype.Primitive, name: "string", - id: RDF.Utils.Bootstrapping.xsd_iri("string") + id: RDF.Utils.Bootstrapping.xsd_iri("string"), + register: false # core datatypes don't need to be registered @impl RDF.XSD.Datatype @spec lexical_mapping(String.t(), Keyword.t()) :: valid_value diff --git a/lib/rdf/xsd/datatypes/time.ex b/lib/rdf/xsd/datatypes/time.ex index 78d4ecc..a85ae3d 100644 --- a/lib/rdf/xsd/datatypes/time.ex +++ b/lib/rdf/xsd/datatypes/time.ex @@ -7,7 +7,8 @@ defmodule RDF.XSD.Time do use RDF.XSD.Datatype.Primitive, name: "time", - id: RDF.Utils.Bootstrapping.xsd_iri("time") + id: RDF.Utils.Bootstrapping.xsd_iri("time"), + register: false # core datatypes don't need to be registered # TODO: Are GMT/UTC actually allowed? Maybe because it is supported by Elixir's Datetime ... @grammar ~r/\A(\d{2}:\d{2}:\d{2}(?:\.\d+)?)((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z/ diff --git a/lib/rdf/xsd/datatypes/unsigned_byte.ex b/lib/rdf/xsd/datatypes/unsigned_byte.ex index 9742bd1..ece87f4 100644 --- a/lib/rdf/xsd/datatypes/unsigned_byte.ex +++ b/lib/rdf/xsd/datatypes/unsigned_byte.ex @@ -2,7 +2,8 @@ defmodule RDF.XSD.UnsignedByte do use RDF.XSD.Datatype.Restriction, name: "unsignedByte", id: RDF.Utils.Bootstrapping.xsd_iri("unsignedByte"), - base: RDF.XSD.UnsignedShort + base: RDF.XSD.UnsignedShort, + register: false # core datatypes don't need to be registered def_facet_constraint RDF.XSD.Facets.MinInclusive, 0 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 255 diff --git a/lib/rdf/xsd/datatypes/unsigned_int.ex b/lib/rdf/xsd/datatypes/unsigned_int.ex index 5864444..63d1ad1 100644 --- a/lib/rdf/xsd/datatypes/unsigned_int.ex +++ b/lib/rdf/xsd/datatypes/unsigned_int.ex @@ -2,7 +2,8 @@ defmodule RDF.XSD.UnsignedInt do use RDF.XSD.Datatype.Restriction, name: "unsignedInt", id: RDF.Utils.Bootstrapping.xsd_iri("unsignedInt"), - base: RDF.XSD.UnsignedLong + base: RDF.XSD.UnsignedLong, + register: false # core datatypes don't need to be registered def_facet_constraint RDF.XSD.Facets.MinInclusive, 0 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 4_294_967_295 diff --git a/lib/rdf/xsd/datatypes/unsigned_long.ex b/lib/rdf/xsd/datatypes/unsigned_long.ex index f7d0d8c..9aed230 100644 --- a/lib/rdf/xsd/datatypes/unsigned_long.ex +++ b/lib/rdf/xsd/datatypes/unsigned_long.ex @@ -2,7 +2,8 @@ defmodule RDF.XSD.UnsignedLong do use RDF.XSD.Datatype.Restriction, name: "unsignedLong", id: RDF.Utils.Bootstrapping.xsd_iri("unsignedLong"), - base: RDF.XSD.NonNegativeInteger + base: RDF.XSD.NonNegativeInteger, + register: false # core datatypes don't need to be registered def_facet_constraint RDF.XSD.Facets.MinInclusive, 0 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 18_446_744_073_709_551_615 diff --git a/lib/rdf/xsd/datatypes/unsigned_short.ex b/lib/rdf/xsd/datatypes/unsigned_short.ex index baa2d72..cf8e84f 100644 --- a/lib/rdf/xsd/datatypes/unsigned_short.ex +++ b/lib/rdf/xsd/datatypes/unsigned_short.ex @@ -2,7 +2,8 @@ defmodule RDF.XSD.UnsignedShort do use RDF.XSD.Datatype.Restriction, name: "unsignedShort", id: RDF.Utils.Bootstrapping.xsd_iri("unsignedShort"), - base: RDF.XSD.UnsignedInt + base: RDF.XSD.UnsignedInt, + register: false # core datatypes don't need to be registered def_facet_constraint RDF.XSD.Facets.MinInclusive, 0 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 65535 diff --git a/mix.exs b/mix.exs index c8f5529..9e0d260 100644 --- a/mix.exs +++ b/mix.exs @@ -14,6 +14,7 @@ defmodule RDF.Mixfile do start_permanent: Mix.env == :prod, deps: deps(), elixirc_paths: elixirc_paths(Mix.env()), + compilers: Mix.compilers ++ [:protocol_ex], # Dialyzer dialyzer: dialyzer(), @@ -68,6 +69,7 @@ defmodule RDF.Mixfile do defp deps do [ {:decimal, "~> 1.5"}, + {:protocol_ex, "~> 0.4"}, {:credo, "~> 1.3", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 1.0.0-rc.7", only: :dev, runtime: false}, diff --git a/mix.lock b/mix.lock index be0ba32..d56712b 100644 --- a/mix.lock +++ b/mix.lock @@ -20,6 +20,7 @@ "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, + "protocol_ex": {:hex, :protocol_ex, "0.4.3", "4acbe35da85109dc40315c1139bb7a65ebc7fc102d384cd8b3038384fbb9b282", [:mix], [], "hexpm", "6ca5ddb3505c9c86f17cd3f19838b34bf89966ae17078f79f81983b6a4391fe9"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm", "13104d7897e38ed7f044c4de953a6c28597d1c952075eb2e328bc6d6f2bfc496"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"}, } diff --git a/test/support/test_datatypes.ex b/test/support/test_datatypes.ex new file mode 100644 index 0000000..b21802c --- /dev/null +++ b/test/support/test_datatypes.ex @@ -0,0 +1,10 @@ +defmodule RDF.TestDatatypes do + defmodule Age do + use RDF.XSD.Datatype.Restriction, + name: "age", + id: "http://example.com/Age", + base: RDF.XSD.PositiveInteger + + def_facet_constraint RDF.XSD.Facets.MaxInclusive, 150 + end +end diff --git a/test/unit/literal/datatype/registry_test.exs b/test/unit/literal/datatype/registry_test.exs index 6ad93f8..0e225d7 100644 --- a/test/unit/literal/datatype/registry_test.exs +++ b/test/unit/literal/datatype/registry_test.exs @@ -1,6 +1,7 @@ defmodule RDF.Literal.Datatype.RegistryTest do - use ExUnit.Case + use RDF.Test.Case + alias RDF.TestDatatypes.Age alias RDF.Literal.Datatype alias RDF.NS @@ -36,20 +37,40 @@ defmodule RDF.Literal.Datatype.RegistryTest do describe "get/1" do - test "IRIs of supported datatypes from the XSD namespace" do - Enum.each(@supported_xsd_datatypes, fn xsd_datatype_iri -> - assert xsd_datatype = Datatype.Registry.get(xsd_datatype_iri) - assert xsd_datatype == Datatype.Registry.get(to_string(xsd_datatype_iri)) - assert RDF.iri(xsd_datatype.id) == xsd_datatype_iri - + test "core datatypes" do + Enum.each(Datatype.Registry.core_datatypes(), fn datatype -> + assert datatype == Datatype.Registry.get(datatype.id) + assert datatype == Datatype.Registry.get(to_string(datatype.id)) end) end - test "IRIs of unsupported datatypes from the XSD namespace" do + test "supported datatypes from the XSD namespace" do + Enum.each(@supported_xsd_datatypes, fn xsd_datatype_iri -> + assert xsd_datatype = Datatype.Registry.get(xsd_datatype_iri) + assert xsd_datatype.id == xsd_datatype_iri + end) + end + + test "unsupported datatypes from the XSD namespace" do Enum.each(@unsupported_xsd_datatypes, fn xsd_datatype_iri -> refute Datatype.Registry.get(xsd_datatype_iri) refute Datatype.Registry.get(to_string(xsd_datatype_iri)) end) end + + test "with IRI of custom datatype" do + assert Age == Datatype.Registry.get(Age.id) + end + + test "with namespace terms" do + assert Age == Datatype.Registry.get(EX.Age) + end + end + + test "core datatypes are handled differently and should not be registered (for performance reasons)" do + Enum.each(Datatype.Registry.core_datatypes(), fn datatype -> + refute Datatype.Registry.Registration.datatype(datatype.id) + refute Datatype.Registry.Registration.datatype(to_string(datatype.id)) + end) end end diff --git a/test/unit/literal_test.exs b/test/unit/literal_test.exs index d20c9fe..b04085e 100644 --- a/test/unit/literal_test.exs +++ b/test/unit/literal_test.exs @@ -1,7 +1,6 @@ defmodule RDF.LiteralTest do - use ExUnit.Case + use RDF.Test.Case - import RDF.Sigils import RDF.TestLiterals alias RDF.{Literal, XSD, LangString} @@ -31,13 +30,18 @@ defmodule RDF.LiteralTest do end end - test "with typed literals" do - Enum.each Datatype.Registry.datatypes(), fn datatype -> + test "with core datatype literals" do + Enum.each Datatype.Registry.core_datatypes(), fn datatype -> datatype_literal = datatype.new("foo").literal assert %Literal{literal: ^datatype_literal} = Literal.new(datatype_literal) end end + test "with custom datatype literals" do + datatype_literal = RDF.TestDatatypes.Age.new(42).literal + assert %Literal{literal: typed_literal} = Literal.new(datatype_literal) + end + test "when options without datatype given" do assert Literal.new(true, []) == XSD.Boolean.new(true) assert Literal.new(42, []) == XSD.Integer.new(42) @@ -80,6 +84,10 @@ defmodule RDF.LiteralTest do assert Literal.new("foo", datatype: NS.XSD.string) == XSD.String.new("foo") end + test "registered custom datatype" do + assert Literal.new(42, datatype: EX.Age) == RDF.TestDatatypes.Age.new(42) + end + test "unmapped/unknown datatype" do assert Literal.new("custom typed value", datatype: "http://example/dt") == Generic.new("custom typed value", datatype: "http://example/dt")