Add pattern XSD datatype facet

This commit is contained in:
Marcel Otto 2020-05-21 17:54:38 +02:00
parent 80b1382fa9
commit c2a28b3eb4
35 changed files with 204 additions and 21 deletions

View file

@ -19,6 +19,7 @@ defmodule RDF.XSD.AnyURI do
def_applicable_facet XSD.Facets.MinLength
def_applicable_facet XSD.Facets.MaxLength
def_applicable_facet XSD.Facets.Length
def_applicable_facet XSD.Facets.Pattern
@doc false
def min_length_conform?(min_length, _value, lexical) do
@ -35,6 +36,11 @@ defmodule RDF.XSD.AnyURI do
String.length(lexical) == length
end
@doc false
def pattern_conform?(pattern, _value, lexical) do
XSD.Facets.Pattern.conform?(pattern, lexical)
end
@impl XSD.Datatype
@spec lexical_mapping(String.t(), Keyword.t()) :: valid_value

View file

@ -12,6 +12,15 @@ defmodule RDF.XSD.Boolean do
alias RDF.XSD
def_applicable_facet XSD.Facets.Pattern
@doc false
def pattern_conform?(pattern, _value, lexical) do
XSD.Facets.Pattern.conform?(pattern, lexical)
end
@impl XSD.Datatype
def lexical_mapping(lexical, _) do
with lexical do

View file

