Support derived datatypes on RDF.Literal.Datatype.value/1 and valid?/2

This commit is contained in:
Marcel Otto 2020-05-24 21:13:04 +02:00
parent 7daf494fb9
commit d247e1bf4f
6 changed files with 71 additions and 2 deletions

View file

@ -22,6 +22,14 @@ defmodule RDF.Triple.InvalidPredicateError do
end
end
defmodule RDF.XSD.Datatype.Mismatch do
defexception [:value, :expected_type]
def message(%{value: value, expected_type: expected_type}) do
"'#{inspect(value)}' is not a #{expected_type}"
end
end
defmodule RDF.Quad.InvalidGraphContextError do
defexception [:graph_context]

View file

@ -60,6 +60,8 @@ defmodule RDF.Literal.Datatype do
@doc """
Returns the value of a `RDF.Literal`.
This function also accepts literals of derived datatypes.
"""
@callback value(Literal.t | literal) :: any
@ -84,12 +86,14 @@ defmodule RDF.Literal.Datatype do
Determines if the lexical form of a `RDF.Literal` is the canonical form.
Note: For `RDF.Literal.Generic` literals with the canonical form not defined,
this always return `true`.
this always returns `true`.
"""
@callback canonical?(Literal.t() | literal | any) :: boolean
@doc """
Determines if the lexical form of a `RDF.Literal` is a member of its lexical value space.
This function also accepts literals of derived datatypes.
"""
@callback valid?(Literal.t() | literal | any) :: boolean

View file

@ -110,6 +110,15 @@ defmodule RDF.XSD.Datatype do
end
def datatype?(_), do: false
@doc false
def datatype!(%__MODULE__{}), do: true
def datatype!((%datatype{} = literal)) do
datatype?(datatype) ||
raise RDF.XSD.Datatype.Mismatch, value: literal, expected_type: __MODULE__
end
def datatype!(value),
do: raise RDF.XSD.Datatype.Mismatch, value: value, expected_type: __MODULE__
# Dialyzer causes a warning on all primitives since the facet_conform?/2 call
# always returns true there, so the other branch is unnecessary. This could
# be fixed by generating a special version for primitives, but it's not worth
@ -190,6 +199,12 @@ defmodule RDF.XSD.Datatype do
def value(%RDF.Literal{literal: literal}), do: value(literal)
def value(%__MODULE__{} = literal), do: literal.value
def value(literal) do
datatype!(literal)
literal.value
end
@impl RDF.Literal.Datatype
def lexical(lexical)
@ -226,6 +241,8 @@ defmodule RDF.XSD.Datatype do
def valid?(%RDF.Literal{literal: literal}), do: valid?(literal)
def valid?(%__MODULE__{value: @invalid_value}), do: false
def valid?(%__MODULE__{}), do: true
def valid?((%datatype{} = literal)),
do: datatype?(datatype) and datatype.valid?(literal)
def valid?(_), do: false
defimpl Inspect do

View file

@ -36,6 +36,9 @@ defmodule RDF.TestDatatypes do
base: RDF.XSD.PositiveInteger
def_facet_constraint RDF.XSD.Facets.MaxInclusive, 150
@impl RDF.XSD.Datatype
def canonical_mapping(value), do: "#{value} years"
end
defmodule DecimalUnitInterval do

View file

@ -23,6 +23,43 @@ defmodule RDF.XSD.IntegerTest do
valid: RDF.XSD.TestData.valid_integers(),
invalid: RDF.XSD.TestData.invalid_integers()
alias RDF.TestDatatypes.Age
describe "value/1" do
test "with a derived datatype" do
assert XSD.byte(42) |> XSD.Integer.value() == 42
assert Age.new(42) |> XSD.Integer.value() == 42
end
test "with another datatype" do
assert_raise RDF.XSD.Datatype.Mismatch, "'#{inspect(XSD.decimal(42).literal)}' is not a #{XSD.Integer}", fn ->
XSD.decimal(42) |> XSD.Integer.value()
end
end
test "with a non-literal" do
assert_raise RDF.XSD.Datatype.Mismatch, "'42' is not a #{XSD.Integer}", fn ->
XSD.Integer.value(42)
end
end
end
describe "valid?/1" do
test "with a derived datatype" do
assert XSD.byte(42) |> XSD.Integer.valid?() == true
assert Age.new(42) |> XSD.Integer.valid?() == true
assert Age.new(200) |> XSD.Integer.valid?() == false
end
test "with another datatype" do
assert XSD.decimal(42) |> XSD.Integer.valid?() == false
end
test "with a non-literal" do
assert XSD.Integer.valid?(42) == false
end
end
describe "cast/1" do
test "casting an integer returns the input as it is" do
assert XSD.integer(0) |> XSD.Integer.cast() == XSD.integer(0)

View file

@ -121,7 +121,7 @@ defmodule RDF.XSD.StringTest do
test "from derived types of the castable datatypes" do
assert XSD.byte(42) |> XSD.String.cast() == XSD.string("42")
assert Age.new(42) |> XSD.String.cast() == XSD.string("42")
assert Age.new(42) |> XSD.String.cast() == XSD.string("42 years")
assert DecimalUnitInterval.new(0.14) |> XSD.String.cast() == XSD.string("0.14")
assert DoubleUnitInterval.new(0.14) |> XSD.String.cast() == XSD.string("0.14")
assert FloatUnitInterval.new(1.0) |> XSD.String.cast() == XSD.string("1")