Add ProtocolEx-based custom datatype registration

This commit is contained in:
Marcel Otto 2020-05-07 15:37:21 +02:00
parent 4fedb2cfc0
commit faaebb2de2
33 changed files with 182 additions and 60 deletions

View file

@ -209,10 +209,7 @@ defmodule RDF do
defdelegate literal(value), to: Literal, as: :new defdelegate literal(value), to: Literal, as: :new
defdelegate literal(value, opts), to: Literal, as: :new defdelegate literal(value, opts), to: Literal, as: :new
def literal?(%Literal{}), do: true defdelegate literal?(literal), to: Literal.Datatype.Registry
def literal?(%RDF.Literal.Generic{}), do: true
def literal?(%datatype{}), do: Literal.Datatype.Registry.datatype?(datatype)
def literal?(_), do: false
defdelegate triple(s, p, o), to: Triple, as: :new defdelegate triple(s, p, o), to: Triple, as: :new
defdelegate triple(tuple), to: Triple, as: :new defdelegate triple(tuple), to: Triple, as: :new

View file

@ -92,12 +92,6 @@ defmodule RDF.Literal do
def coerce(%__MODULE__{} = literal), do: literal 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_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_boolean(value), do: RDF.XSD.Boolean.new(value)
def coerce(value) when is_integer(value), do: RDF.XSD.Integer.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(%DateTime{} = value), do: RDF.XSD.DateTime.new(value)
def coerce(%NaiveDateTime{} = 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(%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 """ @doc """

View file

@ -140,6 +140,17 @@ defmodule RDF.Literal.Datatype do
defmacro __using__(opts) do defmacro __using__(opts) do
name = Keyword.fetch!(opts, :name) name = Keyword.fetch!(opts, :name)
id = Keyword.fetch!(opts, :id) 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 quote do
@behaviour unquote(__MODULE__) @behaviour unquote(__MODULE__)
@ -258,6 +269,15 @@ defmodule RDF.Literal.Datatype do
literal.__struct__.lexical(literal) literal.__struct__.lexical(literal)
end end
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 end
end end

View file

@ -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 defmodule RDF.Literal.Datatype.Registry do
@moduledoc false @moduledoc false
alias RDF.{Literal, IRI, XSD} 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 """ @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} @spec core_ids :: [IRI.t]
def mapping, do: @mapping def core_ids, do: Map.keys(@mapping)
@doc """ @doc """
The IRIs of all datatypes with a `RDF.Literal.Datatype` defined. All core `RDF.Literal.Datatype` modules.
""" """
@spec ids :: [IRI.t] @spec core_datatypes :: Enum.t
def ids, do: Map.keys(@mapping) def core_datatypes, do: @core_datatypes
@doc """ @doc """
All defined `RDF.Literal.Datatype` modules. Checks if the given module is core datatype.
""" """
@spec datatypes :: Enum.t @spec core_datatype?(module) :: boolean
def datatypes, do: @datatypes 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 @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 """ @doc """
Returns the `RDF.Literal.Datatype` for a directly datatype IRI or the datatype IRI of a `RDF.Literal`. 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 @spec get(Literal.t | IRI.t | String.t) :: Literal.Datatype.t
def get(%Literal{} = literal), do: Literal.datatype(literal) def get(%Literal{} = literal), do: Literal.datatype(literal)
def get(id) when is_binary(id), do: id |> IRI.new() |> get() 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 end

View file

@ -0,0 +1,5 @@
import ProtocolEx
defprotocol_ex RDF.Literal.Datatype.Registry.Registration do
def datatype(id), do: nil
end

View file

@ -7,7 +7,8 @@ defmodule RDF.LangString do
use RDF.Literal.Datatype, use RDF.Literal.Datatype,
name: "langString", 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.Datatype
alias RDF.Literal alias RDF.Literal

View file

@ -11,7 +11,8 @@ defmodule RDF.XSD.AnyURI do
use RDF.XSD.Datatype.Primitive, use RDF.XSD.Datatype.Primitive,
name: "anyURI", 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 @impl RDF.XSD.Datatype
@spec lexical_mapping(String.t(), Keyword.t()) :: valid_value @spec lexical_mapping(String.t(), Keyword.t()) :: valid_value

View file

@ -8,7 +8,8 @@ defmodule RDF.XSD.Boolean do
use RDF.XSD.Datatype.Primitive, use RDF.XSD.Datatype.Primitive,
name: "boolean", 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 @impl RDF.XSD.Datatype
def lexical_mapping(lexical, _) do def lexical_mapping(lexical, _) do

View file

@ -2,7 +2,8 @@ defmodule RDF.XSD.Byte do
use RDF.XSD.Datatype.Restriction, use RDF.XSD.Datatype.Restriction,
name: "byte", name: "byte",
id: RDF.Utils.Bootstrapping.xsd_iri("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.MinInclusive, -128
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 127 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 127

View file

@ -11,7 +11,8 @@ defmodule RDF.XSD.Date do
use RDF.XSD.Datatype.Primitive, use RDF.XSD.Datatype.Primitive,
name: "date", 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 ... # TODO: Are GMT/UTC actually allowed? Maybe because it is supported by Elixir's Datetime ...

View file

@ -7,7 +7,8 @@ defmodule RDF.XSD.DateTime do
use RDF.XSD.Datatype.Primitive, use RDF.XSD.Datatype.Primitive,
name: "dateTime", 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 @impl RDF.XSD.Datatype
def lexical_mapping(lexical, opts) do def lexical_mapping(lexical, opts) do

View file

@ -7,7 +7,8 @@ defmodule RDF.XSD.Decimal do
use RDF.XSD.Datatype.Primitive, use RDF.XSD.Datatype.Primitive,
name: "decimal", 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 alias Elixir.Decimal, as: D

View file

@ -8,7 +8,8 @@ defmodule RDF.XSD.Double do
use RDF.XSD.Datatype.Primitive, use RDF.XSD.Datatype.Primitive,
name: "double", 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 @special_values ~W[positive_infinity negative_infinity nan]a

View file

@ -9,5 +9,6 @@ defmodule RDF.XSD.Float do
use RDF.XSD.Datatype.Restriction, use RDF.XSD.Datatype.Restriction,
name: "float", name: "float",
id: RDF.Utils.Bootstrapping.xsd_iri("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 end

View file

@ -2,7 +2,8 @@ defmodule RDF.XSD.Int do
use RDF.XSD.Datatype.Restriction, use RDF.XSD.Datatype.Restriction,
name: "int", name: "int",
id: RDF.Utils.Bootstrapping.xsd_iri("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.MinInclusive, -2_147_483_648
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 2_147_483_647 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 2_147_483_647

View file

@ -10,7 +10,8 @@ defmodule RDF.XSD.Integer do
use RDF.XSD.Datatype.Primitive, use RDF.XSD.Datatype.Primitive,
name: "integer", 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.MinInclusive
def_applicable_facet RDF.XSD.Facets.MaxInclusive def_applicable_facet RDF.XSD.Facets.MaxInclusive

View file

@ -2,7 +2,8 @@ defmodule RDF.XSD.Long do
use RDF.XSD.Datatype.Restriction, use RDF.XSD.Datatype.Restriction,
name: "long", name: "long",
id: RDF.Utils.Bootstrapping.xsd_iri("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.MinInclusive, -9_223_372_036_854_775_808
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 9_223_372_036_854_775_807 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 9_223_372_036_854_775_807

View file

@ -2,7 +2,8 @@ defmodule RDF.XSD.NegativeInteger do
use RDF.XSD.Datatype.Restriction, use RDF.XSD.Datatype.Restriction,
name: "negativeInteger", name: "negativeInteger",
id: RDF.Utils.Bootstrapping.xsd_iri("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 def_facet_constraint RDF.XSD.Facets.MaxInclusive, -1
end end

View file

@ -2,7 +2,8 @@ defmodule RDF.XSD.NonNegativeInteger do
use RDF.XSD.Datatype.Restriction, use RDF.XSD.Datatype.Restriction,
name: "nonNegativeInteger", name: "nonNegativeInteger",
id: RDF.Utils.Bootstrapping.xsd_iri("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 def_facet_constraint RDF.XSD.Facets.MinInclusive, 0
end end

View file

@ -2,7 +2,8 @@ defmodule RDF.XSD.NonPositiveInteger do
use RDF.XSD.Datatype.Restriction, use RDF.XSD.Datatype.Restriction,
name: "nonPositiveInteger", name: "nonPositiveInteger",
id: RDF.Utils.Bootstrapping.xsd_iri("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 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 0
end end

View file

@ -2,7 +2,8 @@ defmodule RDF.XSD.PositiveInteger do
use RDF.XSD.Datatype.Restriction, use RDF.XSD.Datatype.Restriction,
name: "positiveInteger", name: "positiveInteger",
id: RDF.Utils.Bootstrapping.xsd_iri("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 def_facet_constraint RDF.XSD.Facets.MinInclusive, 1
end end

View file

@ -2,7 +2,8 @@ defmodule RDF.XSD.Short do
use RDF.XSD.Datatype.Restriction, use RDF.XSD.Datatype.Restriction,
name: "short", name: "short",
id: RDF.Utils.Bootstrapping.xsd_iri("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.MinInclusive, -32768
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 32767 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 32767

View file

@ -7,7 +7,8 @@ defmodule RDF.XSD.String do
use RDF.XSD.Datatype.Primitive, use RDF.XSD.Datatype.Primitive,
name: "string", 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 @impl RDF.XSD.Datatype
@spec lexical_mapping(String.t(), Keyword.t()) :: valid_value @spec lexical_mapping(String.t(), Keyword.t()) :: valid_value

View file

@ -7,7 +7,8 @@ defmodule RDF.XSD.Time do
use RDF.XSD.Datatype.Primitive, use RDF.XSD.Datatype.Primitive,
name: "time", 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 ... # 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/ @grammar ~r/\A(\d{2}:\d{2}:\d{2}(?:\.\d+)?)((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z/

View file

@ -2,7 +2,8 @@ defmodule RDF.XSD.UnsignedByte do
use RDF.XSD.Datatype.Restriction, use RDF.XSD.Datatype.Restriction,
name: "unsignedByte", name: "unsignedByte",
id: RDF.Utils.Bootstrapping.xsd_iri("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.MinInclusive, 0
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 255 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 255

View file

@ -2,7 +2,8 @@ defmodule RDF.XSD.UnsignedInt do
use RDF.XSD.Datatype.Restriction, use RDF.XSD.Datatype.Restriction,
name: "unsignedInt", name: "unsignedInt",
id: RDF.Utils.Bootstrapping.xsd_iri("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.MinInclusive, 0
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 4_294_967_295 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 4_294_967_295

View file

@ -2,7 +2,8 @@ defmodule RDF.XSD.UnsignedLong do
use RDF.XSD.Datatype.Restriction, use RDF.XSD.Datatype.Restriction,
name: "unsignedLong", name: "unsignedLong",
id: RDF.Utils.Bootstrapping.xsd_iri("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.MinInclusive, 0
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 18_446_744_073_709_551_615 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 18_446_744_073_709_551_615

View file

@ -2,7 +2,8 @@ defmodule RDF.XSD.UnsignedShort do
use RDF.XSD.Datatype.Restriction, use RDF.XSD.Datatype.Restriction,
name: "unsignedShort", name: "unsignedShort",
id: RDF.Utils.Bootstrapping.xsd_iri("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.MinInclusive, 0
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 65535 def_facet_constraint RDF.XSD.Facets.MaxInclusive, 65535

View file

@ -14,6 +14,7 @@ defmodule RDF.Mixfile do
start_permanent: Mix.env == :prod, start_permanent: Mix.env == :prod,
deps: deps(), deps: deps(),
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
compilers: Mix.compilers ++ [:protocol_ex],
# Dialyzer # Dialyzer
dialyzer: dialyzer(), dialyzer: dialyzer(),
@ -68,6 +69,7 @@ defmodule RDF.Mixfile do
defp deps do defp deps do
[ [
{:decimal, "~> 1.5"}, {:decimal, "~> 1.5"},
{:protocol_ex, "~> 0.4"},
{:credo, "~> 1.3", only: [:dev, :test], runtime: false}, {:credo, "~> 1.3", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.0.0-rc.7", only: :dev, runtime: false}, {:dialyxir, "~> 1.0.0-rc.7", only: :dev, runtime: false},

View file

@ -20,6 +20,7 @@
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"}, "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"}, "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"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm", "1d1848c40487cdb0b30e8ed975e34e025860c02e419cb615d255849f3427439d"},
} }

View file

@ -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

View file

@ -1,6 +1,7 @@
defmodule RDF.Literal.Datatype.RegistryTest do defmodule RDF.Literal.Datatype.RegistryTest do
use ExUnit.Case use RDF.Test.Case
alias RDF.TestDatatypes.Age
alias RDF.Literal.Datatype alias RDF.Literal.Datatype
alias RDF.NS alias RDF.NS
@ -36,20 +37,40 @@ defmodule RDF.Literal.Datatype.RegistryTest do
describe "get/1" do describe "get/1" do
test "IRIs of supported datatypes from the XSD namespace" do test "core datatypes" do
Enum.each(@supported_xsd_datatypes, fn xsd_datatype_iri -> Enum.each(Datatype.Registry.core_datatypes(), fn datatype ->
assert xsd_datatype = Datatype.Registry.get(xsd_datatype_iri) assert datatype == Datatype.Registry.get(datatype.id)
assert xsd_datatype == Datatype.Registry.get(to_string(xsd_datatype_iri)) assert datatype == Datatype.Registry.get(to_string(datatype.id))
assert RDF.iri(xsd_datatype.id) == xsd_datatype_iri
end) end)
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 -> Enum.each(@unsupported_xsd_datatypes, fn xsd_datatype_iri ->
refute Datatype.Registry.get(xsd_datatype_iri) refute Datatype.Registry.get(xsd_datatype_iri)
refute Datatype.Registry.get(to_string(xsd_datatype_iri)) refute Datatype.Registry.get(to_string(xsd_datatype_iri))
end) end)
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
end end

View file

@ -1,7 +1,6 @@
defmodule RDF.LiteralTest do defmodule RDF.LiteralTest do
use ExUnit.Case use RDF.Test.Case
import RDF.Sigils
import RDF.TestLiterals import RDF.TestLiterals
alias RDF.{Literal, XSD, LangString} alias RDF.{Literal, XSD, LangString}
@ -31,13 +30,18 @@ defmodule RDF.LiteralTest do
end end
end end
test "with typed literals" do test "with core datatype literals" do
Enum.each Datatype.Registry.datatypes(), fn datatype -> Enum.each Datatype.Registry.core_datatypes(), fn datatype ->
datatype_literal = datatype.new("foo").literal datatype_literal = datatype.new("foo").literal
assert %Literal{literal: ^datatype_literal} = Literal.new(datatype_literal) assert %Literal{literal: ^datatype_literal} = Literal.new(datatype_literal)
end end
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 test "when options without datatype given" do
assert Literal.new(true, []) == XSD.Boolean.new(true) assert Literal.new(true, []) == XSD.Boolean.new(true)
assert Literal.new(42, []) == XSD.Integer.new(42) 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") assert Literal.new("foo", datatype: NS.XSD.string) == XSD.String.new("foo")
end end
test "registered custom datatype" do
assert Literal.new(42, datatype: EX.Age) == RDF.TestDatatypes.Age.new(42)
end
test "unmapped/unknown datatype" do test "unmapped/unknown datatype" do
assert Literal.new("custom typed value", datatype: "http://example/dt") == assert Literal.new("custom typed value", datatype: "http://example/dt") ==
Generic.new("custom typed value", datatype: "http://example/dt") Generic.new("custom typed value", datatype: "http://example/dt")