@ -15,6 +15,15 @@ defmodule RDF.XSD.Date do
alias RDF.XSD
def_applicable_facet XSD.Facets.Pattern
@doc false
def pattern_conform?(pattern, _value, lexical) do
XSD.Facets.Pattern.conform?(pattern, lexical)
end
# TODO: Are GMT/UTC actually allowed? Maybe because it is supported by Elixir's Datetime ...
@grammar ~r/\A(-?\d{4}-\d{2}-\d{2})((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z/
@tz_grammar ~r/\A((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)\Z/

View file

@ -11,6 +11,15 @@ defmodule RDF.XSD.DateTime do
alias RDF.XSD
def_applicable_facet XSD.Facets.Pattern
@doc false
def pattern_conform?(pattern, _value, lexical) do
XSD.Facets.Pattern.conform?(pattern, lexical)
end
@impl XSD.Datatype
def lexical_mapping(lexical, opts) do
case DateTime.from_iso8601(lexical) do

View file

@ -17,6 +17,7 @@ defmodule RDF.XSD.Decimal do
def_applicable_facet XSD.Facets.MaxInclusive
def_applicable_facet XSD.Facets.MinExclusive
def_applicable_facet XSD.Facets.MaxExclusive
def_applicable_facet XSD.Facets.Pattern
@doc false
def min_inclusive_conform?(min_inclusive, value, _lexical) do
@ -38,6 +39,11 @@ defmodule RDF.XSD.Decimal do
D.cmp(value, D.new(max_exclusive)) == :lt
end
@doc false
def pattern_conform?(pattern, _value, lexical) do
XSD.Facets.Pattern.conform?(pattern, lexical)
end
@impl XSD.Datatype
def lexical_mapping(lexical, opts) do

View file

@ -14,10 +14,12 @@ defmodule RDF.XSD.Double do
alias RDF.XSD
def_applicable_facet XSD.Facets.MinInclusive
def_applicable_facet XSD.Facets.MaxInclusive
def_applicable_facet XSD.Facets.MinExclusive
def_applicable_facet XSD.Facets.MaxExclusive
def_applicable_facet XSD.Facets.Pattern
@doc false
def min_inclusive_conform?(min_inclusive, value, _lexical) do
@ -39,6 +41,11 @@ defmodule RDF.XSD.Double do
value < max_exclusive
end
@doc false
def pattern_conform?(pattern, _value, lexical) do
XSD.Facets.Pattern.conform?(pattern, lexical)
end
@impl XSD.Datatype
def lexical_mapping(lexical, opts) do

View file

@ -14,11 +14,12 @@ defmodule RDF.XSD.Integer do
alias RDF.XSD
def_applicable_facet XSD.Facets.MinInclusive
def_applicable_facet XSD.Facets.MaxInclusive
def_applicable_facet XSD.Facets.MinExclusive
def_applicable_facet XSD.Facets.MaxExclusive
def_applicable_facet XSD.Facets.Pattern
@doc false
def min_inclusive_conform?(min_inclusive, value, _lexical) do
@ -40,6 +41,12 @@ defmodule RDF.XSD.Integer do
value < max_exclusive
end
@doc false
def pattern_conform?(pattern, _value, lexical) do
XSD.Facets.Pattern.conform?(pattern, lexical)
end
@impl XSD.Datatype
def lexical_mapping(lexical, _) do
case Integer.parse(lexical) do

View file

@ -11,9 +11,11 @@ defmodule RDF.XSD.String do
alias RDF.XSD
def_applicable_facet XSD.Facets.MinLength
def_applicable_facet XSD.Facets.MaxLength
def_applicable_facet XSD.Facets.Length
def_applicable_facet XSD.Facets.Pattern
@doc false
def min_length_conform?(min_length, value, _lexical) do
@ -30,6 +32,12 @@ defmodule RDF.XSD.String do
String.length(value) == length
end
@doc false
def pattern_conform?(pattern, value, _lexical) do
XSD.Facets.Pattern.conform?(pattern, value)
end
@impl XSD.Datatype
@spec lexical_mapping(String.t(), Keyword.t()) :: valid_value
def lexical_mapping(lexical, _), do: to_string(lexical)

View file

@ -15,6 +15,15 @@ defmodule RDF.XSD.Time do
@grammar ~r/\A(\d{2}:\d{2}:\d{2}(?:\.\d+)?)((?:[\+\-]\d{2}:\d{2})|UTC|GMT|Z)?\Z/
@tz_number_grammar ~r/\A(?:([\+\-])(\d{2}):(\d{2}))\Z/
def_applicable_facet XSD.Facets.Pattern
@doc false
def pattern_conform?(pattern, _value, lexical) do
XSD.Facets.Pattern.conform?(pattern, lexical)
end
@impl XSD.Datatype
def lexical_mapping(lexical, opts) do
case Regex.run(@grammar, lexical) do

View file

@ -0,0 +1,16 @@
defmodule RDF.XSD.Facets.Pattern do
use RDF.XSD.Facet, name: :pattern, type: String.t | [String.t]
@doc !"""
A generic implementation for the `pattern_conform?/3` on the datatypes.
"""
def conform?(pattern, lexical)
def conform?(patterns, lexical) when is_list(patterns) do
Enum.any?(patterns, &(conform?(&1, lexical)))
end
def conform?(pattern, lexical) do
RDF.XSD.Utils.Regex.matches?(lexical, pattern)
end
end

View file

@ -8,6 +8,27 @@ defmodule RDF.TestDatatypes do
def_facet_constraint RDF.XSD.Facets.Length, 2
end
defmodule UsZipcode do
use RDF.XSD.Datatype.Restriction,
name: "us_zipcode",
id: "http://example.com/us-zipcode",
base: RDF.XSD.String
def_facet_constraint RDF.XSD.Facets.Pattern, "[0-9]{5}(-[0-9]{4})?"
end
defmodule AltUsZipcode do
use RDF.XSD.Datatype.Restriction,
name: "alt_us_zipcode",
id: "http://example.com/alt-us-zipcode",
base: RDF.XSD.String
def_facet_constraint RDF.XSD.Facets.Pattern, [
"[0-9]{5}",
"[0-9]{5}-[0-9]{4}",
]
end
defmodule Age do
use RDF.XSD.Datatype.Restriction,
name: "age",

View file

@ -1,8 +1,6 @@
defmodule RDF.EqualityTest do
use RDF.Test.Case
alias RDF.XSD
describe "RDF.IRI and XSD.AnyURI" do
@term_equal_iris [
{RDF.iri("http://example.com/"), RDF.iri("http://example.com/")},

View file

@ -7,11 +7,13 @@ defmodule RDF.XSD.AnyURITest do
RDF.XSD.Facets.MinLength,
RDF.XSD.Facets.MaxLength,
RDF.XSD.Facets.Length,
RDF.XSD.Facets.Pattern
],
facets: %{
max_length: nil,
min_length: nil,
length: nil,
pattern: nil
},
valid: %{
# input => { value, lexical, canonicalized }

View file

@ -3,6 +3,12 @@ defmodule RDF.XSD.BooleanTest do
datatype: RDF.XSD.Boolean,
name: "boolean",
primitive: true,
applicable_facets: [
RDF.XSD.Facets.Pattern
],
facets: %{
pattern: nil
},
valid: %{
# input => { value, lexical, canonicalized }
true => {true, nil, "true"},

View file

@ -10,13 +10,14 @@ defmodule RDF.XSD.ByteTest do
RDF.XSD.Facets.MaxInclusive,
RDF.XSD.Facets.MinExclusive,
RDF.XSD.Facets.MaxExclusive,
RDF.XSD.Facets.Pattern
],
facets: %{
min_inclusive: -128,
max_inclusive: 127,
min_exclusive: nil,
max_exclusive: nil
max_exclusive: nil,
pattern: nil
},
valid: RDF.XSD.TestData.valid_bytes(),
invalid: RDF.XSD.TestData.invalid_bytes()

View file

@ -3,6 +3,12 @@ defmodule RDF.XSD.DateTest do
datatype: RDF.XSD.Date,
name: "date",
primitive: true,
applicable_facets: [
RDF.XSD.Facets.Pattern
],
facets: %{
pattern: nil
},
valid: %{
# input => { value, lexical, canonicalized }
~D[2010-01-01] => {~D[2010-01-01], nil, "2010-01-01"},

View file

@ -5,6 +5,12 @@ defmodule RDF.XSD.DateTimeTest do
datatype: RDF.XSD.DateTime,
name: "dateTime",
primitive: true,
applicable_facets: [
RDF.XSD.Facets.Pattern
],
facets: %{
pattern: nil
},
valid: %{
# input => { value, lexical, canonicalized }
dt("2010-01-01T00:00:00Z") => {dt("2010-01-01T00:00:00Z"), nil, "2010-01-01T00:00:00Z"},

View file

@ -11,12 +11,14 @@ defmodule RDF.XSD.DecimalTest do
RDF.XSD.Facets.MaxInclusive,
RDF.XSD.Facets.MinExclusive,
RDF.XSD.Facets.MaxExclusive,
RDF.XSD.Facets.Pattern
],
facets: %{
min_inclusive: nil,
max_inclusive: nil,
min_exclusive: nil,
max_exclusive: nil
max_exclusive: nil,
pattern: nil
},
valid: %{
# input => {value, lexical, canonicalized}

View file

@ -9,12 +9,14 @@ defmodule RDF.XSD.DoubleTest do
RDF.XSD.Facets.MaxInclusive,
RDF.XSD.Facets.MinExclusive,
RDF.XSD.Facets.MaxExclusive,
RDF.XSD.Facets.Pattern
],
facets: %{
min_inclusive: nil,
max_inclusive: nil,
min_exclusive: nil,
max_exclusive: nil
max_exclusive: nil,
pattern: nil
},
valid: RDF.XSD.TestData.valid_floats(),
invalid: RDF.XSD.TestData.invalid_floats()

View file

@ -10,12 +10,14 @@ defmodule RDF.XSD.FloatTest do
RDF.XSD.Facets.MaxInclusive,
RDF.XSD.Facets.MinExclusive,
RDF.XSD.Facets.MaxExclusive,
RDF.XSD.Facets.Pattern
],
facets: %{
min_inclusive: nil,
max_inclusive: nil,
min_exclusive: nil,
max_exclusive: nil
max_exclusive: nil,
pattern: nil
},
valid: RDF.XSD.TestData.valid_floats(),
invalid: RDF.XSD.TestData.invalid_floats()

View file

@ -10,12 +10,14 @@ defmodule RDF.XSD.IntTest do
RDF.XSD.Facets.MaxInclusive,
RDF.XSD.Facets.MinExclusive,
RDF.XSD.Facets.MaxExclusive,
RDF.XSD.Facets.Pattern
],
facets: %{
min_inclusive: -2_147_483_648,
max_inclusive: 2_147_483_647,
min_exclusive: nil,
max_exclusive: nil
max_exclusive: nil,
pattern: nil
},
valid: RDF.XSD.TestData.valid_ints(),
invalid: RDF.XSD.TestData.invalid_ints()

View file

@ -9,12 +9,14 @@ defmodule RDF.XSD.IntegerTest do
RDF.XSD.Facets.MaxInclusive,
RDF.XSD.Facets.MinExclusive,
RDF.XSD.Facets.MaxExclusive,
RDF.XSD.Facets.Pattern
],
facets: %{
min_inclusive: nil,
max_inclusive: nil,
min_exclusive: nil,
max_exclusive: nil
max_exclusive: nil,
pattern: nil
},
valid: RDF.XSD.TestData.valid_integers(),
invalid: RDF.XSD.TestData.invalid_integers()

View file

@ -10,12 +10,14 @@ defmodule RDF.XSD.LongTest do
RDF.XSD.Facets.MaxInclusive,
RDF.XSD.Facets.MinExclusive,
RDF.XSD.Facets.MaxExclusive,
RDF.XSD.Facets.Pattern
],
facets: %{
min_inclusive: -9_223_372_036_854_775_808,
max_inclusive: 9_223_372_036_854_775_807,
min_exclusive: nil,
max_exclusive: nil
max_exclusive: nil,
pattern: nil
},
valid: RDF.XSD.TestData.valid_longs(),
invalid: RDF.XSD.TestData.invalid_longs()

View file

@ -10,12 +10,14 @@ defmodule RDF.XSD.NegativeIntegerTest do
RDF.XSD.Facets.MaxInclusive,
RDF.XSD.Facets.MinExclusive,
RDF.XSD.Facets.MaxExclusive,
RDF.XSD.Facets.Pattern
],
facets: %{
min_inclusive: nil,
max_inclusive: -1,
min_exclusive: nil,
max_exclusive: nil
max_exclusive: nil,
pattern: nil
},
valid: RDF.XSD.TestData.valid_negative_integers(),
invalid: RDF.XSD.TestData.invalid_negative_integers()

View file

@ -10,12 +10,14 @@ defmodule RDF.XSD.NonNegativeIntegerTest do
RDF.XSD.Facets.MaxInclusive,
RDF.XSD.Facets.MinExclusive,
RDF.XSD.Facets.MaxExclusive,
RDF.XSD.Facets.Pattern
],
facets: %{
min_inclusive: 0,
max_inclusive: nil,
min_exclusive: nil,
max_exclusive: nil
max_exclusive: nil,
pattern: nil
},
valid: RDF.XSD.TestData.valid_non_negative_integers(),
invalid: RDF.XSD.TestData.invalid_non_negative_integers()

View file

@ -10,12 +10,14 @@ defmodule RDF.XSD.NonPositiveIntegerTest do
RDF.XSD.Facets.MaxInclusive,
RDF.XSD.Facets.MinExclusive,
RDF.XSD.Facets.MaxExclusive,
RDF.XSD.Facets.Pattern
],
facets: %{
min_inclusive: nil,
max_inclusive: 0,
min_exclusive: nil,
max_exclusive: nil
max_exclusive: nil,
pattern: nil
},
valid: RDF.XSD.TestData.valid_non_positive_integers(),
invalid: RDF.XSD.TestData.invalid_non_positive_integers()

View file

@ -10,12 +10,14 @@ defmodule RDF.XSD.PositiveIntegerTest do
RDF.XSD.Facets.MaxInclusive,
RDF.XSD.Facets.MinExclusive,
RDF.XSD.Facets.MaxExclusive,
RDF.XSD.Facets.Pattern
],
facets: %{
min_inclusive: 1,
max_inclusive: nil,
min_exclusive: nil,
max_exclusive: nil
max_exclusive: nil,
pattern: nil
},
valid: RDF.XSD.TestData.valid_positive_integers(),
invalid: RDF.XSD.TestData.invalid_positive_integers()

View file

@ -10,12 +10,14 @@ defmodule RDF.XSD.ShortTest do
RDF.XSD.Facets.MaxInclusive,
RDF.XSD.Facets.MinExclusive,
RDF.XSD.Facets.MaxExclusive,
RDF.XSD.Facets.Pattern
],
facets: %{
min_inclusive: -32768,
max_inclusive: 32767,
min_exclusive: nil,
max_exclusive: nil
max_exclusive: nil,
pattern: nil
},
valid: RDF.XSD.TestData.valid_shorts(),
invalid: RDF.XSD.TestData.invalid_shorts()

View file

@ -7,11 +7,13 @@ defmodule RDF.XSD.StringTest do
RDF.XSD.Facets.MinLength,
RDF.XSD.Facets.MaxLength,
RDF.XSD.Facets.Length,
RDF.XSD.Facets.Pattern
],
facets: %{
max_length: nil,
min_length: nil,
length: nil
length: nil,
pattern: nil
},
valid: %{
# input => { value, lexical, canonicalized }

View file

@ -3,6 +3,12 @@ defmodule RDF.XSD.TimeTest do
datatype: RDF.XSD.Time,
name: "time",
primitive: true,
applicable_facets: [
RDF.XSD.Facets.Pattern
],
facets: %{
pattern: nil
},
valid: %{
# input => { value, lexical, canonicalized }
~T[00:00:00] => {~T[00:00:00], nil, "00:00:00"},

View file

@ -10,12 +10,14 @@ defmodule RDF.XSD.UnsignedByteTest do
RDF.XSD.Facets.MaxInclusive,
RDF.XSD.Facets.MinExclusive,
RDF.XSD.Facets.MaxExclusive,
RDF.XSD.Facets.Pattern
],
facets: %{
min_inclusive: 0,
max_inclusive: 255,
min_exclusive: nil,
max_exclusive: nil
max_exclusive: nil,
pattern: nil
},
valid: RDF.XSD.TestData.valid_unsigned_bytes(),
invalid: RDF.XSD.TestData.invalid_unsigned_bytes()

View file

@ -10,12 +10,14 @@ defmodule RDF.XSD.UnsignedIntTest do
RDF.XSD.Facets.MaxInclusive,
RDF.XSD.Facets.MinExclusive,
RDF.XSD.Facets.MaxExclusive,
RDF.XSD.Facets.Pattern
],
facets: %{
min_inclusive: 0,
max_inclusive: 4_294_967_295,
min_exclusive: nil,
max_exclusive: nil
max_exclusive: nil,
pattern: nil
},
valid: RDF.XSD.TestData.valid_unsigned_ints(),
invalid: RDF.XSD.TestData.invalid_unsigned_ints()

View file

@ -10,12 +10,14 @@ defmodule RDF.XSD.UnsignedLongTest do
RDF.XSD.Facets.MaxInclusive,
RDF.XSD.Facets.MinExclusive,
RDF.XSD.Facets.MaxExclusive,
RDF.XSD.Facets.Pattern
],
facets: %{
min_inclusive: 0,
max_inclusive: 18_446_744_073_709_551_615,
min_exclusive: nil,
max_exclusive: nil
max_exclusive: nil,
pattern: nil
},
valid: RDF.XSD.TestData.valid_unsigned_longs(),
invalid: RDF.XSD.TestData.invalid_unsigned_longs()

View file

@ -10,12 +10,14 @@ defmodule RDF.XSD.UnsignedShortTest do
RDF.XSD.Facets.MaxInclusive,
RDF.XSD.Facets.MinExclusive,
RDF.XSD.Facets.MaxExclusive,
RDF.XSD.Facets.Pattern
],
facets: %{
min_inclusive: 0,
max_inclusive: 65535,
min_exclusive: nil,
max_exclusive: nil
max_exclusive: nil,
pattern: nil
},
valid: RDF.XSD.TestData.valid_unsigned_shorts(),
invalid: RDF.XSD.TestData.invalid_unsigned_shorts()

View file

@ -0,0 +1,19 @@
defmodule RDF.XSD.Facets.PatternTest do
use RDF.Test.Case
alias RDF.TestDatatypes.{UsZipcode, AltUsZipcode}
test "with one pattern" do
assert UsZipcode.new("20521") |> RDF.Literal.valid?()
assert UsZipcode.new("20521-9000") |> RDF.Literal.valid?()
refute UsZipcode.new("2052") |> RDF.Literal.valid?()
refute UsZipcode.new("foo") |> RDF.Literal.valid?()
end
test "with multiple patterns" do
assert AltUsZipcode.new("20521") |> RDF.Literal.valid?()
assert AltUsZipcode.new("20521-9000") |> RDF.Literal.valid?()
refute AltUsZipcode.new("2052") |> RDF.Literal.valid?()
refute AltUsZipcode.new("foo") |> RDF.Literal.valid?()
end
end