aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2021-03-08 23:25:35 -0500
committerDavid Robillard <d@drobilla.net>2021-03-09 01:43:52 -0500
commit7b954f5667e82de1b64984a9aeb26b8ebb5cab81 (patch)
tree5668f80ce2dc7a52cf66bbe2f4e4429b18f09e08
parentc579186c5dd4e11bffddd353cef8978a66ef9c10 (diff)
downloadserd1-meson.tar.gz
serd1-meson.tar.bz2
serd1-meson.zip
WIP: Validationserd1-meson
-rw-r--r--.clang-format1
-rw-r--r--bindings/cpp/include/serd/serd.hpp2
-rw-r--r--bindings/cpp/meson.build17
-rw-r--r--bindings/python/serd.pyx20
-rw-r--r--doc/serdi.1103
-rw-r--r--include/serd/serd.h116
-rw-r--r--meson.build23
-rw-r--r--meson/meson.build14
-rw-r--r--schemas/rdf.ttl2
-rw-r--r--schemas/rdfs.ttl2
-rw-r--r--schemas/xsd.ttl98
-rwxr-xr-xscripts/update_man_page.py54
-rw-r--r--serd.ttl154
-rw-r--r--src/iter.c12
-rw-r--r--src/iter.h56
-rw-r--r--src/macros.h1
-rw-r--r--src/model.c111
-rw-r--r--src/n3.c5
-rw-r--r--src/namespaces.h4
-rw-r--r--src/serdi.c64
-rw-r--r--src/string.c4
-rw-r--r--src/validate.c2139
-rw-r--r--src/world.c10
-rw-r--r--subprojects/exess/include/exess/exess.h16
-rw-r--r--subprojects/exess/src/variant.c126
-rw-r--r--test/meson.build2
-rwxr-xr-xtest/run_validation_test_suite.py200
-rw-r--r--test/test_model.c10
-rwxr-xr-xtest/test_quiet.py2
-rw-r--r--test/validate/bad-all-values-from.ttl14
-rw-r--r--test/validate/bad-anyuri.ttl13
-rw-r--r--test/validate/bad-cardinality-high.ttl1
-rw-r--r--test/validate/bad-cardinality-low.ttl1
-rw-r--r--test/validate/bad-cardinality.ttl1
-rw-r--r--test/validate/bad-class-type-undefined.ttl5
-rw-r--r--test/validate/bad-class-type.ttl9
-rw-r--r--test/validate/bad-datatype-property.ttl11
-rw-r--r--test/validate/bad-deprecated-class.ttl12
-rw-r--r--test/validate/bad-deprecated-property.ttl13
-rw-r--r--test/validate/bad-domain.ttl18
-rw-r--r--test/validate/bad-literal-pattern.ttl19
-rw-r--r--test/validate/bad-literal-value-high-exclusive.ttl2
-rw-r--r--test/validate/bad-literal-value-high-inclusive.ttl2
-rw-r--r--test/validate/bad-literal-value-low-exclusive.ttl2
-rw-r--r--test/validate/bad-literal-value-low-inclusive.ttl2
-rw-r--r--test/validate/bad-literal-value.ttl7
-rw-r--r--test/validate/bad-object-property.ttl6
-rw-r--r--test/validate/bad-pattern.ttl12
-rw-r--r--test/validate/bad-plain-literal.ttl2
-rw-r--r--test/validate/bad-predicate-type-undefined.ttl (renamed from test/validate/bad-unknown-property.ttl)1
-rw-r--r--test/validate/bad-predicate-type.ttl9
-rw-r--r--test/validate/bad-range-instance-not-literal.ttl3
-rw-r--r--test/validate/bad-range-instance.ttl6
-rw-r--r--test/validate/bad-range-literal-not-instance.ttl3
-rw-r--r--test/validate/bad-range-literal.ttl12
-rw-r--r--test/validate/bad-some-values-from.ttl1
-rw-r--r--test/validate/bad-string-literal-value-high.ttl2
-rw-r--r--test/validate/bad-string-literal-value-low.ttl2
-rw-r--r--test/validate/bad-subclass-cycle.ttl13
-rw-r--r--test/validate/bad-subproperty-cycle.ttl14
-rw-r--r--test/validate/bad-superclass-restriction.ttl22
-rw-r--r--test/validate/good-anyuri.ttl13
-rw-r--r--test/validate/good-cardinality.ttl1
-rw-r--r--test/validate/good-literal-value-high-inclusive.ttl2
-rw-r--r--test/validate/good-literal-value-low-inclusive.ttl2
-rw-r--r--test/validate/good-owl-thing.ttl16
-rw-r--r--test/validate/good-pattern.ttl2
-rw-r--r--test/validate/good-rdfs-resource.ttl12
-rw-r--r--test/validate/good-some-values-from.ttl1
-rw-r--r--test/validate/good-string-literal-value-low.ttl2
-rw-r--r--test/validate/manifest.ttl226
71 files changed, 2918 insertions, 967 deletions
diff --git a/.clang-format b/.clang-format
index f3917111..3a2231a3 100644
--- a/.clang-format
+++ b/.clang-format
@@ -20,6 +20,7 @@ KeepEmptyLinesAtTheStartOfBlocks: false
SpacesInContainerLiterals: false
ForEachMacros:
- SERD_FOREACH
+ - SERD_FOREACH_NODE
StatementMacros:
- SERD_ALLOCATED
- SERD_API
diff --git a/bindings/cpp/include/serd/serd.hpp b/bindings/cpp/include/serd/serd.hpp
index 95abcc04..80d588c8 100644
--- a/bindings/cpp/include/serd/serd.hpp
+++ b/bindings/cpp/include/serd/serd.hpp
@@ -1626,7 +1626,7 @@ public:
return serd_model_count(cobj(), s.cobj(), p.cobj(), o.cobj(), g.cobj());
}
- Range all() const { return Range(serd_model_all(cobj())); }
+ Range all() const { return Range(serd_model_all(cobj(), SERD_ORDER_SPO)); }
iterator begin() const
{
diff --git a/bindings/cpp/meson.build b/bindings/cpp/meson.build
index 627e537c..b9b2869c 100644
--- a/bindings/cpp/meson.build
+++ b/bindings/cpp/meson.build
@@ -1,19 +1,17 @@
versioned_cpp_name = 'serdxx' + version_suffix
-cpp_prog_args = []
-
# Set ultra strict warnings for developers, if requested
+cpp_suppressions = []
if get_option('strict')
- cpp_warnings = all_cpp_warnings
if cpp.get_id() == 'clang'
- cpp_warnings += [
+ cpp_suppressions += [
'-Wno-documentation-unknown-command',
'-Wno-format-nonliteral',
'-Wno-nullability-extension',
'-Wno-padded',
]
elif cpp.get_id() == 'gcc'
- cpp_warnings += [
+ cpp_suppressions += [
'-Wno-abi-tag',
'-Wno-float-equal',
'-Wno-inline',
@@ -24,7 +22,7 @@ if get_option('strict')
'-Wno-unused-const-variable',
]
elif cpp.get_id() == 'msvc'
- cpp_warnings += [
+ cpp_suppressions += [
'/wd4355', # 'this' used in base member initializer list
'/wd4571', # structured exceptions are no longer caught
'/wd4623', # default constructor implicitly deleted
@@ -36,11 +34,10 @@ if get_option('strict')
'/wd5027', # move assignment operator implicitly deleted
]
endif
-
- cpp_prog_args = cpp.get_supported_arguments(cpp_warnings)
-
endif
+exess_cpp_args = cpp.get_supported_arguments(cpp_suppressions)
+
cpp_headers = [
'include/serd/serd.hpp',
'include/serd/detail/Copyable.hpp',
@@ -70,6 +67,6 @@ test('bindings',
executable('test_serd_hpp',
'test/test_serd_hpp.cpp',
include_directories: include_directories(['include']),
- cpp_args: cpp_prog_args + cpp_test_args,
+ cpp_args: exess_cpp_args + cpp_test_args,
dependencies: [serd_dep, serdxx_dep]),
suite: 'cpp')
diff --git a/bindings/python/serd.pyx b/bindings/python/serd.pyx
index e2ed0c6c..0ec102c2 100644
--- a/bindings/python/serd.pyx
+++ b/bindings/python/serd.pyx
@@ -40,6 +40,7 @@ cdef extern from "serd/serd.h":
ctypedef enum SerdNodeType: pass
ctypedef enum SerdNodeFlag: pass
ctypedef enum SerdField: pass
+ ctypedef enum SerdStatementOrder: pass
ctypedef enum SerdModelFlag: pass
ctypedef uint32_t SerdStatementFlags
@@ -461,7 +462,8 @@ cdef extern from "serd/serd.h":
bint serd_model_empty(const SerdModel* model);
SerdIter* serd_model_begin(const SerdModel* model);
const SerdIter* serd_model_end(const SerdModel* model);
- SerdRange* serd_model_all(const SerdModel* model);
+ SerdRange* serd_model_all(const SerdModel* model,
+ const SerdStatementOrder order);
SerdIter* serd_model_find(const SerdModel* model,
const SerdNode* s,
@@ -607,6 +609,20 @@ class Field(enum.IntEnum):
OBJECT = 2 # Object ("value")
GRAPH = 3 # Graph ("context")
+class StatementOrder(enum.IntEnum):
+ """Statement ordering."""
+ SPO = 0 # Subject, Predicate, Object
+ SOP = 1 # Subject, Object, Predicate
+ OPS = 2 # Object, Predicate, Subject
+ OSP = 3 # Object, Subject, Predicate
+ PSO = 4 # Predicate, Subject, Object
+ POS = 5 # Predicate, Object, Subject
+ GSPO = 6 # Graph, Subject, Predicate, Object
+ GSOP = 7 # Graph, Subject, Object, Predicate
+ GOPS = 8 # Graph, Object, Predicate, Subject
+ GOSP = 9 # Graph, Object, Subject, Predicate
+ GPSO = 10 # Graph, Predicate, Subject, Object
+ GPOS = 11 # Graph, Predicate, Object, Subject
class ModelFlags(enum.IntFlag):
"""Flags that control model storage and indexing."""
@@ -1662,7 +1678,7 @@ cdef class Model:
def all(self) -> Range:
"""Return a range that contains all statements in the model."""
- return Range._manage(serd_model_all(self._ptr))
+ return Range._manage(serd_model_all(self._ptr, StatementOrder.SPO))
def _find(self, statement) -> _Iter:
statement = Statement._from_param(statement)
diff --git a/doc/serdi.1 b/doc/serdi.1
index b169347b..894cdb99 100644
--- a/doc/serdi.1
+++ b/doc/serdi.1
@@ -6,8 +6,9 @@
.Nd read and write RDF syntax
.Sh SYNOPSIS
.Nm serdi
-.Op Fl Cabefhlqv
+.Op Fl CSabefhlmqtvx
.Op Fl I Ar base
+.Op Fl V Ar checks
.Op Fl c Ar prefix
.Op Fl g Ar pattern
.Op Fl i Ar syntax
@@ -61,13 +62,28 @@ Stream model quickly without inlining.
This only has an effect when a model is used, and disables searching and statement reordering for pretty printing.
Statements will be written in simple sorted order, which is faster, but may result in less readable output in Turtle or TriG.
.Pp
-.It Fl V
-Validate inputs.
-All necessary data, including schemas, must be passed as inputs.
-Output will only be written if validation succeeds.
-Combine with
-.Fl o Ar empty
-to suppress output and only show validation errors.
+.It Fl V Ar checks
+Validate data with the given
+.Ar checks ,
+which is a regular expression that matches a set of check names to enable,
+or the special value
+.Dq all
+which enables all checks.
+See
+.Sx VALIDATION
+below for a detailed list of all checks.
+Validation requires a model, so this option implicitly enables
+.Fl m .
+.Pp
+.It Fl X Ar checks
+Exclude
+.Ar checks
+from the set of checks enabled by a previous
+.Fl V
+option.
+This is typically after
+.Fl V Ar all
+to suppress a few specific checks.
.Pp
.It Fl a
Write ASCII output.
@@ -190,6 +206,77 @@ Variables can be written in SPARQL style, for example
or
.Dq $var .
.El
+.Sh VALIDATION
+.Bl -tag -compact -width 3n
+.It Va allValuesFrom
+Checks that all properties with owl:allValuesFrom restrictions have valid value types.
+.It Va cardinalityEqual
+Checks that any instance of a class with a owl:cardinality property restriction has exactly that many values of that property.
+.It Va cardinalityMax
+Checks that any instance of a class with a owl:maxCardinality property restriction has no more than that many values of that property.
+.It Va cardinalityMin
+Checks that any instance of a class with a owl:minCardinality property restriction has at least that many values of that property.
+.It Va classLabel
+Checks that every rdfs:Class has an rdfs:label.
+.It Va classType
+Checks that the value of every rdf:type property is defined as an rdfs:Class.
+.It Va datatypeProperty
+Checks that datatype properties have literal (not instance) values.
+.It Va datatypeType
+Checks that every datatype is defined as a rdfs:Datatype.
+.It Va deprecatedClass
+Checks that there are no instances of deprecated classes.
+.It Va deprecatedProperty
+Checks that there are no uses of deprecated properties.
+.It Va functionalProperty
+Checks that no instance has several values of a functional property.
+.It Va instanceLiteral
+Checks that there are no instances where a literal is expected.
+.It Va instanceType
+Checks that every instance with an explicit type matches that type.
+This is a broad check that triggers other type-related checks, but mainly it will check that every instance of a class conforms to any restrictions on that class.
+.It Va inverseFunctionalProperty
+Checks that at most one instance has a given value of an inverse functional property.
+.It Va literalInstance
+Checks that there are no literals where an instance is expected.
+.It Va literalMaxExclusive
+Checks that literal values are not greater than or equal to any applicable xsd:maxExclusive datatype restrictions.
+.It Va literalMaxInclusive
+Checks that literal values are not greater than any applicable xsd:maxInclusive datatype restrictions.
+.It Va literalMinExclusive
+Checks that literal values are not less than or equal to any applicable xsd:minExclusive datatype restrictions.
+.It Va literalMinInclusive
+Checks that literal values are not less than any applicable xsd:minInclusive datatype restrictions.
+.It Va literalPattern
+Checks that literals with xsd:pattern restrictions match the regular expression pattern for their datatype.
+.It Va literalRestriction
+Checks that literals with supported restrictions conform to those restrictions.
+This is a high-level check that triggers the more specific individual literal restriction checks.
+.It Va literalValue
+Checks that literals with supported XSD datatypes are valid.
+The set of supported types is the same as when writing canonical forms.
+.It Va objectProperty
+Checks that object properties have instance (not literal) values.
+.It Va plainLiteralDatatype
+Checks that there are no typed literals where a plain literal is expected.
+A plain literal may have an optional language tag, but not a datatype.
+.It Va predicateType
+Checks that every predicate is defined as an rdf:Property.
+.It Va propertyDomain
+Checks that any instance with a property with an rdfs:domain is in that domain.
+.It Va propertyLabel
+Checks that every rdf:Property has an rdfs:label.
+.It Va propertyRange
+Checks that the value for any property with an rdfs:range is in that range.
+.It Va someValuesFrom
+Checks that instances of classes with owl:someValuesFrom property restrictions have at least one matching property value.
+.It Va subclassCycle
+Checks that no class is a sub-class of itself, recursively.
+This ensures that the graph is acyclic with respect to rdfs:subClassOf.
+.It Va subpropertyCycle
+Checks that no property is a sub-property of itself, recursively.
+This ensures that the graph is acyclic with respect to rdfs:subPropertyOf.
+.El
.Sh EXIT STATUS
.Nm
exits with a status of 0, or non-zero if an error occured.
diff --git a/include/serd/serd.h b/include/serd/serd.h
index 88d3178b..feac5f17 100644
--- a/include/serd/serd.h
+++ b/include/serd/serd.h
@@ -243,10 +243,26 @@ typedef uint32_t SerdNodeFlags;
typedef enum {
SERD_SUBJECT = 0, ///< Subject
SERD_PREDICATE = 1, ///< Predicate ("key")
- SERD_OBJECT = 2, ///< Object ("value")
- SERD_GRAPH = 3 ///< Graph ("context")
+ SERD_OBJECT = 2, ///< Object ("value")
+ SERD_GRAPH = 3, ///< Graph ("context")
} SerdField;
+/// Statement ordering
+typedef enum {
+ SERD_ORDER_SPO, ///< Subject, Predicate, Object
+ SERD_ORDER_SOP, ///< Subject, Object, Predicate
+ SERD_ORDER_OPS, ///< Object, Predicate, Subject
+ SERD_ORDER_OSP, ///< Object, Subject, Predicate
+ SERD_ORDER_PSO, ///< Predicate, Subject, Object
+ SERD_ORDER_POS, ///< Predicate, Object, Subject
+ SERD_ORDER_GSPO, ///< Graph, Subject, Predicate, Object
+ SERD_ORDER_GSOP, ///< Graph, Subject, Object, Predicate
+ SERD_ORDER_GOPS, ///< Graph, Object, Predicate, Subject
+ SERD_ORDER_GOSP, ///< Graph, Object, Subject, Predicate
+ SERD_ORDER_GPSO, ///< Graph, Predicate, Subject, Object
+ SERD_ORDER_GPOS ///< Graph, Predicate, Object, Subject
+} SerdStatementOrder;
+
/// Flags that control model storage and indexing
typedef enum {
SERD_INDEX_SPO = 1u << 0u, ///< Subject, Predicate, Object
@@ -256,7 +272,7 @@ typedef enum {
SERD_INDEX_PSO = 1u << 4u, ///< Predicate, Subject, Object
SERD_INDEX_POS = 1u << 5u, ///< Predicate, Object, Subject
SERD_INDEX_GRAPHS = 1u << 6u, ///< Support multiple graphs in model
- SERD_STORE_CURSORS = 1u << 7u ///< Store original cursor of statements
+ SERD_STORE_CURSORS = 1u << 7u, ///< Store original cursor of statements
} SerdModelFlag;
/// Bitwise OR of SerdModelFlag values
@@ -1798,7 +1814,8 @@ serd_model_end(const SerdModel* SERD_NONNULL model);
/// Return a range of all statements in `model`
SERD_API
SerdRange* SERD_ALLOCATED
-serd_model_all(const SerdModel* SERD_NONNULL model);
+serd_model_all(const SerdModel* SERD_NONNULL model,
+ const SerdStatementOrder order);
/**
Search for statements by a quad pattern
@@ -1934,20 +1951,103 @@ SerdStatus
serd_model_erase_range(SerdModel* SERD_NONNULL model,
SerdRange* SERD_NONNULL range);
+/// Validator
+typedef struct SerdValidatorImpl SerdValidator;
+
+typedef enum {
+ SERD_CHECK_ALL_VALUES_FROM,
+ SERD_CHECK_ANY_URI,
+ SERD_CHECK_CARDINALITY_EQUAL,
+ SERD_CHECK_CARDINALITY_MAX,
+ SERD_CHECK_CARDINALITY_MIN,
+ SERD_CHECK_CLASS_CYCLE,
+ SERD_CHECK_CLASS_LABEL,
+ SERD_CHECK_CLASS_TYPE,
+ SERD_CHECK_DATATYPE_PROPERTY,
+ SERD_CHECK_DATATYPE_TYPE,
+ SERD_CHECK_DEPRECATED_CLASS,
+ SERD_CHECK_DEPRECATED_PROPERTY,
+ SERD_CHECK_FUNCTIONAL_PROPERTY,
+ SERD_CHECK_INSTANCE_LITERAL,
+ SERD_CHECK_INSTANCE_TYPE,
+ SERD_CHECK_INVERSE_FUNCTIONAL_PROPERTY,
+ SERD_CHECK_LITERAL_INSTANCE,
+ SERD_CHECK_LITERAL_MAX_EXCLUSIVE,
+ SERD_CHECK_LITERAL_MAX_INCLUSIVE,
+ SERD_CHECK_LITERAL_MIN_EXCLUSIVE,
+ SERD_CHECK_LITERAL_MIN_INCLUSIVE,
+ SERD_CHECK_LITERAL_PATTERN,
+ SERD_CHECK_LITERAL_RESTRICTION,
+ SERD_CHECK_LITERAL_VALUE,
+ SERD_CHECK_OBJECT_PROPERTY,
+ SERD_CHECK_PLAIN_LITERAL_DATATYPE,
+ SERD_CHECK_PREDICATE_TYPE,
+ SERD_CHECK_PROPERTY_CYCLE,
+ SERD_CHECK_PROPERTY_DOMAIN,
+ SERD_CHECK_PROPERTY_LABEL,
+ SERD_CHECK_PROPERTY_RANGE,
+ SERD_CHECK_SOME_VALUES_FROM,
+} SerdValidatorCheck;
+
+typedef uint64_t SerdValidatorChecks;
+
+SERD_API
+SerdValidatorChecks
+serd_validator_checks_enable(const SerdValidatorChecks checks,
+ const char* SERD_NONNULL);
+
+SERD_API
+SerdValidatorChecks
+serd_validator_checks_disable(SerdValidatorChecks checks,
+ const char* SERD_NONNULL);
+
+SERD_MALLOC_API
+SerdValidator* SERD_ALLOCATED
+serd_validator_new(SerdWorld* SERD_NONNULL world);
+
+/// Free `validator`
+SERD_API
+void
+serd_validator_free(SerdValidator* SERD_NULLABLE validator);
+
+SERD_API
+SerdStatus
+serd_validator_enable_checks(SerdValidator* SERD_NONNULL validator,
+ const char* SERD_NONNULL pattern);
+
+SERD_API
+SerdStatus
+serd_validator_disable_checks(SerdValidator* SERD_NONNULL validator,
+ const char* SERD_NONNULL pattern);
+
/**
Validate model.
- This performs validation based on the RDFS and OWL vocabularies. All
- necessary data, including those vocabularies and any property/class
+ This performs validation based on the XSD, RDF, RDFS, and OWL vocabularies.
+ All necessary data, including those vocabularies and any property/class
definitions that use them, are assumed to be in `model`.
Validation errors are reported to the world's error sink.
- @return 0 on success.
+ @param validator Validator configured to run the desired checks.
+
+ @param model The model to run the validator on.
+
+ @param graph Optional graph to check. Is this is non-null, then top-level
+ checks will be initiated only by statements in the given graph. The entire
+ model is still searched while running a check so that, for example, schemas
+ that define classes and properties can be stored in separate graphs. If
+ this is null, then the validator simply ignores graphs and searches the
+ entire model for everything.
+
+ @return #SERD_SUCCESS if no errors are found, or #SERD_ERR_INVALID if
+ validation checks failed.
*/
SERD_API
SerdStatus
-serd_validate(const SerdModel* SERD_NONNULL model);
+serd_validate_model(SerdValidator* SERD_NONNULL const validator,
+ const SerdModel* SERD_NONNULL model,
+ const SerdNode* SERD_NONNULL graph);
/**
@}
diff --git a/meson.build b/meson.build
index cefc0ef2..4f27f76e 100644
--- a/meson.build
+++ b/meson.build
@@ -24,18 +24,19 @@ if add_languages('cpp', required: get_option('bindings_cpp'))
endif
# Set ultra strict warnings for developers, if requested
+c_warnings = []
+c_suppressions = []
if get_option('strict')
subdir('meson')
c_warnings = all_c_warnings
+
if cc.get_id() == 'clang'
- c_warnings += [
- '-Wno-bad-function-cast',
- '-Wno-cast-align',
- '-Wno-covered-switch-default',
- '-Wno-disabled-macro-expansion',
- '-Wno-double-promotion',
- '-Wno-float-equal',
+ c_suppressions += [
+ # '-Wno-cast-align',
+ # '-Wno-covered-switch-default',
+ # '-Wno-disabled-macro-expansion',
+ # '-Wno-double-promotion',
'-Wno-format-nonliteral',
'-Wno-implicit-fallthrough',
'-Wno-nullability-extension',
@@ -44,19 +45,20 @@ if get_option('strict')
'-Wno-reserved-id-macro',
]
elif cc.get_id() == 'gcc'
- c_warnings += [
+ c_suppressions += [
'-Wno-bad-function-cast',
'-Wno-cast-align',
'-Wno-format-nonliteral',
'-Wno-inline',
'-Wno-padded',
'-Wno-strict-overflow',
+ '-Wno-suggest-attribute=pure',
'-Wno-switch-default',
'-Wno-unsuffixed-float-constants',
'-Wno-unused-const-variable',
]
elif cc.get_id() == 'msvc'
- c_warnings += [
+ c_suppressions += [
'/wd4061', # enumerator in switch is not explicitly handled
'/wd4200', # nonstandard: zero-sized array in struct/union
'/wd4514', # unreferenced inline function has been removed
@@ -69,9 +71,10 @@ if get_option('strict')
]
endif
- c_warnings = cc.get_supported_arguments(c_warnings)
endif
+c_warnings += cc.get_supported_arguments(c_suppressions)
+
# Add special arguments for MSVC
if cc.get_id() == 'msvc'
msvc_args = [
diff --git a/meson/meson.build b/meson/meson.build
index 20e0522f..5dd0de38 100644
--- a/meson/meson.build
+++ b/meson/meson.build
@@ -109,7 +109,7 @@ gcc_c_warnings = [
]
# Set all_c_warnings for the current C compiler
-if is_variable('cc')
+if is_variable('cc') and not is_variable('all_c_warnings')
if cc.get_id() == 'clang'
all_c_warnings = ['-Weverything']
elif cc.get_id() == 'gcc'
@@ -133,10 +133,13 @@ if is_variable('cc')
else
all_c_warnings = []
endif
+
+ all_c_warnings = cc.get_supported_arguments(all_c_warnings)
+
endif
# Set all_cpp_warnings for the current C++ compiler
-if is_variable('cpp')
+if is_variable('cpp') and not is_variable('all_cpp_warnings')
if cpp.get_id() == 'clang'
all_cpp_warnings = [
'-Weverything',
@@ -179,10 +182,13 @@ if is_variable('cpp')
else
all_cpp_warnings = []
endif
+
+ all_cpp_warnings = cpp.get_supported_arguments(all_cpp_warnings)
+
endif
# Set all_objc_warnings for the current Objective C compiler
-if is_variable('objcc')
+if is_variable('objcc') and not is_variable('all_objc_warnings')
all_objc_warnings = []
if objcc.get_id() == 'clang'
all_objc_warnings = ['-Weverything']
@@ -193,4 +199,6 @@ if is_variable('objcc')
else
all_objc_warnings = []
endif
+
+ all_objc_warnings = objcc.get_supported_arguments(all_objc_warnings)
endif
diff --git a/schemas/rdf.ttl b/schemas/rdf.ttl
index 5ef93450..bd9a6738 100644
--- a/schemas/rdf.ttl
+++ b/schemas/rdf.ttl
@@ -2,7 +2,7 @@
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
-<http://www.w3.org/1999/02/22-rdf-syntax-ns#>
+rdf:
rdfs:comment "The RDF Vocabulary (RDF)" ;
a owl:Ontology ;
rdfs:seeAlso <http://www.w3.org/2000/01/rdf-schema-more> .
diff --git a/schemas/rdfs.ttl b/schemas/rdfs.ttl
index 43c51e58..a266b972 100644
--- a/schemas/rdfs.ttl
+++ b/schemas/rdfs.ttl
@@ -2,7 +2,7 @@
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
-<http://www.w3.org/2000/01/rdf-schema#>
+rdfs:
rdfs:comment "The RDF Schema vocabulary (RDFS)" ;
a owl:Ontology ;
rdfs:seeAlso <http://www.w3.org/2000/01/rdf-schema-more> .
diff --git a/schemas/xsd.ttl b/schemas/xsd.ttl
index 46f6793a..82a9ccf0 100644
--- a/schemas/xsd.ttl
+++ b/schemas/xsd.ttl
@@ -3,41 +3,10 @@
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
-<http://www.w3.org/2001/XMLSchema#>
+xsd:
a owl:Ontology ;
rdfs:comment "XML Schema Datatypes" .
-xsd:ID
- a rdfs:Datatype ;
- owl:onDatatype xsd:NCName .
-
-xsd:IDREF
- a rdfs:Datatype ;
- owl:onDatatype xsd:NCName .
-
-xsd:ENTITY
- a rdfs:Datatype ;
- owl:onDatatype xsd:NCName .
-
-xsd:NCName
- a rdfs:Datatype ;
- owl:onDatatype xsd:Name .
-
-xsd:NMTOKEN
- a rdfs:Datatype ;
- owl:onDatatype xsd:token .
-
-xsd:Name
- a rdfs:Datatype ;
- owl:onDatatype xsd:token .
-
-xsd:totalDigits
- a rdf:Property ,
- owl:DatatypeProperty ;
- rdfs:range xsd:positiveInteger ;
- rdfs:label "total digits" ;
- rdfs:comment "The maximum number of decimal digits required to represent a value." .
-
xsd:fractionDigits
a rdf:Property ,
owl:DatatypeProperty ;
@@ -75,10 +44,6 @@ xsd:minExclusive
rdfs:label "min exclusive" ;
rdfs:comment "The exclusive lower bound of an ordered datatype." .
-xsd:QName
- a rdfs:Datatype ;
- rdfs:label "XML qualified name" .
-
xsd:anyURI
a rdfs:Datatype ;
rdfs:label "URI reference" .
@@ -89,7 +54,7 @@ xsd:base64Binary
rdfs:comment "Base64-encoded arbitrary binary data." ;
owl:withRestrictions (
[
- xsd:pattern "(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?"
+ xsd:pattern "(([A-Za-z0-9+/] *[A-Za-z0-9+/] *[A-Za-z0-9+/] *[A-Za-z0-9+/] *)*(([A-Za-z0-9+/] *[A-Za-z0-9+/] *[A-Za-z0-9+/] *[A-Za-z0-9+/])|([A-Za-z0-9+/] *[A-Za-z0-9+/] *[AEIMQUYcgkosw048] *=)|([A-Za-z0-9+/] *[AQgw] *= *=)))?"
]
) .
@@ -98,7 +63,7 @@ xsd:boolean
rdfs:label "boolean" ;
owl:withRestrictions (
[
- xsd:pattern "(true|false)"
+ xsd:pattern "(true|false|0|1)"
]
) .
@@ -109,7 +74,8 @@ xsd:byte
owl:withRestrictions (
[
xsd:maxInclusive 127
- ] [
+ ]
+ [
xsd:minInclusive -128
]
) .
@@ -119,7 +85,7 @@ xsd:date
rdfs:label "date" ;
owl:withRestrictions (
[
- xsd:pattern "-?[0-9][0-9][0-9][0-9]([0-9]*)?-(0[1-9]|1[0-2])-([0-3][0-9])([+-]([0-1][0-9]|2[0-3]):[0-5][0-9])?"
+ xsd:pattern "-?[0-9][0-9][0-9][0-9][0-9]*-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])(Z|[-+][0-2][0-9]:[0-5][0-9])?"
]
) .
@@ -133,7 +99,7 @@ xsd:decimal
rdfs:comment "A subset of the real numbers, which can be represented by decimal numerals." ;
owl:withRestrictions (
[
- xsd:pattern "[+-]?[0-9]*\\.?[0-9]*"
+ xsd:pattern "-?INF|NaN|[+-]?(([0-9]+[.]?[0-9]*)|([0-9]*[.]?[0-9]+))([eE][-+]?[0-9]+)?"
]
) .
@@ -143,7 +109,7 @@ xsd:double
rdfs:comment "IEEE double-precision 64-bit floating point." ;
owl:withRestrictions (
[
- xsd:pattern "[+-]?[0-9]*\\.?[0-9]*([eE][-+]?[0-9]+)?"
+ xsd:pattern "-?INF|NaN|[+-]?(([0-9]+[.]?[0-9]*)|([0-9]*[.]?[0-9]+))([eE][-+]?[0-9]+)?"
]
) .
@@ -157,28 +123,13 @@ xsd:float
rdfs:comment "IEEE single-precision 32-bit floating point." ;
owl:onDatatype xsd:double .
-xsd:gDay
- a rdfs:Datatype .
-
-xsd:gMonth
- a rdfs:Datatype .
-
-xsd:gMonthDay
- a rdfs:Datatype .
-
-xsd:gYear
- a rdfs:Datatype .
-
-xsd:gYearMonth
- a rdfs:Datatype .
-
xsd:hexBinary
a rdfs:Datatype ;
rdfs:label "hex binary" ;
rdfs:comment "Hex-encoded arbitrary binary data." ;
owl:withRestrictions (
[
- xsd:pattern "[0-9A-F]*"
+ xsd:pattern "([0-9A-Fa-f][0-9A-Fa-f])*"
]
) .
@@ -189,7 +140,8 @@ xsd:int
owl:withRestrictions (
[
xsd:maxInclusive 2147483647
- ] [
+ ]
+ [
xsd:minInclusive -2147483648
]
) .
@@ -201,7 +153,8 @@ xsd:integer
owl:withRestrictions (
[
xsd:pattern "[-+]?[0-9]+"
- ] [
+ ]
+ [
xsd:fractionDigits 0
]
) .
@@ -212,7 +165,7 @@ xsd:language
owl:onDatatype xsd:token ;
owl:withRestrictions (
[
- xsd:pattern "[a-zA-Z]{1,8}(-[a-zA-Z0-9]{1,8})*"
+ xsd:pattern "[a-zA-Z][a-zA-Z]?[a-zA-Z]?[a-zA-Z]?[a-zA-Z]?[a-zA-Z]?[a-zA-Z]?[a-zA-Z]?(-[a-zA-Z0-9][a-zA-Z0-9]?[a-zA-Z0-9]?[a-zA-Z0-9]?[a-zA-Z0-9]?[a-zA-Z0-9]?[a-zA-Z0-9]?[a-zA-Z0-9]?)*"
]
) .
@@ -223,7 +176,8 @@ xsd:long
owl:withRestrictions (
[
xsd:maxInclusive 9223372036854775807
- ] [
+ ]
+ [
xsd:minInclusive -9223372036854775808
]
) .
@@ -244,8 +198,9 @@ xsd:nonNegativeInteger
owl:onDatatype xsd:integer ;
owl:withRestrictions (
[
- xsd:pattern "[0-9]*"
- ] [
+ xsd:pattern "[+]?[0-9]+"
+ ]
+ [
xsd:minInclusive 0
]
) .
@@ -257,7 +212,8 @@ xsd:nonPositiveInteger
owl:withRestrictions (
[
xsd:pattern "(0|-[0-9]+)"
- ] [
+ ]
+ [
xsd:maxInclusive 0
]
) .
@@ -274,8 +230,9 @@ xsd:positiveInteger
owl:onDatatype xsd:nonNegativeInteger ;
owl:withRestrictions (
[
- xsd:pattern "[+]?[0-9]+"
- ] [
+ xsd:pattern "[+]?[0-9]*[1-9]+[0-9]*"
+ ]
+ [
xsd:minInclusive 1
]
) .
@@ -287,7 +244,8 @@ xsd:short
owl:withRestrictions (
[
xsd:maxInclusive 32767
- ] [
+ ]
+ [
xsd:minInclusive -32768
]
) .
@@ -302,7 +260,7 @@ xsd:time
rdfs:label "time" ;
owl:withRestrictions (
[
- xsd:pattern "[1-2][0-9]:[0-5][0-9]:[0-5][0-9].[0-9][0-9][0-9]"
+ xsd:pattern "(([0-1][0-9])|(2[0-4])):[0-5][0-9]:[0-5][0-9](.[0-9]+)?(Z|[-+][0-2][0-9]:[0-5][0-9])?"
]
) .
@@ -338,7 +296,7 @@ xsd:unsignedLong
owl:onDatatype xsd:nonNegativeInteger ;
owl:withRestrictions (
[
- xsd:maxInclusive 18446744073709551615
+ xsd:maxInclusive "18446744073709551615"^^xsd:unsignedLong
]
) .
diff --git a/scripts/update_man_page.py b/scripts/update_man_page.py
new file mode 100755
index 00000000..0ae93442
--- /dev/null
+++ b/scripts/update_man_page.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python3
+
+# Copyright 2020 David Robillard <d@drobilla.net>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+"""
+Update the serdi man page from the canonical check descriptions in serd.ttl.
+"""
+
+import serd
+import argparse
+import sys
+
+
+def run(serd_ttl_path):
+ rdf = serd.Namespace("http://www.w3.org/1999/02/22-rdf-syntax-ns#")
+ rdfs = serd.Namespace("http://www.w3.org/2000/01/rdf-schema#")
+ ns_serd = serd.Namespace("http://drobilla.net/sw/serd#")
+ ns_checks = serd.Namespace("http://drobilla.net/sw/serd/checks#")
+
+ world = serd.World()
+ model = world.load(serd_ttl_path)
+ for s in model.range((None, rdf.type, ns_serd.ValidatorCheck)):
+ check = s.subject()
+ check_name = ns_checks.name(check)
+ assert check_name is not None
+
+ check_description = model.get(s.subject(), rdfs.comment)
+
+ print(".It Va {}".format(check_name))
+ print(str(check_description).replace('. ', '.\n').strip())
+
+
+if __name__ == "__main__":
+ ap = argparse.ArgumentParser(
+ usage="%(prog)s SERD_TTL_PATH",
+ description=__doc__,
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ )
+
+ ap.add_argument("serd_ttl_path", help="path to serd.ttl")
+
+ run(**vars(ap.parse_args(sys.argv[1:])))
diff --git a/serd.ttl b/serd.ttl
index 9a8db362..ec5f4a02 100644
--- a/serd.ttl
+++ b/serd.ttl
@@ -1,7 +1,25 @@
@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
-@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+@prefix serd: <http://drobilla.net/ns/serd#> .
+@prefix checks: <http://drobilla.net/ns/serd/checks#> .
+
+<http://www.w3.org/TR/n-quads/>
+ a doap:Specification ;
+ rdfs:label "NQuads" .
+
+<http://www.w3.org/TR/n-triples/>
+ a doap:Specification ;
+ rdfs:label "NTriples" .
+
+<http://www.w3.org/TR/trig/>
+ a doap:Specification ;
+ rdfs:label "TriG" .
+
+<http://www.w3.org/TR/turtle/>
+ a doap:Specification ;
+ rdfs:label "Turtle" .
<http://drobilla.net/drobilla#me>
a foaf:Person ;
@@ -30,3 +48,137 @@
a doap:GitBranch ;
doap:location <http://gitlab.com/drobilla/serd.git>
] .
+
+serd:ValidatorCheck
+ a rdfs:Class ;
+ rdfs:label "Validator Check" ;
+ rdfs:comment "A serd validator check" .
+
+checks:allValuesFrom
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that all properties with owl:allValuesFrom restrictions have valid value types." .
+
+checks:anyUri
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that the value of any property with range xsd:anyURI is a URI." .
+
+checks:cardinalityEqual
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that any instance of a class with a owl:cardinality property restriction has exactly that many values of that property." .
+
+checks:cardinalityMax
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that any instance of a class with a owl:maxCardinality property restriction has no more than that many values of that property." .
+
+checks:cardinalityMin
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that any instance of a class with a owl:minCardinality property restriction has at least that many values of that property." .
+
+checks:classLabel
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that every rdfs:Class has an rdfs:label." .
+
+checks:classCycle
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that no class is a sub-class of itself, recursively. This ensures that the graph is acyclic with respect to rdfs:subClassOf." .
+
+checks:classType
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that the value of every rdf:type property is defined as an rdfs:Class." .
+
+checks:datatypeProperty
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that datatype properties have literal (not instance) values." .
+
+checks:datatypeType
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that every datatype is defined as a rdfs:Datatype." .
+
+checks:deprecatedClass
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that there are no instances of deprecated classes." .
+
+checks:deprecatedProperty
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that there are no uses of deprecated properties." .
+
+checks:functionalProperty
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that no instance has several values of a functional property." .
+
+checks:instanceLiteral
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that there are no instances where a literal is expected." .
+
+checks:instanceType
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that every instance with an explicit type matches that type. This is a broad check that triggers other type-related checks, but mainly it will check that every instance of a class conforms to any restrictions on that class." .
+
+checks:inverseFunctionalProperty
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that at most one instance has a given value of an inverse functional property." .
+
+checks:literalInstance
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that there are no literals where an instance is expected." .
+
+checks:literalMaxExclusive
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that literal values are not greater than or equal to any applicable xsd:maxExclusive datatype restrictions." .
+
+checks:literalMaxInclusive
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that literal values are not greater than any applicable xsd:maxInclusive datatype restrictions." .
+
+checks:literalMinExclusive
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that literal values are not less than or equal to any applicable xsd:minExclusive datatype restrictions." .
+
+checks:literalMinInclusive
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that literal values are not less than any applicable xsd:minInclusive datatype restrictions." .
+
+checks:literalPattern
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that literals with xsd:pattern restrictions match the regular expression pattern for their datatype." .
+
+checks:literalRestriction
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that literals with supported restrictions conform to those restrictions. This is a high-level check that triggers the more specific individual literal restriction checks." .
+
+checks:literalValue
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that literals with supported XSD datatypes are valid. The set of supported types is the same as when writing canonical forms." .
+
+checks:objectProperty
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that object properties have instance (not literal) values." .
+
+checks:plainLiteralDatatype
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that there are no typed literals where a plain literal is expected. A plain literal may have an optional language tag, but not a datatype." .
+
+checks:predicateType
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that every predicate is defined as an rdf:Property." .
+
+checks:propertyCycle
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that no property is a sub-property of itself, recursively. This ensures that the graph is acyclic with respect to rdfs:subPropertyOf." .
+
+checks:propertyDomain
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that any instance with a property with an rdfs:domain is in that domain." .
+
+checks:propertyLabel
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that every rdf:Property has an rdfs:label." .
+
+checks:propertyRange
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that the value for any property with an rdfs:range is in that range." .
+
+checks:someValuesFrom
+ a serd:ValidatorCheck ;
+ rdfs:comment "Checks that instances of classes with owl:someValuesFrom property restrictions have at least one matching property value." .
+
diff --git a/src/iter.c b/src/iter.c
index 62c3ef4f..1f3c6c28 100644
--- a/src/iter.c
+++ b/src/iter.c
@@ -81,12 +81,12 @@ check_version(const SerdIter* const iter)
}
SerdIter*
-serd_iter_new(const SerdModel* model,
- ZixBTreeIter* cur,
- const SerdQuad pat,
- SerdOrder order,
- SearchMode mode,
- int n_prefix)
+serd_iter_new(const SerdModel* model,
+ ZixBTreeIter* cur,
+ const SerdQuad pat,
+ SerdStatementOrder order,
+ SearchMode mode,
+ int n_prefix)
{
SerdIter* iter = (SerdIter*)calloc(1, sizeof(SerdIter));
iter->model = model;
diff --git a/src/iter.h b/src/iter.h
index 0b0d7e5b..ddfff419 100644
--- a/src/iter.h
+++ b/src/iter.h
@@ -25,21 +25,21 @@
#include <stdbool.h>
#include <stdint.h>
-/** Triple ordering */
-typedef enum {
- SPO, ///< Subject, Predicate, Object
- SOP, ///< Subject, Object, Predicate
- OPS, ///< Object, Predicate, Subject
- OSP, ///< Object, Subject, Predicate
- PSO, ///< Predicate, Subject, Object
- POS, ///< Predicate, Object, Subject
- GSPO, ///< Graph, Subject, Predicate, Object
- GSOP, ///< Graph, Subject, Object, Predicate
- GOPS, ///< Graph, Object, Predicate, Subject
- GOSP, ///< Graph, Object, Subject, Predicate
- GPSO, ///< Graph, Predicate, Subject, Object
- GPOS ///< Graph, Predicate, Object, Subject
-} SerdOrder;
+/* /\** Triple ordering *\/ */
+/* typedef enum { */
+/* SPO, ///< Subject, Predicate, Object */
+/* SOP, ///< Subject, Object, Predicate */
+/* OPS, ///< Object, Predicate, Subject */
+/* OSP, ///< Object, Subject, Predicate */
+/* PSO, ///< Predicate, Subject, Object */
+/* POS, ///< Predicate, Object, Subject */
+/* GSPO, ///< Graph, Subject, Predicate, Object */
+/* GSOP, ///< Graph, Subject, Object, Predicate */
+/* GOPS, ///< Graph, Object, Predicate, Subject */
+/* GOSP, ///< Graph, Object, Subject, Predicate */
+/* GPSO, ///< Graph, Predicate, Subject, Object */
+/* GPOS ///< Graph, Predicate, Object, Subject */
+/* } SerdOrder; */
/** Mode for searching or iteration */
typedef enum {
@@ -50,13 +50,13 @@ typedef enum {
} SearchMode;
struct SerdIterImpl {
- const SerdModel* model; ///< Model being iterated over
- uint64_t version; ///< Model version when iterator was created
- ZixBTreeIter* cur; ///< Current DB cursor
- SerdQuad pat; ///< Pattern (in ordering order)
- SerdOrder order; ///< Store order (which index)
- SearchMode mode; ///< Iteration mode
- int n_prefix; ///< Prefix for RANGE and FILTER_RANGE
+ const SerdModel* model; ///< Model being iterated over
+ uint64_t version; ///< Model version when iterator was created
+ ZixBTreeIter* cur; ///< Current DB cursor
+ SerdQuad pat; ///< Pattern (in ordering order)
+ SerdStatementOrder order; ///< Store order (which index)
+ SearchMode mode; ///< Iteration mode
+ int n_prefix; ///< Prefix for RANGE and FILTER_RANGE
};
#define NUM_ORDERS 12
@@ -82,12 +82,12 @@ static const int orderings[NUM_ORDERS][TUP_LEN] = {
};
SerdIter*
-serd_iter_new(const SerdModel* model,
- ZixBTreeIter* cur,
- const SerdQuad pat,
- SerdOrder order,
- SearchMode mode,
- int n_prefix);
+serd_iter_new(const SerdModel* model,
+ ZixBTreeIter* cur,
+ const SerdQuad pat,
+ SerdStatementOrder order,
+ SearchMode mode,
+ int n_prefix);
bool
serd_iter_scan_next(SerdIter* iter);
diff --git a/src/macros.h b/src/macros.h
index ac5af149..af232cc5 100644
--- a/src/macros.h
+++ b/src/macros.h
@@ -17,6 +17,7 @@
#ifndef SERD_MACROS_H
#define SERD_MACROS_H
+#define MAX(x, y) ((x) < (y) ? (y) : (x))
#define MIN(x, y) ((x) < (y) ? (x) : (y))
#endif // SERD_MACROS_H
diff --git a/src/model.c b/src/model.c
index f3e56971..f06a5e82 100644
--- a/src/model.c
+++ b/src/model.c
@@ -31,8 +31,10 @@
#include <stddef.h>
#include <stdlib.h>
-#define DEFAULT_ORDER SPO
-#define DEFAULT_GRAPH_ORDER GSPO
+#define DEFAULT_ORDER SERD_ORDER_SPO
+#define DEFAULT_GRAPH_ORDER SERD_ORDER_GSPO
+
+static const SerdQuad wildcard_pattern = {0, 0, 0, 0};
/**
Compare quads lexicographically, ignoring graph.
@@ -81,13 +83,13 @@ serd_quad_compare(const void* x, const void* y, void* user_data)
corresponding order with a G prepended (so G will be the MSN).
*/
static inline bool
-serd_model_has_index(const SerdModel* model,
- SerdOrder* order,
- int* n_prefix,
- bool graphs)
+serd_model_has_index(const SerdModel* model,
+ SerdStatementOrder* order,
+ int* n_prefix,
+ bool graphs)
{
if (graphs) {
- *order = (SerdOrder)(*order + GSPO);
+ *order = (SerdStatementOrder)(*order + SERD_ORDER_GSPO);
*n_prefix += 1;
}
@@ -101,7 +103,7 @@ serd_model_has_index(const SerdModel* model,
@param n_prefix Set to the length of the range prefix
(for `mode` == RANGE and `mode` == FILTER_RANGE)
*/
-static SerdOrder
+static SerdStatementOrder
serd_model_best_index(const SerdModel* model,
const SerdQuad pat,
SearchMode* mode,
@@ -113,7 +115,7 @@ serd_model_best_index(const SerdModel* model,
((pat[0] ? 1u : 0u) * 0x100 + (pat[1] ? 1u : 0u) * 0x010 +
(pat[2] ? 1u : 0u) * 0x001);
- SerdOrder good[2] = {(SerdOrder)-1, (SerdOrder)-1};
+ SerdStatementOrder good[2] = {(SerdStatementOrder)-1, (SerdStatementOrder)-1};
#define PAT_CASE(sig, m, g0, g1, np) \
case sig: \
@@ -137,12 +139,12 @@ serd_model_best_index(const SerdModel* model,
*n_prefix = graph_search ? 4 : 3;
return graph_search ? DEFAULT_GRAPH_ORDER : DEFAULT_ORDER;
- PAT_CASE(0x001, RANGE, OPS, OSP, 1);
- PAT_CASE(0x010, RANGE, POS, PSO, 1);
- PAT_CASE(0x011, RANGE, OPS, POS, 2);
- PAT_CASE(0x100, RANGE, SPO, SOP, 1);
- PAT_CASE(0x101, RANGE, SOP, OSP, 2);
- PAT_CASE(0x110, RANGE, SPO, PSO, 2);
+ PAT_CASE(0x001, RANGE, SERD_ORDER_OPS, SERD_ORDER_OSP, 1);
+ PAT_CASE(0x010, RANGE, SERD_ORDER_POS, SERD_ORDER_PSO, 1);
+ PAT_CASE(0x011, RANGE, SERD_ORDER_OPS, SERD_ORDER_POS, 2);
+ PAT_CASE(0x100, RANGE, SERD_ORDER_SPO, SERD_ORDER_SOP, 1);
+ PAT_CASE(0x101, RANGE, SERD_ORDER_SOP, SERD_ORDER_OSP, 2);
+ PAT_CASE(0x110, RANGE, SERD_ORDER_SPO, SERD_ORDER_PSO, 2);
default:
break;
}
@@ -160,8 +162,8 @@ serd_model_best_index(const SerdModel* model,
// Not so good orderings that require filtering, but can
// still be constrained to a range
switch (sig) {
- PAT_CASE(0x011, FILTER_RANGE, OSP, PSO, 1);
- PAT_CASE(0x101, FILTER_RANGE, SPO, OPS, 1);
+ PAT_CASE(0x011, FILTER_RANGE, SERD_ORDER_OSP, SERD_ORDER_PSO, 1);
+ PAT_CASE(0x101, FILTER_RANGE, SERD_ORDER_SPO, SERD_ORDER_OPS, 1);
// SPO is always present, so 0x110 is never reached here
default:
break;
@@ -212,9 +214,11 @@ serd_model_new(SerdWorld* world, SerdModelFlags flags)
}
// Create end iterator
- const SerdOrder order = model->indices[GSPO] ? GSPO : SPO;
- ZixBTreeIter* cur = zix_btree_end(model->indices[order]);
- const SerdQuad pat = {0, 0, 0, 0};
+ const SerdStatementOrder order =
+ model->indices[SERD_ORDER_GSPO] ? SERD_ORDER_GSPO : SERD_ORDER_SPO;
+
+ ZixBTreeIter* cur = zix_btree_end(model->indices[order]);
+ const SerdQuad pat = {0, 0, 0, 0};
model->end = serd_iter_new(model, cur, pat, order, ALL, 0);
@@ -230,7 +234,7 @@ serd_model_copy(const SerdModel* model)
SerdModel* copy = serd_model_new(model->world, model->flags);
- SerdRange* all = serd_model_all(model);
+ SerdRange* all = serd_model_all(model, SERD_ORDER_SPO);
serd_model_add_range(copy, all);
serd_range_free(all);
@@ -250,8 +254,8 @@ serd_model_equals(const SerdModel* a, const SerdModel* b)
return false;
}
- SerdRange* ra = serd_model_all(a);
- SerdRange* rb = serd_model_all(b);
+ SerdRange* ra = serd_model_all(a, SERD_ORDER_SPO);
+ SerdRange* rb = serd_model_all(b, SERD_ORDER_SPO);
bool result = true;
while (!serd_range_empty(ra) && !serd_range_empty(rb)) {
@@ -333,7 +337,8 @@ serd_model_flags(const SerdModel* model)
size_t
serd_model_size(const SerdModel* model)
{
- const SerdOrder order = model->indices[GSPO] ? GSPO : SPO;
+ const SerdStatementOrder order =
+ model->indices[SERD_ORDER_GSPO] ? SERD_ORDER_GSPO : SERD_ORDER_SPO;
return zix_btree_size(model->indices[order]);
}
@@ -343,6 +348,34 @@ serd_model_empty(const SerdModel* model)
return serd_model_size(model) == 0;
}
+// FIXME : expose
+
+static SerdIter*
+serd_model_begin_ordered(const SerdModel* model, const SerdStatementOrder order)
+{
+ return model->indices[order]
+ ? serd_iter_new(model,
+ zix_btree_begin(model->indices[order]),
+ wildcard_pattern,
+ order,
+ ALL,
+ 0)
+ : NULL;
+}
+
+static SerdIter*
+serd_model_end_ordered(const SerdModel* model, const SerdStatementOrder order)
+{
+ return model->indices[order]
+ ? serd_iter_new(model,
+ zix_btree_end(model->indices[order]),
+ wildcard_pattern,
+ order,
+ ALL,
+ 0)
+ : NULL;
+}
+
SerdIter*
serd_model_begin(const SerdModel* model)
{
@@ -350,9 +383,10 @@ serd_model_begin(const SerdModel* model)
return serd_iter_copy(serd_model_end(model));
}
- const SerdOrder order = model->indices[GSPO] ? GSPO : SPO;
- ZixBTreeIter* cur = zix_btree_begin(model->indices[order]);
- const SerdQuad pat = {0, 0, 0, 0};
+ const SerdStatementOrder order =
+ model->indices[SERD_ORDER_GSPO] ? SERD_ORDER_GSPO : SERD_ORDER_SPO;
+ ZixBTreeIter* cur = zix_btree_begin(model->indices[order]);
+ const SerdQuad pat = {0, 0, 0, 0};
return serd_iter_new(model, cur, pat, order, ALL, 0);
}
@@ -363,10 +397,17 @@ serd_model_end(const SerdModel* model)
}
SerdRange*
-serd_model_all(const SerdModel* model)
+serd_model_all(const SerdModel* model, const SerdStatementOrder order)
{
- return serd_range_new(serd_model_begin(model),
- serd_iter_copy(serd_model_end(model)));
+ const SerdStatementOrder real_order =
+ (order >= SERD_ORDER_GSPO && !(model->flags & SERD_INDEX_GRAPHS))
+ ? order - SERD_ORDER_GSPO
+ : order;
+
+ return model->indices[real_order]
+ ? serd_range_new(serd_model_begin_ordered(model, real_order),
+ serd_model_end_ordered(model, real_order))
+ : NULL;
}
SerdIter*
@@ -381,9 +422,9 @@ serd_model_find(const SerdModel* model,
return serd_model_begin(model);
}
- SearchMode mode = ALL;
- int n_prefix = 0;
- const SerdOrder index_order =
+ SearchMode mode = ALL;
+ int n_prefix = 0;
+ const SerdStatementOrder index_order =
serd_model_best_index(model, pat, &mode, &n_prefix);
ZixBTree* const db = model->indices[index_order];
@@ -560,7 +601,7 @@ serd_model_add_internal(SerdModel* model,
if (model->indices[i]) {
if (!zix_btree_insert(model->indices[i], statement)) {
added = true;
- } else if (i == GSPO) {
+ } else if (i == SERD_ORDER_GSPO) {
break; // Statement already indexed
}
}
@@ -631,7 +672,7 @@ serd_model_erase(SerdModel* model, SerdIter* iter)
const SerdStatement* statement = serd_iter_get(iter);
SerdStatement* removed = NULL;
- for (int i = SPO; i <= GPOS; ++i) {
+ for (int i = SERD_ORDER_SPO; i <= SERD_ORDER_GPOS; ++i) {
if (model->indices[i]) {
zix_btree_remove(model->indices[i],
statement,
diff --git a/src/n3.c b/src/n3.c
index dd1b5168..7cfe4e2e 100644
--- a/src/n3.c
+++ b/src/n3.c
@@ -727,7 +727,10 @@ resolve_IRIREF(SerdReader* const reader,
// Resolve relative URI reference to a full URI
uri = serd_resolve_uri(uri, serd_env_base_uri_view(reader->env));
if (!uri.scheme.len) {
- return SERD_ERR_BAD_URI;
+ return r_err(reader,
+ SERD_ERR_BAD_SYNTAX,
+ "failed to resolve relative URI reference <%s>\n",
+ serd_node_string(dest));
}
// Push a new temporary node for constructing the resolved URI
diff --git a/src/namespaces.h b/src/namespaces.h
index e98eb5e1..5e2aa8c7 100644
--- a/src/namespaces.h
+++ b/src/namespaces.h
@@ -17,7 +17,9 @@
#ifndef SERD_NAMESPACES_H
#define SERD_NAMESPACES_H
-#define NS_XSD "http://www.w3.org/2001/XMLSchema#"
+#define NS_OWL "http://www.w3.org/2002/07/owl#"
#define NS_RDF "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+#define NS_RDFS "http://www.w3.org/2000/01/rdf-schema#"
+#define NS_XSD "http://www.w3.org/2001/XMLSchema#"
#endif // SERD_NAMESPACES_H
diff --git a/src/serdi.c b/src/serdi.c
index b0f96f6c..6e3be8b0 100644
--- a/src/serdi.c
+++ b/src/serdi.c
@@ -64,7 +64,8 @@ print_usage(const char* name, bool error)
fprintf(os, "Use - for INPUT to read from standard input.\n\n");
fprintf(os, " -C Convert literals to canonical form.\n");
fprintf(os, " -I BASE_URI Input base URI.\n");
- fprintf(os, " -V Validate inputs.\n");
+ fprintf(os, " -V CHECKS Validate with checks matching CHECKS.\n");
+ fprintf(os, " -X CHECKS Exclude validation checks matching CHECKS.\n");
fprintf(os, " -a Write ASCII output if possible.\n");
fprintf(os, " -b Fast bulk output for large serialisations.\n");
fprintf(os, " -c PREFIX Chop PREFIX from matching blank node IDs.\n");
@@ -75,15 +76,17 @@ print_usage(const char* name, bool error)
fprintf(os, " -i SYNTAX Input syntax: turtle/ntriples/trig/nquads.\n");
fprintf(os, " -k BYTES Parser stack size.\n");
fprintf(os, " -l Lax (non-strict) parsing.\n");
- fprintf(os, " -m Build and serialise a model (no streaming).\n");
- fprintf(os, " -o SYNTAX Output syntax: turtle/ntriples/nquads.\n");
+ fprintf(os, " -m Build a model in memory before writing.\n");
+ fprintf(os, " -o SYNTAX Output syntax: empty/turtle/ntriples/nquads.\n");
fprintf(os, " -p PREFIX Add PREFIX to blank node IDs.\n");
fprintf(os, " -q Suppress all output except data.\n");
fprintf(os, " -r ROOT_URI Keep relative URIs within ROOT_URI.\n");
- fprintf(os, " -s INPUT Parse INPUT as string.\n");
+ fprintf(os, " -s STRING Parse STRING as input.\n");
fprintf(os, " -t Write terser output without newlines.\n");
fprintf(os, " -v Display version information and exit.\n");
+ fprintf(os, " -w FILENAME Write output to FILENAME instead of stdout.\n");
fprintf(os, " -x Support parsing variable nodes like `?x'.\n");
+ fprintf(os, "\nSee serdi(1) for more detailed information: man serdi\n");
return error ? 1 : 0;
}
@@ -235,7 +238,18 @@ main(int argc, char** argv)
base = serd_new_uri(SERD_MEASURE_STRING(argv[a]));
} else if (argv[a][1] == 'V') {
+ if (++a == argc) {
+ return missing_arg(argv[0], 'V');
+ }
+
+ // Just enable validation and skip the pattern, checks are parsed below
validate = use_model = true;
+ } else if (argv[a][1] == 'X') {
+ if (++a == argc) {
+ return missing_arg(argv[0], 'X');
+ }
+
+ // Checks are parsed below
} else if (argv[a][1] == 'a') {
writer_flags |= SERD_WRITE_ASCII;
} else if (argv[a][1] == 'b') {
@@ -390,7 +404,8 @@ main(int argc, char** argv)
if (use_model) {
const SerdModelFlags flags =
SERD_INDEX_SPO | (input_has_graphs ? SERD_INDEX_GRAPHS : 0u) |
- (no_inline ? 0u : SERD_INDEX_OPS) | (validate ? SERD_STORE_CURSORS : 0u);
+ (no_inline ? 0u : SERD_INDEX_OPS) |
+ (validate ? (SERD_STORE_CURSORS | SERD_INDEX_POS) : 0u);
model = serd_model_new(world, flags);
inserter = serd_inserter_new(model, env, NULL);
@@ -453,8 +468,10 @@ main(int argc, char** argv)
for (int i = 0; i < n_inputs; ++i) {
if (!base) {
- SerdNode* file_uri =
- serd_new_file_uri(SERD_MEASURE_STRING(inputs[i]), SERD_EMPTY_STRING());
+ SerdNode* file_uri = serd_new_real_file_uri(inputs[i], NULL);
+ /* SERD_MEASURE_STRING(inputs[i]), SERD_EMPTY_STRING()); */
+
+ /* fprintf(stderr, "SET BASE URI <%s>\n", serd_node_string(file_uri)); */
serd_env_set_base_uri(env, serd_node_string_view(file_uri));
serd_node_free(file_uri);
@@ -473,18 +490,21 @@ main(int argc, char** argv)
inputs[i],
n_inputs > 1 ? prefix : add_prefix,
bulk_read))) {
+ /* serd_world_logf(world, */
+ /* "serd", */
+ /* SERD_LOG_LEVEL_ERR, */
+ /* 0, */
+ /* NULL, */
+ /* "error parsing %s\n", */
+ /* inputs[i]); */
break;
}
}
free(prefix);
- if (!st && validate) {
- st = serd_validate(model);
- }
-
- if (st <= SERD_FAILURE && use_model) {
+ if (!st && output_syntax != SERD_SYNTAX_EMPTY && use_model) {
const SerdSink* writer_sink = serd_writer_sink(writer);
- SerdRange* range = serd_model_all(model);
+ SerdRange* range = serd_model_all(model, SERD_ORDER_GSPO);
serd_env_write_prefixes(env, writer_sink);
@@ -492,6 +512,22 @@ main(int argc, char** argv)
serd_range_free(range);
}
+ if (!st && validate) {
+ SerdValidator* const validator = serd_validator_new(world);
+
+ for (int i = 1; i < argc && argv[i][0] == '-'; ++i) {
+ if (argv[i][1] == 'V') {
+ serd_validator_enable_checks(validator, argv[++i]);
+ } else if (argv[i][1] == 'X') {
+ serd_validator_disable_checks(validator, argv[++i]);
+ }
+ }
+
+ st = serd_validate_model(validator, model, NULL);
+
+ serd_validator_free(validator);
+ }
+
serd_sink_free(canon);
serd_sink_free(filter);
serd_sink_free(inserter);
@@ -508,5 +544,5 @@ main(int argc, char** argv)
serd_byte_sink_free(byte_sink);
- return (st > SERD_FAILURE) ? 1 : 0;
+ return st <= SERD_FAILURE ? 0 : (int)st;
}
diff --git a/src/string.c b/src/string.c
index d9a0140c..f91d6e52 100644
--- a/src/string.c
+++ b/src/string.c
@@ -41,7 +41,7 @@ serd_strerror(SerdStatus status)
case SERD_FAILURE:
return "Non-fatal failure";
case SERD_ERR_UNKNOWN:
- return "Unknown error";
+ break;
case SERD_ERR_BAD_SYNTAX:
return "Invalid syntax";
case SERD_ERR_BAD_ARG:
@@ -70,8 +70,6 @@ serd_strerror(SerdStatus status)
return "Invalid or unresolved URI";
case SERD_ERR_INVALID:
return "Invalid data";
- default:
- break;
}
return "Unknown error"; // never reached
}
diff --git a/src/validate.c b/src/validate.c
index 46291b64..8c5ff073 100644
--- a/src/validate.c
+++ b/src/validate.c
@@ -14,9 +14,10 @@
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
-#include "serd_config.h" // IWYU pragma: keep
-
+#include "macros.h"
#include "model.h"
+#include "namespaces.h"
+#include "node.h"
#include "rerex/rerex.h"
#include "serd/serd.h"
#include "world.h"
@@ -27,121 +28,235 @@
#include <stdlib.h>
#include <string.h>
+#include <assert.h>
+
+/* #define NS_checks "http://drobilla.net/sw/serd/checks#" */
#define NS_owl "http://www.w3.org/2002/07/owl#"
#define NS_rdf "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
#define NS_rdfs "http://www.w3.org/2000/01/rdf-schema#"
#define NS_xsd "http://www.w3.org/2001/XMLSchema#"
-#define VERRORF(ctx, statement, fmt, ...) \
- report(ctx, statement, SERD_LOG_LEVEL_ERR, fmt, __VA_ARGS__)
-
-#define VERROR(ctx, statement, fmt) \
- report(ctx, statement, SERD_LOG_LEVEL_ERR, fmt)
-
-#define VWARNF(ctx, statement, fmt, ...) \
- report(ctx, statement, SERD_LOG_LEVEL_WARNING, fmt, __VA_ARGS__)
-
-#define VNOTEF(ctx, statement, fmt, ...) \
- report(ctx, statement, SERD_LOG_LEVEL_NOTICE, fmt, __VA_ARGS__)
-
-#define VNOTE(ctx, statement, fmt) \
- report(ctx, statement, SERD_LOG_LEVEL_NOTICE, fmt)
-
#define SERD_FOREACH(name, range) \
for (const SerdStatement*(name) = NULL; \
!serd_range_empty(range) && ((name) = serd_range_front(range)); \
serd_range_next(range))
+#define SERD_FOREACH_NODE(field, name, range) \
+ for (const SerdNode*(name) = NULL; \
+ !serd_range_empty(range) && \
+ (name = serd_statement_node(serd_range_front(range), field)); \
+ serd_range_next(range))
+
+#define NODE_FMT "%s%s%s"
+
+#define NODE_ARGS(node) \
+ open_quote(node), serd_node_string(node), close_quote(node)
+
+#define N_CHECKS 32
+
+static const char* check_names[N_CHECKS] = {
+ "allValuesFrom", //
+ "anyUri", //
+ "cardinalityEqual", //
+ "cardinalityMax", //
+ "cardinalityMin", //
+ "classCycle", //
+ "classLabel", //
+ "classType", //
+ "datatypeProperty", //
+ "datatypeType", //
+ "deprecatedClass", //
+ "deprecatedProperty", //
+ "functionalProperty", //
+ "instanceLiteral", //
+ "instanceType", //
+ "inverseFunctionalProperty", //
+ "literalInstance", //
+ "literalMaxExclusive", //
+ "literalMaxInclusive", //
+ "literalMinExclusive", //
+ "literalMinInclusive", //
+ "literalPattern", //
+ "literalRestriction", //
+ "literalValue", //
+ "objectProperty", //
+ "plainLiteralDatatype", //
+ "predicateType", //
+ "propertyCycle", //
+ "propertyDomain", //
+ "propertyLabel", //
+ "propertyRange", //
+ "someValuesFrom", //
+};
+
typedef unsigned long Count;
typedef struct {
- SerdNode* owl_Class;
- SerdNode* owl_DatatypeProperty;
- SerdNode* owl_FunctionalProperty;
- SerdNode* owl_InverseFunctionalProperty;
- SerdNode* owl_ObjectProperty;
- SerdNode* owl_Restriction;
- SerdNode* owl_Thing;
- SerdNode* owl_allValuesFrom;
- SerdNode* owl_cardinality;
- SerdNode* owl_equivalentClass;
- SerdNode* owl_maxCardinality;
- SerdNode* owl_minCardinality;
- SerdNode* owl_onDatatype;
- SerdNode* owl_onProperty;
- SerdNode* owl_someValuesFrom;
- SerdNode* owl_withRestrictions;
- SerdNode* rdf_PlainLiteral;
- SerdNode* rdf_Property;
- SerdNode* rdf_first;
- SerdNode* rdf_rest;
- SerdNode* rdf_type;
- SerdNode* rdfs_Class;
- SerdNode* rdfs_Datatype;
- SerdNode* rdfs_Literal;
- SerdNode* rdfs_Resource;
- SerdNode* rdfs_domain;
- SerdNode* rdfs_label;
- SerdNode* rdfs_range;
- SerdNode* rdfs_subClassOf;
- SerdNode* xsd_anyURI;
- SerdNode* xsd_float;
- SerdNode* xsd_decimal;
- SerdNode* xsd_double;
- SerdNode* xsd_maxExclusive;
- SerdNode* xsd_maxInclusive;
- SerdNode* xsd_minExclusive;
- SerdNode* xsd_minInclusive;
- SerdNode* xsd_pattern;
- SerdNode* xsd_string;
- SerdNode* sentinel;
+ const SerdNode* owl_Class;
+ const SerdNode* owl_DatatypeProperty;
+ const SerdNode* owl_deprecated;
+ const SerdNode* owl_DeprecatedClass;
+ const SerdNode* owl_DeprecatedProperty;
+ const SerdNode* owl_FunctionalProperty;
+ const SerdNode* owl_InverseFunctionalProperty;
+ const SerdNode* owl_ObjectProperty;
+ const SerdNode* owl_Restriction;
+ const SerdNode* owl_Thing;
+ const SerdNode* owl_allValuesFrom;
+ const SerdNode* owl_cardinality;
+ const SerdNode* owl_equivalentClass;
+ const SerdNode* owl_maxCardinality;
+ const SerdNode* owl_minCardinality;
+ const SerdNode* owl_onDatatype;
+ const SerdNode* owl_onProperty;
+ const SerdNode* owl_someValuesFrom;
+ const SerdNode* owl_withRestrictions;
+ const SerdNode* rdf_PlainLiteral;
+ const SerdNode* rdf_Property;
+ const SerdNode* rdf_first;
+ const SerdNode* rdf_rest;
+ const SerdNode* rdf_type;
+ const SerdNode* rdfs_Class;
+ const SerdNode* rdfs_Datatype;
+ const SerdNode* rdfs_Literal;
+ const SerdNode* rdfs_Resource;
+ const SerdNode* rdfs_domain;
+ const SerdNode* rdfs_label;
+ const SerdNode* rdfs_range;
+ const SerdNode* rdfs_subClassOf;
+ const SerdNode* rdfs_subPropertyOf;
+ const SerdNode* xsd_anyURI;
+ const SerdNode* xsd_maxExclusive;
+ const SerdNode* xsd_maxInclusive;
+ const SerdNode* xsd_minExclusive;
+ const SerdNode* xsd_minInclusive;
+ const SerdNode* xsd_pattern;
+ const SerdNode* sentinel;
} URIs;
-typedef struct {
- URIs uris;
+struct SerdValidatorImpl {
+ const SerdWorld* world;
const SerdModel* model;
+ const SerdNode* graph;
+ const SerdNode* true_node;
+ URIs uris;
+ uint32_t checks;
unsigned n_errors;
- unsigned n_restrictions;
- bool quiet;
-} ValidationContext;
+ unsigned n_checks;
+ bool suppressed;
+};
-static int
-check_class_restriction(ValidationContext* ctx,
+typedef struct {
+ const char* name;
+} Check;
+
+static SerdStatus
+check_class_restriction(SerdValidator* ctx,
+ const SerdNode* root_klass,
const SerdNode* restriction,
const SerdStatement* statement,
const SerdNode* instance);
-SERD_LOG_FUNC(4, 5)
-static int
-report(ValidationContext* ctx,
- const SerdStatement* statement,
- const SerdLogLevel level,
- const char* fmt,
- ...)
+static const SerdNode*
+string_node(const SerdValidator* const ctx, const SerdNode* const node)
+{
+ const SerdNode* const label =
+ serd_model_get(ctx->model, node, ctx->uris.rdfs_label, NULL, NULL);
+
+ return label ? label : node;
+}
+
+static const char*
+open_quote(const SerdNode* const node)
+{
+ return (serd_node_type(node) == SERD_LITERAL) ? "\""
+ : (serd_node_type(node) == SERD_URI) ? "<"
+ : (serd_node_type(node) == SERD_BLANK) ? "_:"
+ : "";
+}
+
+static const char*
+close_quote(const SerdNode* const node)
{
- if (ctx->quiet) {
- return 0;
+ return (serd_node_type(node) == SERD_LITERAL) ? "\""
+ : (serd_node_type(node) == SERD_URI) ? ">"
+ : "";
+}
+
+SERD_LOG_FUNC(5, 0)
+static void
+vreportf(SerdValidator* const ctx,
+ const SerdValidatorCheck check,
+ const SerdLogLevel level,
+ const SerdStatement* const statement,
+ const char* const fmt,
+ va_list args)
+{
+ const char* file = NULL;
+ char line[24] = {0};
+ char col[24] = {0};
+ char status_string[12] = {0};
+
+ snprintf(status_string, sizeof(status_string), "%d", SERD_ERR_INVALID);
+
+ const SerdCursor* const cursor =
+ statement ? serd_statement_cursor(statement) : NULL;
+
+ if (cursor) {
+ file = serd_node_string(serd_cursor_name(cursor));
+
+ snprintf(line, sizeof(line), "%u", serd_cursor_line(cursor));
+ snprintf(col, sizeof(col), "%u", serd_cursor_column(cursor));
}
- va_list args;
- va_start(args, fmt);
- serd_world_vlogf_internal(ctx->model->world,
- SERD_ERR_INVALID,
- level,
- serd_statement_cursor(statement),
- fmt,
- args);
- va_end(args);
+ const SerdLogField fields[] = {{"SERD_STATUS", status_string},
+ {"SERD_CHECK", check_names[check]},
+ {"SERD_FILE", file},
+ {"SERD_LINE", line},
+ {"SERD_COL", col}};
- ++ctx->n_errors;
- return 1;
+ serd_world_vlogf(
+ ctx->model->world, "serd", level, cursor ? 5 : 2, fields, fmt, args);
}
-static bool
-check(ValidationContext* ctx, const bool value)
+SERD_LOG_FUNC(5, 6)
+static SerdStatus
+report_check(SerdValidator* const ctx,
+ const SerdStatement* const statement,
+ const SerdValidatorCheck check,
+ const bool condition,
+ const char* fmt,
+ ...)
{
- ++ctx->n_restrictions;
- return value;
+ if (!ctx->suppressed && (ctx->checks & (1u << check))) {
+ ctx->n_checks += 1;
+ ctx->n_errors += condition ? 0 : 1;
+
+ if (!condition) {
+ va_list args;
+ va_start(args, fmt);
+ vreportf(ctx, check, SERD_LOG_LEVEL_ERR, statement, fmt, args);
+ va_end(args);
+ }
+ }
+
+ return condition ? SERD_SUCCESS : SERD_ERR_INVALID;
+}
+
+SERD_LOG_FUNC(4, 5)
+static void
+log_note(SerdValidator* const ctx,
+ const SerdStatement* const statement,
+ const SerdValidatorCheck check,
+ const char* fmt,
+ ...)
+{
+ if (!ctx->suppressed) {
+ va_list args;
+ va_start(args, fmt);
+ vreportf(ctx, check, SERD_LOG_LEVEL_NOTICE, statement, fmt, args);
+ va_end(args);
+ }
}
/*
@@ -151,10 +266,10 @@ check(ValidationContext* ctx, const bool value)
following `pred` arcs starting from child.
*/
static bool
-is_descendant(ValidationContext* ctx,
- const SerdNode* child,
- const SerdNode* parent,
- const SerdNode* pred)
+is_descendant(SerdValidator* const ctx,
+ const SerdNode* const child,
+ const SerdNode* const parent,
+ const SerdNode* const pred)
{
if (serd_node_equals(child, parent) ||
serd_model_ask(
@@ -162,206 +277,310 @@ is_descendant(ValidationContext* ctx,
return true;
}
- SerdRange* i = serd_model_range(ctx->model, child, pred, NULL, NULL);
- SERD_FOREACH (s, i) {
- const SerdNode* o = serd_statement_object(s);
+ SerdRange* const i = serd_model_range(ctx->model, child, pred, NULL, NULL);
+ SERD_FOREACH_NODE (SERD_OBJECT, o, i) {
if (!serd_node_equals(child, o) && is_descendant(ctx, o, parent, pred)) {
serd_range_free(i);
return true;
}
}
- serd_range_free(i);
+ serd_range_free(i);
return false;
}
-// Return true iff `child` is a subclass of `parent`
-static bool
-is_subclass(ValidationContext* ctx,
- const SerdNode* child,
- const SerdNode* parent)
+// Return true iff `klass` is a subclass of `super`
+static inline bool
+is_subclass(SerdValidator* const ctx,
+ const SerdNode* const klass,
+ const SerdNode* const super)
{
- return is_descendant(ctx, child, parent, ctx->uris.rdfs_subClassOf);
+ return is_descendant(ctx, klass, super, ctx->uris.rdfs_subClassOf);
}
-// Return true iff `child` is a sub-datatype of `parent`
-static bool
-is_subdatatype(ValidationContext* ctx,
- const SerdNode* child,
- const SerdNode* parent)
+static inline void
+update_status(SerdStatus* const old, const SerdStatus next)
{
- return is_descendant(ctx, child, parent, ctx->uris.owl_onDatatype);
+ *old = next > *old ? next : *old;
}
-static bool
-regex_match(ValidationContext* const ctx,
- const SerdStatement* const pattern_statement,
- const char* const regex,
- const char* const str)
+static inline SerdStatus
+merge_status(const SerdStatus a, const SerdStatus b)
{
- RerexPattern* re = NULL;
- size_t end = 0;
- const RerexStatus st = rerex_compile(regex, &end, &re);
- if (st) {
- VERRORF(ctx,
- pattern_statement,
- "Error in pattern \"%s\" at offset %zu (%s)\n",
- regex,
- end,
- rerex_strerror(st));
- return false;
+ return a > b ? a : b;
+}
+
+static int
+bound_cmp(SerdValidator* ctx,
+ const SerdStatement* literal_statement,
+ const SerdNode* literal,
+ const SerdNode* type,
+ const SerdStatement* bound_statement,
+ const SerdNode* bound)
+{
+ const ExessDatatype value_type =
+ exess_datatype_from_uri(serd_node_string(type));
+
+ if (value_type != EXESS_NOTHING) {
+ const ExessVariant bound_value = serd_node_get_value_as(bound, value_type);
+
+ if (bound_value.datatype == EXESS_NOTHING) {
+ return !!serd_world_logf_internal(
+ ctx->model->world,
+ SERD_ERR_INVALID,
+ SERD_LOG_LEVEL_ERR,
+ serd_statement_cursor(bound_statement),
+ "Failed to parse bound literal \"%s\" (%s)",
+ serd_node_string(bound),
+ exess_strerror(bound_value.value.as_status));
+ }
+
+ const ExessVariant literal_value =
+ serd_node_get_value_as(literal, value_type);
+
+ if (literal_value.datatype == EXESS_NOTHING) {
+ return !!serd_world_logf_internal(
+ ctx->model->world,
+ SERD_ERR_INVALID,
+ SERD_LOG_LEVEL_ERR,
+ serd_statement_cursor(literal_statement),
+ "Failed to parse literal \"%s\" (%s)",
+ serd_node_string(literal),
+ exess_strerror(literal_value.value.as_status));
+ }
+
+ return exess_compare(literal_value, bound_value);
}
- RerexMatcher* matcher = rerex_new_matcher(re);
- const bool ret = rerex_match(matcher, str);
+ return strcmp(serd_node_string(literal), serd_node_string(bound));
+}
- rerex_free_matcher(matcher);
- rerex_free_pattern(re);
+static inline bool
+less(const int cmp)
+{
+ return cmp < 0;
+}
- return ret;
+static inline bool
+less_equal(const int cmp)
+{
+ return cmp <= 0;
}
-static int
-bound_cmp(ValidationContext* ctx,
- const SerdNode* literal,
- const SerdNode* type,
- const SerdNode* bound)
+static inline bool
+greater(const int cmp)
{
- const bool is_numeric = (is_subdatatype(ctx, type, ctx->uris.xsd_decimal) ||
- is_subdatatype(ctx, type, ctx->uris.xsd_double));
+ return cmp > 0;
+}
- if (is_numeric) {
- const double fbound = serd_get_double(bound);
- const double fliteral = serd_get_double(literal);
+static inline bool
+greater_equal(const int cmp)
+{
+ return cmp >= 0;
+}
- return ((fliteral < fbound) ? -1 : (fliteral > fbound) ? 1 : 0);
+static SerdStatus
+check_bound(SerdValidator* const ctx,
+ const SerdValidatorCheck check,
+ const SerdStatement* const statement,
+ const SerdNode* const literal,
+ const SerdNode* const type,
+ const SerdNode* const restriction,
+ const SerdNode* const bound_property,
+ bool (*pred)(int),
+ const char* const message)
+{
+ SerdIter* const b =
+ serd_model_find(ctx->model, restriction, bound_property, 0, 0);
+ if (!b) {
+ return SERD_SUCCESS;
}
- return strcmp(serd_node_string(literal), serd_node_string(bound));
+ const SerdNode* const bound = serd_statement_object(serd_iter_get(b));
+ const int cmp =
+ bound_cmp(ctx, statement, literal, type, serd_iter_get(b), bound);
+
+ serd_iter_free(b);
+
+ return report_check(ctx,
+ statement,
+ check,
+ pred(cmp),
+ "Value \"%s\" %s \"%s\"",
+ serd_node_string(literal),
+ message,
+ serd_node_string(bound));
}
-static bool
-check_literal_restriction(ValidationContext* ctx,
- const SerdStatement* statement,
- const SerdNode* literal,
- const SerdNode* type,
- const SerdNode* restriction)
+static RerexPattern*
+parse_regex(const SerdWorld* const world,
+ const SerdStatement* const pattern_statement,
+ const char* const regex)
{
- const char* str = serd_node_string(literal);
+ const SerdCursor* const cursor =
+ pattern_statement ? serd_statement_cursor(pattern_statement) : NULL;
- // Check xsd:pattern
- const SerdStatement* pat_statement = serd_model_get_statement(
- ctx->model, restriction, ctx->uris.xsd_pattern, 0, 0);
- if (pat_statement) {
- const SerdNode* pat_node = serd_statement_object(pat_statement);
- const char* pat = serd_node_string(pat_node);
- if (check(ctx, !regex_match(ctx, pat_statement, pat, str))) {
- VERRORF(ctx,
- statement,
- "Value \"%s\" does not match pattern \"%s\"\n",
- serd_node_string(literal),
- pat);
- return false;
- }
+ RerexPattern* re = NULL;
+ size_t end = 0;
+ const RerexStatus st = rerex_compile(regex, &end, &re);
+ if (st) {
+ serd_world_logf_internal(world,
+ SERD_ERR_INVALID,
+ SERD_LOG_LEVEL_ERR,
+ cursor,
+ "Error in pattern \"%s\" at offset %zu (%s)",
+ regex,
+ end,
+ rerex_strerror(st));
+ return NULL;
}
- // Check xsd:minInclusive
- const SerdNode* lower =
- serd_model_get(ctx->model, restriction, ctx->uris.xsd_minInclusive, 0, 0);
- if (lower) {
- if (check(ctx, bound_cmp(ctx, literal, type, lower) < 0)) {
- VERRORF(ctx,
- statement,
- "Value \"%s\" < minimum \"%s\"\n",
- serd_node_string(literal),
- serd_node_string(lower));
- return false;
- }
- }
+ return re;
+}
- // Check xsd:maxInclusive
- const SerdNode* upper =
- serd_model_get(ctx->model, restriction, ctx->uris.xsd_maxInclusive, 0, 0);
- if (upper) {
- if (check(ctx, bound_cmp(ctx, literal, type, upper) > 0)) {
- VERRORF(ctx,
- statement,
- "Value \"%s\" > than maximum \"%s\"\n",
- serd_node_string(literal),
- serd_node_string(upper));
- return false;
- }
+static bool
+regex_match(SerdValidator* const ctx,
+ const SerdStatement* const pattern_statement,
+ const char* const regex,
+ const char* const str)
+{
+ RerexPattern* const re = parse_regex(ctx->world, pattern_statement, regex);
+ if (!re) {
+ return false;
}
- // Check xsd:minExclusive
- const SerdNode* elower =
- serd_model_get(ctx->model, restriction, ctx->uris.xsd_minExclusive, 0, 0);
- if (elower) {
- if (check(ctx, bound_cmp(ctx, literal, type, elower) <= 0)) {
- VERRORF(ctx,
- statement,
- "Value \"%s\" <= exclusive minimum \"%s\"\n",
- serd_node_string(literal),
- serd_node_string(elower));
- return false;
- }
- }
+ RerexMatcher* const matcher = rerex_new_matcher(re);
+ const bool ret = rerex_match(matcher, str);
- // Check xsd:maxExclusive
- const SerdNode* eupper =
- serd_model_get(ctx->model, restriction, ctx->uris.xsd_maxExclusive, 0, 0);
- if (eupper) {
- if (check(ctx, bound_cmp(ctx, literal, type, eupper) >= 0)) {
- VERRORF(ctx,
- statement,
- "Value \"%s\" >= exclusive maximum \"%s\"\n",
- serd_node_string(literal),
- serd_node_string(eupper));
- return false;
- }
- ++ctx->n_restrictions;
- }
+ rerex_free_matcher(matcher);
+ rerex_free_pattern(re);
- return true; // Unknown restriction, be quietly tolerant
+ return ret;
}
-static bool
-is_datatype(ValidationContext* ctx, const SerdNode* dtype)
+static SerdStatus
+check_literal_restriction(SerdValidator* const ctx,
+ const SerdStatement* const statement,
+ const SerdNode* const literal,
+ const SerdNode* const type,
+ const SerdNode* const restriction)
{
- SerdRange* t =
- serd_model_range(ctx->model, dtype, ctx->uris.rdf_type, NULL, NULL);
- SERD_FOREACH (s, t) {
- const SerdNode* type = serd_statement_object(s);
- if (is_subdatatype(ctx, type, ctx->uris.rdfs_Datatype)) {
- serd_range_free(t);
- return true; // Subdatatype of rdfs:Datatype
- }
+ SerdStatus st = SERD_SUCCESS;
+
+ // Check xsd:pattern
+ const SerdStatement* const pat_statement = serd_model_get_statement(
+ ctx->model, restriction, ctx->uris.xsd_pattern, 0, 0);
+ if (pat_statement) {
+ const char* const str = serd_node_string(literal);
+ const SerdNode* const pat_node = serd_statement_object(pat_statement);
+ const char* const pat = serd_node_string(pat_node);
+
+ st = merge_status(st,
+ report_check(ctx,
+ statement,
+ SERD_CHECK_LITERAL_PATTERN,
+ regex_match(ctx, pat_statement, pat, str),
+ "Value \"%s\" doesn't match pattern \"%s\"",
+ serd_node_string(literal),
+ pat));
}
- serd_range_free(t);
- return false;
+ // Check inclusive/exclusive min and max
+
+ typedef bool (*BoundCmpPredicate)(int);
+
+ typedef struct {
+ SerdValidatorCheck check_id;
+ const SerdNode* restriction_property;
+ BoundCmpPredicate pred;
+ const char* const message;
+ } BoundCheck;
+
+ const BoundCheck bound_checks[] = {
+ {SERD_CHECK_LITERAL_MIN_INCLUSIVE,
+ ctx->uris.xsd_minInclusive,
+ greater_equal,
+ "<"},
+ {SERD_CHECK_LITERAL_MAX_INCLUSIVE,
+ ctx->uris.xsd_maxInclusive,
+ less_equal,
+ ">"},
+ {SERD_CHECK_LITERAL_MIN_EXCLUSIVE,
+ ctx->uris.xsd_minExclusive,
+ greater,
+ "<="},
+ {SERD_CHECK_LITERAL_MAX_EXCLUSIVE, ctx->uris.xsd_maxExclusive, less, ">="},
+ };
+
+ for (size_t i = 0; i < sizeof(bound_checks) / sizeof(BoundCheck); ++i) {
+ st = merge_status(st,
+ check_bound(ctx,
+ bound_checks[i].check_id,
+ statement,
+ literal,
+ type,
+ restriction,
+ bound_checks[i].restriction_property,
+ bound_checks[i].pred,
+ bound_checks[i].message));
+ }
+
+ return st;
}
static bool
-literal_is_valid(ValidationContext* ctx,
- const SerdStatement* statement,
- const SerdNode* literal,
- const SerdNode* type)
+literal_is_valid(SerdValidator* const ctx,
+ const SerdStatement* const statement,
+ const SerdNode* const literal,
+ const SerdNode* const type)
{
if (!type) {
return true;
}
// Check that datatype is defined
- const SerdNode* datatype = serd_node_datatype(literal);
- if (datatype && !is_datatype(ctx, datatype)) {
- VERRORF(ctx,
- statement,
- "Datatype <%s> is not defined\n",
- serd_node_string(datatype));
+ const SerdNode* const node_datatype = serd_node_datatype(literal);
+ if (node_datatype && report_check(ctx,
+ statement,
+ SERD_CHECK_DATATYPE_TYPE,
+ serd_model_ask(ctx->model,
+ node_datatype,
+ ctx->uris.rdf_type,
+ ctx->uris.rdfs_Datatype,
+ NULL),
+ "Undefined datatype <%s>",
+ serd_node_string(node_datatype))) {
return false;
}
+ const SerdNode* const type_string = string_node(ctx, type);
+
+ const ExessDatatype value_type =
+ node_datatype ? exess_datatype_from_uri(serd_node_string(node_datatype))
+ : EXESS_NOTHING;
+
+ if (value_type != EXESS_NOTHING) {
+ /* Check if the literal parses correctly by measuring the canonical string.
+ This is better than trying to read a variant here, because it
+ automatically supports some unbounded datatypes like xsd:decimal and
+ xsd:base64Binary without needing to allocate space for the value. */
+
+ const ExessResult r =
+ exess_write_canonical(serd_node_string(literal), value_type, 0, NULL);
+
+ if (report_check(ctx,
+ statement,
+ SERD_CHECK_LITERAL_VALUE,
+ r.status == EXESS_SUCCESS,
+ "Invalid xsd:%s literal \"%s\" (%s)",
+ serd_node_string(node_datatype) + sizeof(EXESS_XSD_URI) -
+ 1,
+ serd_node_string(literal),
+ exess_strerror(r.status))) {
+ return false;
+ }
+ }
+
// Find restrictions list
const SerdNode* head =
serd_model_get(ctx->model, type, ctx->uris.owl_withRestrictions, 0, 0);
@@ -380,11 +599,15 @@ literal_is_valid(ValidationContext* ctx,
break;
}
- const SerdNode* first = serd_statement_object(s_first);
+ const SerdNode* const first = serd_statement_object(s_first);
// Check this restriction
- if (!check_literal_restriction(ctx, statement, literal, type, first)) {
- VNOTEF(ctx, s_first, "Restriction on <%s>\n", serd_node_string(type));
+ if (check_literal_restriction(ctx, statement, literal, type, first)) {
+ log_note(ctx,
+ s_first,
+ SERD_CHECK_LITERAL_RESTRICTION,
+ "Restriction on datatype " NODE_FMT,
+ NODE_ARGS(type_string));
serd_iter_free(i_first);
return false;
}
@@ -395,152 +618,205 @@ literal_is_valid(ValidationContext* ctx,
}
// Recurse up datatype hierarchy
- const SerdNode* super =
+ const SerdNode* const super =
serd_model_get(ctx->model, type, ctx->uris.owl_onDatatype, 0, 0);
return super ? literal_is_valid(ctx, statement, literal, super) : true;
}
static bool
-is_a(ValidationContext* ctx, const SerdNode* subject, const SerdNode* type)
-{
- return serd_model_ask(ctx->model, subject, ctx->uris.rdf_type, type, 0);
-}
-
-static bool
-has_explicit_type(ValidationContext* ctx,
- const SerdNode* node,
- const SerdNode* klass)
+is_a(SerdValidator* const ctx,
+ const SerdNode* const node,
+ const SerdNode* const type)
{
- if (is_a(ctx, node, klass)) {
- return true; // Directly stated to be an instance
+ if (serd_model_ask(ctx->model, node, ctx->uris.rdf_type, type, 0)) {
+ return true; // Instance explicitly has this type
}
- SerdRange* t =
+ SerdRange* const node_types =
serd_model_range(ctx->model, node, ctx->uris.rdf_type, NULL, NULL);
- SERD_FOREACH (s, t) {
- if (is_subclass(ctx, serd_statement_object(s), klass)) {
- serd_range_free(t);
- return true; // Explicit instance of a subclass
+
+ SERD_FOREACH_NODE (SERD_OBJECT, node_type, node_types) {
+ if (is_subclass(ctx, node_type, type)) {
+ serd_range_free(node_types);
+ return true; // Instance explicitly has a subtype of this type
}
}
- serd_range_free(t);
+ serd_range_free(node_types);
return false;
}
-static bool
-is_instance_of(ValidationContext* ctx,
- const SerdNode* node,
- const SerdNode* klass)
+static SerdStatus
+check_instance_type(SerdValidator* const ctx,
+ const SerdValidatorCheck check,
+ const SerdNode* const root_klass,
+ const SerdStatement* const statement,
+ const SerdNode* const instance,
+ const SerdNode* const klass)
{
- if (!serd_model_ask(ctx->model, node, NULL, NULL, NULL)) {
- /* Nothing about this node known in the model at all, assume it is some
- external resource we can't validate. */
- return true;
+ SerdStatus st = SERD_SUCCESS;
+
+ // Any URI node is an xsd:anyURI
+ if (serd_node_equals(klass, ctx->uris.xsd_anyURI)) {
+ return report_check(ctx,
+ statement,
+ SERD_CHECK_ANY_URI,
+ serd_node_type(instance) == SERD_URI,
+ "Node " NODE_FMT " isn't a URI",
+ NODE_ARGS(instance));
}
- if (serd_node_type(node) == SERD_BLANK) {
- /* Be permissive for blank nodes and don't require explicit type
- annotation, to avoid countless errors with things like lists. */
- return true;
+ // An instance can not be a rdfs:Literal or a rdfs:Datatype
+ if (report_check(ctx,
+ statement,
+ SERD_CHECK_INSTANCE_LITERAL,
+ !is_subclass(ctx, klass, ctx->uris.rdfs_Literal) &&
+ !is_a(ctx, klass, ctx->uris.rdfs_Datatype),
+ "Instance " NODE_FMT " isn't a literal",
+ NODE_ARGS(instance))) {
+ return SERD_ERR_INVALID;
}
- return (has_explicit_type(ctx, node, klass) ||
- serd_node_equals(klass, ctx->uris.rdfs_Resource) ||
- serd_node_equals(klass, ctx->uris.owl_Thing));
-}
-
-static bool
-check_instance_type(ValidationContext* ctx,
- const SerdStatement* statement,
- const SerdNode* node,
- const SerdNode* klass)
-{
- if (is_subclass(ctx, klass, ctx->uris.rdfs_Literal) ||
- is_a(ctx, klass, ctx->uris.rdfs_Datatype)) {
- VERROR(ctx, statement, "Class instance found where literal expected\n");
- return false;
+ // Every instance is a rdfs:Resource and owl:Thing
+ if (serd_node_equals(klass, ctx->uris.rdfs_Resource) ||
+ serd_node_equals(klass, ctx->uris.owl_Thing)) {
+ return SERD_SUCCESS;
}
- if (is_a(ctx, klass, ctx->uris.owl_Restriction)) {
- if (check_class_restriction(ctx, klass, statement, node)) {
- return false;
+ // If the class is a restriction, check it
+ if (serd_model_ask(ctx->model,
+ klass,
+ ctx->uris.rdf_type,
+ ctx->uris.owl_Restriction,
+ NULL)) {
+ if ((st = check_class_restriction(
+ ctx, root_klass, klass, statement, instance))) {
+ return st;
}
}
- SerdRange* r =
+ SerdRange* const superclasses =
serd_model_range(ctx->model, klass, ctx->uris.rdfs_subClassOf, NULL, NULL);
- SERD_FOREACH (s, r) {
- const SerdNode* super = serd_statement_object(s);
- if (!serd_node_equals(super, klass) &&
- !check_instance_type(ctx, statement, node, super)) {
- serd_range_free(r);
- return false;
+ SERD_FOREACH_NODE (SERD_OBJECT, superclass, superclasses) {
+ const SerdNode* const klass_string = string_node(ctx, klass);
+ const SerdNode* const superclass_string = string_node(ctx, superclass);
+ if (!serd_node_equals(klass, superclass) &&
+ check_instance_type(
+ ctx, check, klass, statement, instance, superclass)) {
+ if (serd_node_type(superclass) == SERD_URI ||
+ serd_node_type(superclass) == SERD_CURIE) {
+ log_note(ctx,
+ serd_range_front(superclasses),
+ check,
+ "A " NODE_FMT " is a " NODE_FMT,
+ NODE_ARGS(klass_string),
+ NODE_ARGS(superclass_string));
+ }
+
+ serd_range_free(superclasses);
+ return SERD_ERR_INVALID;
}
}
- serd_range_free(r);
+ serd_range_free(superclasses);
- if (!is_instance_of(ctx, node, klass)) {
- VERRORF(ctx,
- statement,
- "Node %s is not an instance of %s\n",
- serd_node_string(node),
- serd_node_string(klass));
- return false;
+ if (serd_model_ask(ctx->model, instance, ctx->uris.rdf_type, klass, NULL)) {
+ return SERD_SUCCESS;
}
- return true;
+ const SerdNode* const instance_string = string_node(ctx, instance);
+ const SerdNode* const klass_string = string_node(ctx, klass);
+
+ if (!serd_model_ask(ctx->model, instance, NULL, NULL, NULL)) {
+ /* Nothing about this node known in the model at all, assume it is some
+ external resource we can't validate. */
+ serd_world_logf_internal(ctx->model->world,
+ SERD_ERR_INVALID,
+ SERD_LOG_LEVEL_WARNING,
+ serd_statement_cursor(statement),
+ "Nothing known about " NODE_FMT
+ ", assuming it is a " NODE_FMT,
+ NODE_ARGS(instance_string),
+ NODE_ARGS(klass_string));
+
+ // FIXME: test
+ // return SERD_FAILURE;
+ /* return true; */
+ return SERD_FAILURE;
+ }
+
+ if (serd_node_type(instance) == SERD_BLANK) {
+ /* Be permissive for blank nodes and don't require explicit type
+ annotation, to avoid countless errors with things like lists. */
+ // return SERD_FAILURE;
+ return SERD_SUCCESS;
+ }
+
+ return report_check(ctx,
+ statement,
+ SERD_CHECK_INSTANCE_TYPE,
+ is_a(ctx, instance, klass),
+ "Instance " NODE_FMT " isn't a " NODE_FMT,
+ NODE_ARGS(instance_string),
+ NODE_ARGS(klass_string));
}
-static bool
-check_type(ValidationContext* ctx,
- const SerdStatement* statement,
- const SerdNode* node,
- const SerdNode* type)
+static SerdStatus
+check_type(SerdValidator* ctx,
+ const SerdValidatorCheck check,
+ const SerdStatement* statement,
+ const SerdNode* node,
+ const SerdNode* type)
{
- if (serd_node_equals(type, ctx->uris.rdfs_Resource) ||
- serd_node_equals(type, ctx->uris.owl_Thing)) {
- return true; // Trivially true for everything (more or less)
+ const SerdNode* const type_string = string_node(ctx, type);
+
+ // Everything is an rdfs:Resource
+ if (serd_node_equals(type, ctx->uris.rdfs_Resource)) {
+ return SERD_SUCCESS;
}
- if (serd_node_type(node) == SERD_LITERAL) {
+ switch (serd_node_type(node)) {
+ case SERD_LITERAL:
+ // Every literal is an rdfs:Literal
if (serd_node_equals(type, ctx->uris.rdfs_Literal)) {
- return true; // Trivially true for a literal
+ return SERD_SUCCESS;
}
+ // A plain literal can not have a datatype
if (serd_node_equals(type, ctx->uris.rdf_PlainLiteral)) {
- const SerdNode* const datatype = serd_node_datatype(node);
- if (datatype) {
- VERRORF(ctx,
- statement,
- "Literal \"%s\" should be plain, but has datatype "
- "<%s>\n",
- serd_node_string(node),
- serd_node_string(datatype));
- return false;
+ if (report_check(ctx,
+ statement,
+ SERD_CHECK_PLAIN_LITERAL_DATATYPE,
+ !serd_node_datatype(node),
+ "Typed literal \"%s\" isn't a plain literal",
+ serd_node_string(node))) {
+ return SERD_ERR_INVALID;
}
- } else if (!is_a(ctx, type, ctx->uris.rdfs_Datatype)) {
- VERRORF(ctx,
- statement,
- "Literal \"%s\" where instance of <%s> expected\n",
- serd_node_string(node),
- serd_node_string(type));
- return false;
+ } else if (report_check(ctx,
+ statement,
+ SERD_CHECK_LITERAL_INSTANCE,
+ is_a(ctx, type, ctx->uris.rdfs_Datatype),
+ "Literal \"%s\" isn't an instance of " NODE_FMT,
+ serd_node_string(node),
+ NODE_ARGS(type_string))) {
+ return SERD_ERR_INVALID;
}
- return literal_is_valid(ctx, statement, node, type);
- }
+ return literal_is_valid(ctx, statement, node, type) ? SERD_SUCCESS
+ : SERD_ERR_INVALID;
- if (serd_node_type(node) == SERD_URI) {
- if (!is_subdatatype(ctx, type, ctx->uris.xsd_anyURI)) {
- // Only check if type is not anyURI, since node is a URI
- return check_instance_type(ctx, statement, node, type);
+ case SERD_URI:
+ if (serd_node_equals(type, ctx->uris.xsd_anyURI)) {
+ return SERD_SUCCESS;
}
- } else {
- return check_instance_type(ctx, statement, node, type);
+ break;
+
+ case SERD_CURIE:
+ case SERD_BLANK:
+ case SERD_VARIABLE:
+ break;
}
- return true;
+ return check_instance_type(ctx, check, type, statement, node, type);
}
static Count
@@ -556,246 +832,860 @@ count_non_blanks(SerdRange* i, SerdField field)
return n;
}
-static int
-check_statement(ValidationContext* ctx, const SerdStatement* statement)
-{
- int st = 0;
- const URIs* uris = &ctx->uris;
- const SerdNode* subj = serd_statement_subject(statement);
- const SerdNode* pred = serd_statement_predicate(statement);
- const SerdNode* obj = serd_statement_object(statement);
-
- if (serd_node_equals(pred, uris->rdf_type)) {
- // Type statement, check that object is a valid instance of type
- check_type(ctx, statement, subj, obj);
- }
-
- if (!serd_model_ask(ctx->model, pred, uris->rdfs_label, 0, 0)) {
- // Warn if property has no label
- st = VWARNF(
- ctx, statement, "Property <%s> has no label\n", serd_node_string(pred));
- }
-
- if (serd_node_type(obj) == SERD_LITERAL &&
- !literal_is_valid(ctx, statement, obj, serd_node_datatype(obj))) {
- st = SERD_ERR_INVALID;
- }
-
- // Check restrictions based on property type
- if (is_a(ctx, pred, uris->owl_DatatypeProperty)) {
- if (serd_node_type(obj) != SERD_LITERAL) {
- st = VERROR(ctx, statement, "Datatype property with non-literal value\n");
- }
- } else if (is_a(ctx, pred, uris->owl_ObjectProperty)) {
- if (serd_node_type(obj) == SERD_LITERAL) {
- st = VERROR(ctx, statement, "Object property with literal value\n");
- }
- } else if (is_a(ctx, pred, uris->owl_FunctionalProperty)) {
- SerdRange* o = serd_model_range(ctx->model, subj, pred, NULL, NULL);
- const Count n = count_non_blanks(o, SERD_OBJECT);
- if (n > 1) {
- st = VERRORF(ctx, statement, "Functional property with %lu objects\n", n);
- }
- serd_range_free(o);
- } else if (is_a(ctx, pred, uris->owl_InverseFunctionalProperty)) {
- SerdRange* s = serd_model_range(ctx->model, NULL, pred, obj, NULL);
- const Count n = count_non_blanks(s, SERD_SUBJECT);
- if (n > 1) {
- st = VERRORF(
- ctx, statement, "Inverse functional property with %lu subjects\n", n);
- }
- serd_range_free(s);
- } else {
- SerdRange* t = serd_model_range(ctx->model, pred, uris->rdf_type, 0, 0);
-
- bool is_property = false;
- SERD_FOREACH (s, t) {
- const SerdNode* type = serd_statement_object(s);
- if (is_subclass(ctx, type, uris->rdf_Property)) {
- is_property = true;
- break;
+static SerdStatus
+check_cardinality_restriction(SerdValidator* const ctx,
+ const SerdNode* const root_klass,
+ const SerdNode* const restriction,
+ const SerdStatement* const statement,
+ const SerdNode* const instance)
+{
+ const SerdNode* const prop = serd_model_get(
+ ctx->model, restriction, ctx->uris.owl_onProperty, NULL, NULL);
+
+ const SerdStatement* const equal_statement = serd_model_get_statement(
+ ctx->model, restriction, ctx->uris.owl_cardinality, NULL, NULL);
+
+ const SerdStatement* const min_statement = serd_model_get_statement(
+ ctx->model, restriction, ctx->uris.owl_minCardinality, NULL, NULL);
+
+ const SerdStatement* const max_statement = serd_model_get_statement(
+ ctx->model, restriction, ctx->uris.owl_maxCardinality, NULL, NULL);
+
+ if (!equal_statement && !min_statement && !max_statement) {
+ return SERD_SUCCESS;
+ }
+
+ const SerdNode* const prop_string = string_node(ctx, prop);
+ const SerdNode* const klass_string = string_node(ctx, root_klass);
+
+ SerdStatus st = SERD_SUCCESS;
+ const Count n_values =
+ (Count)serd_model_count(ctx->model, instance, prop, NULL, NULL);
+
+ // Check owl:cardinality
+ if (equal_statement) {
+ const SerdNode* card = serd_statement_object(equal_statement);
+ const Count expected = strtoul(serd_node_string(card), NULL, 10);
+ if ((st =
+ report_check(ctx,
+ statement,
+ SERD_CHECK_CARDINALITY_EQUAL,
+ n_values == expected,
+ "Instance " NODE_FMT " has %lu " NODE_FMT " properties",
+ NODE_ARGS(instance),
+ n_values,
+ NODE_ARGS(prop_string)))) {
+ log_note(ctx,
+ equal_statement,
+ SERD_CHECK_CARDINALITY_EQUAL,
+ "A " NODE_FMT " must have exactly %lu",
+ NODE_ARGS(klass_string),
+ expected);
+ return st;
+ }
+ }
+
+ // Check owl:minCardinality
+ if (min_statement) {
+ const SerdNode* card = serd_statement_object(min_statement);
+ const Count n_min = strtoul(serd_node_string(card), NULL, 10);
+ if ((st =
+ report_check(ctx,
+ statement,
+ SERD_CHECK_CARDINALITY_MIN,
+ n_values >= n_min,
+ "Instance " NODE_FMT " has %lu " NODE_FMT " properties",
+ NODE_ARGS(instance),
+ n_values,
+ NODE_ARGS(prop_string)))) {
+ log_note(ctx,
+ min_statement,
+ SERD_CHECK_CARDINALITY_MIN,
+ "A " NODE_FMT " must have at least %lu",
+ NODE_ARGS(klass_string),
+ n_min);
+ return st;
+ }
+ }
+
+ // Check owl:maxCardinality
+ if (max_statement) {
+ const SerdNode* const card = serd_statement_object(max_statement);
+ const Count n_max = strtoul(serd_node_string(card), NULL, 10);
+ if ((st =
+ report_check(ctx,
+ statement,
+ SERD_CHECK_CARDINALITY_MAX,
+ n_values <= n_max,
+ "Instance " NODE_FMT " has %lu " NODE_FMT " properties",
+ NODE_ARGS(instance),
+ n_values,
+ NODE_ARGS(prop_string)))) {
+ log_note(ctx,
+ max_statement,
+ SERD_CHECK_CARDINALITY_MAX,
+ "A " NODE_FMT " must have at most %lu",
+ NODE_ARGS(klass_string),
+ n_max);
+ return st;
+ }
+ }
+
+ return st;
+}
+
+static SerdStatus
+check_property_value_restriction(SerdValidator* const ctx,
+ const SerdNode* const root_klass,
+ const SerdNode* const restriction,
+ const SerdStatement* const statement,
+ const SerdNode* const instance)
+{
+ SerdStatus st = SERD_SUCCESS;
+
+ const SerdNode* const prop = serd_model_get(
+ ctx->model, restriction, ctx->uris.owl_onProperty, NULL, NULL);
+
+ const SerdStatement* const all_statement = serd_model_get_statement(
+ ctx->model, restriction, ctx->uris.owl_allValuesFrom, NULL, NULL);
+
+ const SerdStatement* const some_statement = serd_model_get_statement(
+ ctx->model, restriction, ctx->uris.owl_someValuesFrom, NULL, NULL);
+
+ if (!all_statement && !some_statement) {
+ return SERD_SUCCESS;
+ }
+
+ const SerdNode* const prop_string = string_node(ctx, prop);
+ const SerdNode* const klass_string = string_node(ctx, root_klass);
+
+ SerdRange* const values =
+ serd_model_range(ctx->model, instance, prop, NULL, NULL);
+
+ if (all_statement) {
+ const SerdNode* const type = serd_statement_object(all_statement);
+ const SerdNode* const type_string = string_node(ctx, type);
+ SERD_FOREACH (v, values) {
+ const SerdNode* const value = serd_statement_object(v);
+ const SerdStatus all_st = report_check(
+ ctx,
+ v,
+ SERD_CHECK_ALL_VALUES_FROM,
+ !check_type(ctx, SERD_CHECK_ALL_VALUES_FROM, v, value, type),
+ "Value isn't a " NODE_FMT,
+ NODE_ARGS(type_string));
+
+ if (all_st) {
+ st = merge_status(st, all_st);
+ log_note(ctx,
+ all_statement,
+ SERD_CHECK_ALL_VALUES_FROM,
+ "Required for any " NODE_FMT " of a " NODE_FMT,
+ NODE_ARGS(prop_string),
+ NODE_ARGS(klass_string));
}
}
+ }
- if (!is_property) {
- st = VERROR(ctx, statement, "Use of undefined property\n");
+ if (some_statement) {
+ const SerdNode* const type = serd_statement_object(some_statement);
+ const SerdNode* const type_string = string_node(ctx, type);
+
+ // Search for some value with the required type
+ bool found = false;
+ {
+ ctx->suppressed = true;
+ SERD_FOREACH_NODE (SERD_OBJECT, value, values) {
+ if (!check_type(
+ ctx, SERD_CHECK_SOME_VALUES_FROM, statement, value, type)) {
+ found = true;
+ break;
+ }
+ }
+ ctx->suppressed = false;
}
- serd_range_free(t);
+ const SerdStatus some_st =
+ report_check(ctx,
+ statement,
+ SERD_CHECK_SOME_VALUES_FROM,
+ found,
+ NODE_FMT " has no " NODE_FMT " that is a " NODE_FMT,
+ NODE_ARGS(instance),
+ NODE_ARGS(prop_string),
+ NODE_ARGS(type_string));
+
+ if (some_st) {
+ log_note(ctx,
+ some_statement,
+ SERD_CHECK_SOME_VALUES_FROM,
+ "An instance of " NODE_FMT " must have at least 1",
+ NODE_ARGS(klass_string));
+ }
+
+ st = merge_status(st, some_st);
}
- // Check range
- SerdRange* r = serd_model_range(ctx->model, pred, uris->rdfs_range, 0, 0);
- SERD_FOREACH (s, r) {
- const SerdNode* range = serd_statement_object(s);
- if (!has_explicit_type(ctx, obj, range) &&
- !check_type(ctx, statement, obj, range)) {
- VNOTEF(
- ctx, serd_range_front(r), "In range of <%s>\n", serd_node_string(pred));
+ serd_range_free(values);
+
+ return st;
+}
+
+static SerdStatus
+check_class_restriction(SerdValidator* const ctx,
+ const SerdNode* const root_klass,
+ const SerdNode* const restriction,
+ const SerdStatement* const statement,
+ const SerdNode* const instance)
+{
+ SerdStatus st = SERD_SUCCESS;
+
+ st = merge_status(st,
+ check_cardinality_restriction(
+ ctx, root_klass, restriction, statement, instance));
+
+ st = merge_status(st,
+ check_property_value_restriction(
+ ctx, root_klass, restriction, statement, instance));
+
+ return st;
+}
+
+/* Top-Level Checks */
+
+static SerdStatus
+check_class_label(SerdValidator* const ctx)
+{
+ const SerdModel* const model = ctx->model;
+ const URIs* const uris = &ctx->uris;
+ SerdStatus st = SERD_SUCCESS;
+
+ // For each rdfs:Class
+ SerdRange* const klasses =
+ serd_model_range(model, NULL, uris->rdf_type, uris->rdfs_Class, ctx->graph);
+ SERD_FOREACH (k, klasses) {
+ const SerdNode* const klass = serd_statement_subject(k);
+
+ // Check that it has an rdfs:label in the same graph
+ st = merge_status(
+ st,
+ report_check(
+ ctx,
+ k,
+ SERD_CHECK_CLASS_LABEL,
+ serd_model_ask(ctx->model, klass, uris->rdfs_label, 0, ctx->graph),
+ "Class <%s> has no label",
+ serd_node_string(klass)));
+ }
+ serd_range_free(klasses);
+
+ return st;
+}
+
+static SerdStatus
+check_class_type(SerdValidator* const ctx)
+{
+ // For each explicit instance of a class
+ SerdStatus st = SERD_SUCCESS;
+ const SerdNode* last_klass = NULL;
+ SerdRange* const instances =
+ serd_model_range(ctx->model, NULL, ctx->uris.rdf_type, NULL, ctx->graph);
+ // FIXME: prefer OPS order and skip dupes
+ SERD_FOREACH (i, instances) {
+ const SerdNode* const klass = serd_statement_object(i);
+ if (serd_node_equals(klass, last_klass)) {
+ continue;
+ }
+
+ const bool defined = serd_model_ask(ctx->model, klass, NULL, NULL, NULL);
+
+ st = merge_status(st,
+ report_check(ctx,
+ i,
+ SERD_CHECK_CLASS_TYPE,
+ defined,
+ "Undefined class <%s>",
+ serd_node_string(klass)));
+
+ if (defined) {
+ st = merge_status(
+ st,
+ report_check(
+ ctx,
+ i,
+ SERD_CHECK_CLASS_TYPE,
+ serd_model_ask(ctx->model, klass, ctx->uris.rdf_type, NULL, NULL) &&
+ is_a(ctx, klass, ctx->uris.rdfs_Class),
+ "<%s> isn't a class",
+ serd_node_string(klass)));
}
+
+ last_klass = klass;
}
- serd_range_free(r);
+ serd_range_free(instances);
- // Check domain
- SerdRange* d = serd_model_range(ctx->model, pred, uris->rdfs_domain, 0, 0);
- SERD_FOREACH (s, d) {
- const SerdNode* domain = serd_statement_object(s);
- if (!has_explicit_type(ctx, subj, domain) &&
- !check_type(ctx, statement, subj, domain)) {
- VNOTEF(ctx,
- serd_range_front(d),
- "In domain of <%s>\n",
- serd_node_string(pred));
+ return st;
+}
+
+static SerdStatus
+check_datatype_property(SerdValidator* const ctx)
+{
+ const SerdModel* const model = ctx->model;
+ const URIs* uris = &ctx->uris;
+ SerdStatus st = SERD_SUCCESS;
+
+ // For each owl:DatatypeProperty
+ SerdRange* const properties = serd_model_range(
+ model, NULL, uris->rdf_type, uris->owl_DatatypeProperty, NULL);
+ SERD_FOREACH (p, properties) {
+ const SerdNode* const prop = serd_statement_subject(p);
+ const SerdNode* const prop_string = string_node(ctx, prop);
+
+ // For each statement of this property in the target graph
+ SerdRange* const statements =
+ serd_model_range(model, NULL, prop, NULL, ctx->graph);
+ SERD_FOREACH (s, statements) {
+ const SerdNode* const object = serd_statement_object(s);
+
+ // Check that the object is a literal
+ if ((st = report_check(ctx,
+ s,
+ SERD_CHECK_DATATYPE_PROPERTY,
+ serd_node_type(object) == SERD_LITERAL,
+ NODE_FMT " isn't a literal",
+ NODE_ARGS(serd_statement_object(s))))) {
+ log_note(ctx,
+ p,
+ SERD_CHECK_DATATYPE_PROPERTY,
+ "A " NODE_FMT " must be a literal",
+ NODE_ARGS(prop_string));
+ }
}
+ serd_range_free(statements);
}
- serd_range_free(d);
+ serd_range_free(properties);
return st;
}
-static int
-cardinality_error(ValidationContext* ctx,
- const SerdStatement* statement,
- const SerdStatement* restriction_statement,
- const SerdNode* property,
- const Count actual_values,
- const char* comparison,
- const Count expected_values)
-{
- const int st = VERRORF(ctx,
- statement,
- "Property <%s> has %lu %s %lu values\n",
- serd_node_string(property),
- actual_values,
- comparison,
- expected_values);
- VNOTE(ctx, restriction_statement, "Restriction here\n");
+static SerdStatus
+check_deprecated(SerdValidator* const ctx)
+{
+ const SerdModel* const model = ctx->model;
+ SerdStatus st = SERD_SUCCESS;
+
+ // For each deprecated thing
+ SerdRange* const things = serd_model_range(
+ model, NULL, ctx->uris.owl_deprecated, ctx->true_node, NULL);
+ SERD_FOREACH (t, things) {
+ const SerdNode* const thing = serd_statement_subject(t);
+ const SerdNode* const thing_string = string_node(ctx, thing);
+
+ if (is_a(ctx, thing, ctx->uris.rdf_Property)) {
+ // For each statement of this property in the target graph
+ SerdRange* const statements =
+ serd_model_range(model, NULL, thing, NULL, ctx->graph);
+ SERD_FOREACH (s, statements) {
+ st = report_check(ctx,
+ s,
+ SERD_CHECK_DEPRECATED_PROPERTY,
+ false,
+ "Use of deprecated property");
+ log_note(ctx,
+ t,
+ SERD_CHECK_DEPRECATED_PROPERTY,
+ "Property " NODE_FMT " is deprecated",
+ NODE_ARGS(thing_string));
+ }
+ serd_range_free(statements);
+
+ } else if (is_a(ctx, thing, ctx->uris.rdfs_Class)) {
+ // For each explicit instance of this class in the target graph
+ SerdRange* const statements =
+ serd_model_range(model, NULL, ctx->uris.rdf_type, thing, ctx->graph);
+ SERD_FOREACH (s, statements) {
+ st = report_check(ctx,
+ s,
+ SERD_CHECK_DEPRECATED_CLASS,
+ false,
+ "Instance of deprecated class");
+ log_note(ctx,
+ t,
+ SERD_CHECK_DEPRECATED_CLASS,
+ "Class " NODE_FMT " is deprecated",
+ NODE_ARGS(thing_string));
+ }
+ serd_range_free(statements);
+ }
+ }
+ serd_range_free(things);
+
return st;
}
-static int
-check_property_restriction(ValidationContext* ctx,
- const SerdNode* restriction,
- const SerdNode* prop,
- const SerdStatement* statement,
- const SerdNode* instance)
-{
- int st = 0;
- const Count values =
- (Count)serd_model_count(ctx->model, instance, prop, NULL, NULL);
+static SerdStatus
+check_functional_property(SerdValidator* const ctx)
+{
+ const SerdModel* const model = ctx->model;
+ const URIs* uris = &ctx->uris;
+ SerdStatus st = SERD_SUCCESS;
+
+ // For each owl:FunctionalProperty
+ SerdRange* const properties = serd_model_range(
+ model, NULL, uris->rdf_type, uris->owl_FunctionalProperty, NULL);
+ SERD_FOREACH (p, properties) {
+ const SerdNode* const prop = serd_statement_subject(p);
+ const SerdNode* const prop_string = string_node(ctx, prop);
+
+ const SerdNode* last_subj = NULL;
+
+ // For each instance with this property in the target graph
+ SerdRange* const statements =
+ serd_model_range(model, NULL, prop, NULL, ctx->graph);
+ SERD_FOREACH (s, statements) {
+ const SerdNode* const subj = serd_statement_subject(s);
+ if (serd_node_equals(subj, last_subj)) {
+ continue;
+ }
- // Check exact cardinality
- const SerdStatement* c = serd_model_get_statement(
- ctx->model, restriction, ctx->uris.owl_cardinality, NULL, NULL);
- if (c) {
- const SerdNode* card = serd_statement_object(c);
- const Count count = strtoul(serd_node_string(card), NULL, 10);
- if (check(ctx, values != count)) {
- st = cardinality_error(ctx, statement, c, prop, values, "!=", count);
+ // Count the number of values on this instance
+ SerdRange* const o =
+ serd_model_range(ctx->model, subj, prop, NULL, ctx->graph);
+ const Count n = count_non_blanks(o, SERD_OBJECT);
+
+ serd_range_free(o);
+ if (report_check(ctx,
+ s,
+ SERD_CHECK_FUNCTIONAL_PROPERTY,
+ n <= 1,
+ "Instance has %lu " NODE_FMT " properties",
+ n,
+ NODE_ARGS(prop_string))) {
+ st = SERD_ERR_INVALID;
+ log_note(ctx,
+ p,
+ SERD_CHECK_FUNCTIONAL_PROPERTY,
+ "An instance may have at most 1");
+ }
+
+ last_subj = subj;
}
+ serd_range_free(statements);
}
+ serd_range_free(properties);
- // Check minimum cardinality
- const SerdStatement* l = serd_model_get_statement(
- ctx->model, restriction, ctx->uris.owl_minCardinality, NULL, NULL);
- if (l) {
- const SerdNode* card = serd_statement_object(l);
- const Count count = strtoul(serd_node_string(card), NULL, 10);
- if (check(ctx, values < count)) {
- st = cardinality_error(ctx, statement, l, prop, values, "<", count);
+ return st;
+}
+
+// FIXME: name
+static SerdStatus
+check_instance(SerdValidator* const ctx)
+{
+ const SerdModel* const model = ctx->model;
+ const URIs* uris = &ctx->uris;
+ SerdStatus st = SERD_SUCCESS;
+
+ // For each rdf:type property in the target graph
+ SerdRange* const types =
+ serd_model_range(model, NULL, uris->rdf_type, NULL, ctx->graph);
+ SERD_FOREACH (t, types) {
+ const SerdNode* const instance = serd_statement_subject(t);
+ const SerdNode* const type = serd_statement_object(t);
+ const SerdNode* const type_string = string_node(ctx, type);
+
+ if ((st = check_instance_type(
+ ctx, SERD_CHECK_INSTANCE_TYPE, type, t, instance, type))) {
+ log_note(ctx,
+ t,
+ SERD_CHECK_INSTANCE_TYPE,
+ "Instance is a " NODE_FMT,
+ NODE_ARGS(type_string));
+ break;
}
}
+ serd_range_free(types);
- // Check maximum cardinality
- const SerdStatement* u = serd_model_get_statement(
- ctx->model, restriction, ctx->uris.owl_maxCardinality, NULL, NULL);
- if (u) {
- const SerdNode* card = serd_statement_object(u);
- const Count count = strtoul(serd_node_string(card), NULL, 10);
- if (check(ctx, values > count)) {
- st = cardinality_error(ctx, statement, u, prop, values, ">", count);
- }
- }
-
- // Check someValuesFrom
- const SerdStatement* s = serd_model_get_statement(
- ctx->model, restriction, ctx->uris.owl_someValuesFrom, 0, 0);
- if (s) {
- const SerdNode* some = serd_statement_object(s);
-
- ctx->quiet = true;
- SerdRange* v = serd_model_range(ctx->model, instance, prop, NULL, NULL);
- bool found = false;
- SERD_FOREACH (i, v) {
- const SerdNode* value = serd_statement_object(i);
- if (check_type(ctx, statement, value, some)) {
- found = true;
- break;
+ return st;
+}
+
+static SerdStatus
+check_inverse_functional_property(SerdValidator* const ctx)
+{
+ const SerdModel* const model = ctx->model;
+ const URIs* uris = &ctx->uris;
+ SerdStatus st = SERD_SUCCESS;
+
+ // For each owl:InverseFunctionalProperty
+ SerdRange* const properties = serd_model_range(
+ model, NULL, uris->rdf_type, uris->owl_InverseFunctionalProperty, NULL);
+ SERD_FOREACH (p, properties) {
+ const SerdNode* const prop = serd_statement_subject(p);
+ const SerdNode* const prop_string = string_node(ctx, prop);
+
+ const SerdNode* last_obj = NULL;
+
+ // For each value of this property in the target graph
+ SerdRange* const statements =
+ serd_model_range(model, NULL, prop, NULL, ctx->graph);
+ SERD_FOREACH (statement, statements) {
+ const SerdNode* const obj = serd_statement_object(statement);
+ const SerdNode* const obj_string = string_node(ctx, obj);
+ if (serd_node_equals(obj, last_obj)) {
+ continue;
}
+
+ // Count the number of subjects with this value in the target graph
+ SerdRange* s = serd_model_range(ctx->model, NULL, prop, obj, ctx->graph);
+ const Count n = count_non_blanks(s, SERD_SUBJECT);
+
+ if (n > 1) {
+ // Get the range again so we can print a note for every value
+ serd_range_free(s);
+ s = serd_model_range(ctx->model, NULL, prop, obj, ctx->graph);
+
+ SERD_FOREACH (value_statement, s) {
+ const SerdNode* const subj = serd_statement_subject(value_statement);
+ const SerdNode* const subj_string = string_node(ctx, subj);
+
+ report_check(ctx,
+ value_statement,
+ SERD_CHECK_INVERSE_FUNCTIONAL_PROPERTY,
+ false,
+ "Instance " NODE_FMT " shares the " NODE_FMT
+ " " NODE_FMT,
+ NODE_ARGS(subj_string),
+ NODE_ARGS(prop_string),
+ NODE_ARGS(obj_string));
+ }
+
+ log_note(ctx,
+ p,
+ SERD_CHECK_INVERSE_FUNCTIONAL_PROPERTY,
+ "At most 1 instance may have a given " NODE_FMT,
+ NODE_ARGS(prop_string));
+ }
+
+ serd_range_free(s);
+ last_obj = obj;
}
- ctx->quiet = false;
+ serd_range_free(statements);
+ }
+ serd_range_free(properties);
- if (check(ctx, !found)) {
- st = VERRORF(ctx,
- statement,
- "%s has no <%s> values of type <%s>\n",
- serd_node_string(instance),
- serd_node_string(prop),
- serd_node_string(some));
- VNOTE(ctx, s, "Restriction here\n");
- }
- serd_range_free(v);
- }
-
- // Check allValuesFrom
- const SerdStatement* a = serd_model_get_statement(
- ctx->model, restriction, ctx->uris.owl_allValuesFrom, 0, 0);
- if (a) {
- ++ctx->n_restrictions;
- const SerdNode* all = serd_statement_object(a);
-
- SerdRange* v = serd_model_range(ctx->model, instance, prop, NULL, NULL);
- SERD_FOREACH (i, v) {
- const SerdNode* value = serd_statement_object(i);
- if (!check_type(ctx, statement, value, all)) {
- st = VERRORF(ctx,
- i,
- "<%s> value not of type <%s>\n",
- serd_node_string(prop),
- serd_node_string(all));
- VNOTE(ctx, a, "Restriction here\n");
- break;
+ return st;
+}
+
+static SerdStatus
+check_object_property(SerdValidator* const ctx)
+{
+ const SerdModel* const model = ctx->model;
+ SerdStatus st = SERD_SUCCESS;
+
+ // For each owl:ObjectProperty
+ SerdRange* const properties = serd_model_range(
+ model, NULL, ctx->uris.rdf_type, ctx->uris.owl_ObjectProperty, NULL);
+ SERD_FOREACH_NODE (SERD_SUBJECT, prop, properties) {
+ const SerdNode* const prop_string = string_node(ctx, prop);
+
+ // For each statement of this property in the target graph
+ SerdRange* const statements =
+ serd_model_range(model, NULL, prop, NULL, ctx->graph);
+ SERD_FOREACH (s, statements) {
+ if (report_check(ctx,
+ s,
+ SERD_CHECK_OBJECT_PROPERTY,
+ serd_node_type(serd_statement_object(s)) != SERD_LITERAL,
+ "Object property has literal value")) {
+ st = SERD_ERR_INVALID;
+ log_note(ctx,
+ serd_range_front(properties),
+ SERD_CHECK_OBJECT_PROPERTY,
+ "A " NODE_FMT " must be an instance",
+ NODE_ARGS(prop_string));
}
}
- serd_range_free(v);
+ serd_range_free(statements);
}
+ serd_range_free(properties);
return st;
}
-static int
-check_class_restriction(ValidationContext* ctx,
- const SerdNode* restriction,
- const SerdStatement* statement,
- const SerdNode* instance)
+static SerdStatus
+check_property_domain(SerdValidator* const ctx)
{
- const SerdNode* prop = serd_model_get(
- ctx->model, restriction, ctx->uris.owl_onProperty, NULL, NULL);
+ const SerdModel* const model = ctx->model;
+ SerdStatus st = SERD_SUCCESS;
+
+ // For each property with an rdfs:domain
+ SerdRange* const properties =
+ serd_model_range(model, NULL, ctx->uris.rdfs_domain, NULL, NULL);
+ SERD_FOREACH (p, properties) {
+ const SerdNode* const prop = serd_statement_subject(p);
+ const SerdNode* const prop_string = string_node(ctx, prop);
+ const SerdNode* const domain = serd_statement_object(p);
+ const SerdNode* const domain_string = string_node(ctx, domain);
+
+ // For each statement of this property in the target graph
+ SerdRange* const statements =
+ serd_model_range(model, NULL, prop, NULL, ctx->graph);
+ SERD_FOREACH (statement, statements) {
+ const SerdNode* const subj = serd_statement_subject(statement);
+
+ // Check that the subject is in the domain
+ if (check_instance_type(
+ ctx, SERD_CHECK_PROPERTY_DOMAIN, domain, statement, subj, domain)) {
+ log_note(ctx,
+ p,
+ SERD_CHECK_PROPERTY_DOMAIN,
+ "An instance with a " NODE_FMT " must be a " NODE_FMT,
+ NODE_ARGS(prop_string),
+ NODE_ARGS(domain_string));
+ }
+ }
+ serd_range_free(statements);
+ }
+ serd_range_free(properties);
- return prop ? check_property_restriction(
- ctx, restriction, prop, statement, instance)
- : 0;
+ return st;
}
-static void
-init_uris(URIs* uris)
+static SerdStatus
+check_property_label(SerdValidator* const ctx)
{
-#define URI(prefix, suffix) \
- uris->prefix##_##suffix = \
- serd_new_uri(SERD_STATIC_STRING(NS_##prefix #suffix))
+ const SerdModel* const model = ctx->model;
+ SerdStatus st = SERD_SUCCESS;
+
+ // For each ?property a rdf:Property in the target graph
+ SerdRange* const properties = serd_model_range(
+ model, NULL, ctx->uris.rdf_type, ctx->uris.rdf_Property, ctx->graph);
+ SERD_FOREACH (p, properties) {
+ const SerdNode* const property = serd_statement_subject(p);
+
+ update_status(
+ &st,
+ report_check(ctx,
+ p,
+ SERD_CHECK_PROPERTY_LABEL,
+ serd_model_ask(
+ ctx->model, property, ctx->uris.rdfs_label, 0, ctx->graph),
+ "Property <%s> has no label",
+ serd_node_string(property)));
+ }
+ serd_range_free(properties);
+
+ return st;
+}
+
+static SerdStatus
+check_property_range(SerdValidator* const ctx)
+{
+ const SerdModel* const model = ctx->model;
+ SerdStatus st = SERD_SUCCESS;
+
+ // For each property with an rdfs:range
+ SerdRange* const properties =
+ serd_model_range(model, NULL, ctx->uris.rdfs_range, NULL, NULL);
+ SERD_FOREACH (p, properties) {
+ const SerdNode* const prop = serd_statement_subject(p);
+ const SerdNode* const klass = serd_statement_object(p);
+ const SerdNode* const prop_string = string_node(ctx, prop);
+
+ // For each statement of this property in the target graph
+ SerdRange* const statements =
+ serd_model_range(model, NULL, prop, NULL, ctx->graph);
+ SERD_FOREACH (statement, statements) {
+ const SerdNode* const obj = serd_statement_object(statement);
+
+ // Check that the object is in the range
+ if (check_type(ctx, SERD_CHECK_PROPERTY_RANGE, statement, obj, klass)) {
+ log_note(ctx,
+ p,
+ SERD_CHECK_PROPERTY_RANGE,
+ "Required for any " NODE_FMT " value",
+ NODE_ARGS(prop_string));
+ }
+ }
+ serd_range_free(statements);
+ }
+ serd_range_free(properties);
+
+ return st;
+}
+
+static SerdStatus
+check_predicate_type(SerdValidator* const ctx)
+{
+ // For each predicate
+ SerdStatus st = SERD_SUCCESS;
+ const SerdNode* last_pred = NULL;
+ // FIXME: graph
+ SerdRange* const all = serd_model_all(ctx->model, SERD_ORDER_POS);
+ SERD_FOREACH (s, all) {
+ const SerdNode* const pred = serd_statement_predicate(s);
+ if (serd_node_equals(pred, last_pred)) {
+ continue;
+ }
+
+ const bool defined = serd_model_ask(ctx->model, pred, NULL, NULL, NULL);
+
+ st = merge_status(st,
+ report_check(ctx,
+ s,
+ SERD_CHECK_PREDICATE_TYPE,
+ defined,
+ "Undefined property <%s>",
+ serd_node_string(pred)));
+
+ if (defined) {
+ st = merge_status(
+ st,
+ report_check(
+ ctx,
+ s,
+ SERD_CHECK_PREDICATE_TYPE,
+ serd_model_ask(ctx->model, pred, ctx->uris.rdf_type, NULL, NULL) &&
+ is_a(ctx, pred, ctx->uris.rdf_Property),
+ "<%s> isn't a property",
+ serd_node_string(pred)));
+ }
+
+ last_pred = pred;
+ }
+ serd_range_free(all);
+
+ return st;
+}
+
+static SerdStatus
+check_acyclic(SerdValidator* const ctx,
+ const SerdValidatorCheck check,
+ const SerdNode* const root,
+ const SerdNode* const node,
+ const SerdNode* const property,
+ const char* const fmt)
+{
+ SerdStatus st = SERD_SUCCESS;
+
+ // FIXME: graph
+ SerdRange* const links =
+ serd_model_range(ctx->model, node, property, NULL, NULL);
+ SERD_FOREACH (l, links) {
+ const SerdNode* const object = serd_statement_object(l);
+ const SerdNode* const object_string = string_node(ctx, object);
+
+ if ((st = report_check(ctx,
+ l,
+ check,
+ !serd_node_equals(object, root),
+ fmt,
+ NODE_ARGS(object_string)))) {
+ break;
+ }
+
+ if ((st = check_acyclic(ctx, check, root, object, property, fmt))) {
+ log_note(ctx, l, check, "Via " NODE_FMT, NODE_ARGS(object_string));
+ break;
+ }
+ }
+ serd_range_free(links);
+
+ return st;
+}
+
+static SerdStatus
+check_subclass_cycle(SerdValidator* const ctx)
+{
+ const SerdModel* const model = ctx->model;
+ SerdStatus st = SERD_SUCCESS;
+
+ // For each subclass
+ SerdRange* const properties =
+ serd_model_range(model, NULL, ctx->uris.rdfs_subClassOf, NULL, NULL);
+ SERD_FOREACH_NODE (SERD_SUBJECT, root, properties) {
+ st = merge_status(st,
+ check_acyclic(ctx,
+ SERD_CHECK_CLASS_CYCLE,
+ root,
+ root,
+ ctx->uris.rdfs_subClassOf,
+ "Class " NODE_FMT
+ " is a sub-class of itself"));
+ }
+ serd_range_free(properties);
+
+ return st;
+}
+
+static SerdStatus
+check_subproperty_cycle(SerdValidator* const ctx)
+{
+ const SerdModel* const model = ctx->model;
+ SerdStatus st = SERD_SUCCESS;
+
+ // For each subproperty relation
+ SerdRange* const properties =
+ serd_model_range(model, NULL, ctx->uris.rdfs_subPropertyOf, NULL, NULL);
+ SERD_FOREACH_NODE (SERD_SUBJECT, root, properties) {
+ st = merge_status(st,
+ check_acyclic(ctx,
+ SERD_CHECK_PROPERTY_CYCLE,
+ root,
+ root,
+ ctx->uris.rdfs_subPropertyOf,
+ "Property " NODE_FMT
+ " is a sub-property of itself"));
+ }
+ serd_range_free(properties);
+
+ return st;
+}
+
+/* Statement Checks */
+
+static SerdStatus
+statement_check_valid_literal(SerdValidator* const ctx,
+ const SerdStatement* const statement)
+{
+ const SerdNode* const object = serd_statement_object(statement);
+ if (serd_node_type(object) != SERD_LITERAL) {
+ return SERD_SUCCESS;
+ }
+
+ if (!literal_is_valid(ctx, statement, object, serd_node_datatype(object))) {
+ /* log_note(ctx, l, check, "Via " NODE_FMT, NODE_ARGS(object_string)); */
+
+ return SERD_ERR_INVALID;
+ }
+
+ return SERD_SUCCESS;
+}
+
+/* Entry Points */
+
+SerdValidator*
+serd_validator_new(SerdWorld* const world)
+{
+ SerdValidator* const validator =
+ (SerdValidator*)calloc(1, sizeof(SerdValidator));
+
+ if (!validator) {
+ return NULL;
+ }
+
+ SerdNodes* const nodes = world->nodes;
+
+ validator->world = world;
+ validator->true_node = serd_nodes_manage(nodes, serd_new_boolean(true));
+
+#define URI(prefix, suffix) \
+ validator->uris.prefix##_##suffix = serd_nodes_manage( \
+ nodes, serd_new_uri(SERD_STATIC_STRING(NS_##prefix #suffix)))
URI(owl, Class);
URI(owl, DatatypeProperty);
+ URI(owl, DeprecatedClass);
+ URI(owl, DeprecatedProperty);
URI(owl, FunctionalProperty);
URI(owl, InverseFunctionalProperty);
URI(owl, ObjectProperty);
@@ -803,6 +1693,7 @@ init_uris(URIs* uris)
URI(owl, Thing);
URI(owl, allValuesFrom);
URI(owl, cardinality);
+ URI(owl, deprecated);
URI(owl, equivalentClass);
URI(owl, maxCardinality);
URI(owl, minCardinality);
@@ -823,43 +1714,177 @@ init_uris(URIs* uris)
URI(rdfs, label);
URI(rdfs, range);
URI(rdfs, subClassOf);
+ URI(rdfs, subPropertyOf);
URI(xsd, anyURI);
- URI(xsd, float);
- URI(xsd, decimal);
- URI(xsd, double);
URI(xsd, maxExclusive);
URI(xsd, maxInclusive);
URI(xsd, minExclusive);
URI(xsd, minInclusive);
URI(xsd, pattern);
- URI(xsd, string);
+
+#undef URI
+
+ return validator;
+}
+
+void
+serd_validator_free(SerdValidator* const validator)
+{
+ free(validator);
}
SerdStatus
-serd_validate(const SerdModel* model)
+serd_validator_enable_checks(SerdValidator* const validator,
+ const char* const regex)
{
- ValidationContext ctx;
- memset(&ctx, 0, sizeof(ValidationContext));
- init_uris(&ctx.uris);
+ if (!strcmp(regex, "all")) {
+ return serd_validator_enable_checks(validator, ".*");
+ }
+
+ RerexPattern* const re = parse_regex(validator->world, NULL, regex);
+ if (!re) {
+ return SERD_ERR_BAD_ARG;
+ }
- ctx.model = model;
- ctx.n_errors = 0;
- ctx.n_restrictions = 0;
+ bool matched = false;
+ RerexMatcher* matcher = rerex_new_matcher(re);
- int st = 0;
- SerdRange* i = serd_model_all(ctx.model);
- SERD_FOREACH (statement, i) {
- st = check_statement(&ctx, statement) || st;
+ for (unsigned i = 0; i < N_CHECKS; ++i) {
+ if (rerex_match(matcher, check_names[i])) {
+ validator->checks |= (1u << i);
+ matched = true;
+ }
}
- serd_range_free(i);
- printf("Found %u errors (checked %u restrictions)\n",
- ctx.n_errors,
- ctx.n_restrictions);
+ rerex_free_matcher(matcher);
+ rerex_free_pattern(re);
- for (SerdNode** n = (SerdNode**)&ctx.uris; *n; ++n) {
- serd_node_free(*n);
+ return matched ? SERD_SUCCESS : SERD_FAILURE;
+}
+
+SerdStatus
+serd_validator_disable_checks(SerdValidator* const validator,
+ const char* const regex)
+{
+ RerexPattern* const re = parse_regex(validator->world, NULL, regex);
+ if (!re) {
+ return SERD_ERR_BAD_ARG;
+ }
+
+ bool matched = false;
+ RerexMatcher* matcher = rerex_new_matcher(re);
+
+ for (unsigned i = 0; i < N_CHECKS; ++i) {
+ if (rerex_match(matcher, check_names[i])) {
+ validator->checks &= ~(1u << i);
+ matched = true;
+ }
+ }
+
+ rerex_free_matcher(matcher);
+ rerex_free_pattern(re);
+
+ return matched ? SERD_SUCCESS : SERD_FAILURE;
+}
+
+SerdStatus
+serd_validate_model(SerdValidator* const validator,
+ const SerdModel* const model,
+ const SerdNode* const graph)
+{
+ SerdValidator* const ctx = validator;
+ SerdStatus st = SERD_SUCCESS;
+
+ ctx->model = model;
+ ctx->graph = graph;
+
+ if (ctx->checks & (1u << SERD_CHECK_PREDICATE_TYPE)) {
+ update_status(&st, check_predicate_type(ctx));
+ }
+
+ if (ctx->checks & (1u << SERD_CHECK_CLASS_CYCLE)) {
+ update_status(&st, check_subclass_cycle(ctx));
+ }
+
+ if (ctx->checks & (1u << SERD_CHECK_CLASS_LABEL)) {
+ update_status(&st, check_class_label(ctx));
+ }
+
+ if (ctx->checks & (1u << SERD_CHECK_CLASS_TYPE)) {
+ update_status(&st, check_class_type(ctx));
+ }
+
+ if (ctx->checks & (1u << SERD_CHECK_DATATYPE_PROPERTY)) {
+ update_status(&st, check_datatype_property(ctx));
+ }
+
+ if (ctx->checks & ((1u << SERD_CHECK_DEPRECATED_PROPERTY) |
+ (1u << SERD_CHECK_DEPRECATED_CLASS))) {
+ update_status(&st, check_deprecated(ctx));
+ }
+
+ if (ctx->checks & (1u << SERD_CHECK_FUNCTIONAL_PROPERTY)) {
+ update_status(&st, check_functional_property(ctx));
+ }
+
+ if (ctx->checks & (1u << SERD_CHECK_INSTANCE_TYPE)) {
+ update_status(&st, check_instance(ctx));
+ }
+
+ if (ctx->checks & (1u << SERD_CHECK_INVERSE_FUNCTIONAL_PROPERTY)) {
+ update_status(&st, check_inverse_functional_property(ctx));
+ }
+
+ if (ctx->checks & (1u << SERD_CHECK_OBJECT_PROPERTY)) {
+ update_status(&st, check_object_property(ctx));
+ }
+
+ if (ctx->checks & (1u << SERD_CHECK_PROPERTY_CYCLE)) {
+ update_status(&st, check_subproperty_cycle(ctx));
+ }
+
+ if (ctx->checks & (1u << SERD_CHECK_PROPERTY_DOMAIN)) {
+ update_status(&st, check_property_domain(ctx));
+ }
+
+ if (ctx->checks & (1u << SERD_CHECK_PROPERTY_LABEL)) {
+ update_status(&st, check_property_label(ctx));
+ }
+
+ if (ctx->checks & (1u << SERD_CHECK_PROPERTY_RANGE)) {
+ update_status(&st, check_property_range(ctx));
+ }
+
+ if (ctx->checks & ((1u << SERD_CHECK_DATATYPE_TYPE) | //
+ (1u << SERD_CHECK_LITERAL_INSTANCE) |
+ (1u << SERD_CHECK_LITERAL_MAX_EXCLUSIVE) |
+ (1u << SERD_CHECK_LITERAL_MAX_INCLUSIVE) |
+ (1u << SERD_CHECK_LITERAL_MIN_EXCLUSIVE) |
+ (1u << SERD_CHECK_LITERAL_MIN_INCLUSIVE) |
+ (1u << SERD_CHECK_LITERAL_PATTERN) |
+ (1u << SERD_CHECK_LITERAL_RESTRICTION) |
+ (1u << SERD_CHECK_LITERAL_VALUE))) {
+ SerdRange* const all = serd_model_all(ctx->model, SERD_ORDER_SPO);
+ SERD_FOREACH (statement, all) {
+ update_status(&st, statement_check_valid_literal(ctx, statement));
+ }
+ serd_range_free(all);
}
- return !st && ctx.n_errors == 0 ? SERD_SUCCESS : SERD_ERR_INVALID;
+ ctx->graph = NULL;
+
+ return (ctx->n_errors > 0)
+ ? serd_world_logf_internal(ctx->model->world,
+ SERD_ERR_INVALID,
+ SERD_LOG_LEVEL_ERR,
+ NULL,
+ "Failed %u of %u validation checks",
+ ctx->n_errors,
+ ctx->n_checks)
+ : serd_world_logf_internal(ctx->model->world,
+ SERD_SUCCESS,
+ SERD_LOG_LEVEL_INFO,
+ NULL,
+ "Passed all %u validation checks",
+ ctx->n_checks);
}
diff --git a/src/world.c b/src/world.c
index a6e32dc6..1ea688cb 100644
--- a/src/world.c
+++ b/src/world.c
@@ -85,7 +85,7 @@ serd_ansi_reset(FILE* stream)
#endif
}
-static const char* const log_level_strings[] = {"emergengy",
+static const char* const log_level_strings[] = {"emergency",
"alert",
"critical",
"error",
@@ -159,6 +159,14 @@ serd_world_vlogf(const SerdWorld* world,
// Using a copy isn't necessary here, but it avoids a clang-tidy bug
vfprintf(stderr, fmt, ap);
+
+ // Print clang-tidy-style check suffix
+ const char* const check = serd_log_entry_get_field(&e, "SERD_CHECK");
+ if (check) {
+ fprintf(stderr, " [%s]", check);
+ }
+
+ fprintf(stderr, "\n");
}
va_end(ap);
diff --git a/subprojects/exess/include/exess/exess.h b/subprojects/exess/include/exess/exess.h
index f327eb2e..13e9cf82 100644
--- a/subprojects/exess/include/exess/exess.h
+++ b/subprojects/exess/include/exess/exess.h
@@ -1415,6 +1415,11 @@ EXESS_PURE_API
const uint8_t* EXESS_NULLABLE
exess_get_ubyte(const ExessVariant* EXESS_NONNULL variant);
+/// Return a pointer to the value if `variant` is a date, otherwise null
+EXESS_PURE_API
+const ExessBlob* EXESS_NULLABLE
+exess_get_blob(const ExessVariant* EXESS_NONNULL variant);
+
/// Return a pointer to the value if `variant` is a duration, otherwise null
EXESS_PURE_API
const ExessDuration* EXESS_NULLABLE
@@ -1435,10 +1440,15 @@ EXESS_PURE_API
const ExessDate* EXESS_NULLABLE
exess_get_date(const ExessVariant* EXESS_NONNULL variant);
-/// Return a pointer to the value if `variant` is a date, otherwise null
+/**
+ @}
+ @defgroup exess_variant_coparison Comparison
+ @{
+*/
+
EXESS_PURE_API
-const ExessBlob* EXESS_NULLABLE
-exess_get_blob(const ExessVariant* EXESS_NONNULL variant);
+int
+exess_compare(ExessVariant lhs, ExessVariant rhs);
/**
@}
diff --git a/subprojects/exess/src/variant.c b/subprojects/exess/src/variant.c
index d8977abd..b32f27d6 100644
--- a/subprojects/exess/src/variant.c
+++ b/subprojects/exess/src/variant.c
@@ -275,6 +275,132 @@ exess_get_date(const ExessVariant* const variant)
return variant->datatype == EXESS_DATE ? &variant->value.as_date : NULL;
}
+// Comparison
+
+int
+exess_compare(const ExessVariant lhs, const ExessVariant rhs)
+{
+ if (lhs.datatype != rhs.datatype) {
+ return !lhs.datatype ? -1
+ : !rhs.datatype ? 1
+ : strcmp(exess_datatype_uri(lhs.datatype),
+ exess_datatype_uri(rhs.datatype));
+ }
+
+ switch (lhs.datatype) {
+ case EXESS_NOTHING:
+ return 0;
+
+ case EXESS_BOOLEAN:
+ return lhs.value.as_bool < rhs.value.as_bool ? -1
+ : lhs.value.as_bool > rhs.value.as_bool ? 1
+ : 0;
+
+ case EXESS_DECIMAL:
+ case EXESS_DOUBLE:
+ return lhs.value.as_double < rhs.value.as_double ? -1
+ : lhs.value.as_double > rhs.value.as_double ? 1
+ : 0;
+
+ case EXESS_FLOAT:
+ return lhs.value.as_float < rhs.value.as_float ? -1
+ : lhs.value.as_float > rhs.value.as_float ? 1
+ : 0;
+
+ case EXESS_INTEGER:
+ case EXESS_NON_POSITIVE_INTEGER:
+ case EXESS_NEGATIVE_INTEGER:
+ case EXESS_LONG:
+ return lhs.value.as_long < rhs.value.as_long ? -1
+ : lhs.value.as_long > rhs.value.as_long ? 1
+ : 0;
+
+ case EXESS_INT:
+ return lhs.value.as_int < rhs.value.as_int ? -1
+ : lhs.value.as_int > rhs.value.as_int ? 1
+ : 0;
+
+ case EXESS_SHORT:
+ return lhs.value.as_short < rhs.value.as_short ? -1
+ : lhs.value.as_short > rhs.value.as_short ? 1
+ : 0;
+
+ case EXESS_BYTE:
+ return lhs.value.as_byte < rhs.value.as_byte ? -1
+ : lhs.value.as_byte > rhs.value.as_byte ? 1
+ : 0;
+
+ case EXESS_NON_NEGATIVE_INTEGER:
+ case EXESS_ULONG:
+ return lhs.value.as_ulong < rhs.value.as_ulong ? -1
+ : lhs.value.as_ulong > rhs.value.as_ulong ? 1
+ : 0;
+
+ case EXESS_UINT:
+ return lhs.value.as_uint < rhs.value.as_uint ? -1
+ : lhs.value.as_uint > rhs.value.as_uint ? 1
+ : 0;
+
+ case EXESS_USHORT:
+ return lhs.value.as_ushort < rhs.value.as_ushort ? -1
+ : lhs.value.as_ushort > rhs.value.as_ushort ? 1
+ : 0;
+
+ case EXESS_UBYTE:
+ return lhs.value.as_ubyte < rhs.value.as_ubyte ? -1
+ : lhs.value.as_ubyte > rhs.value.as_ubyte ? 1
+ : 0;
+
+ case EXESS_POSITIVE_INTEGER:
+ return lhs.value.as_ulong < rhs.value.as_ulong ? -1
+ : lhs.value.as_ulong > rhs.value.as_ulong ? 1
+ : 0;
+
+ case EXESS_DURATION:
+ return lhs.value.as_duration.months < rhs.value.as_duration.months ? -1
+ : lhs.value.as_duration.months > rhs.value.as_duration.months ? 1
+ : lhs.value.as_duration.seconds < rhs.value.as_duration.seconds ? -1
+ : lhs.value.as_duration.seconds > rhs.value.as_duration.seconds ? 1
+ : lhs.value.as_duration.nanoseconds <
+ rhs.value.as_duration.nanoseconds
+ ? -1
+ : lhs.value.as_duration.nanoseconds >
+ rhs.value.as_duration.nanoseconds
+ ? 1
+ : 0;
+
+ case EXESS_DATETIME:
+
+ case EXESS_TIME:
+ case EXESS_DATE:
+ case EXESS_HEX:
+ case EXESS_BASE64:
+ break;
+ }
+
+ /* return exess_make_nothing(EXESS_UNSUPPORTED); */
+
+ /* if (lhs.datatype == EXESS_BOOL) { */
+ /* return lhs.value.as_long < rhs.value.as_long ? -1 */
+ /* : lhs.value.as_long > rhs.value.as_long ? 1 */
+ /* : 0; */
+ /* } */
+
+ /* if (lhs.datatype == EXESS_LONG) { */
+ /* return lhs.value.as_long < rhs.value.as_long ? -1 */
+ /* : lhs.value.as_long > rhs.value.as_long ? 1 */
+ /* : 0; */
+ /* } */
+
+ /* if (lhs.datatype == EXESS_ULONG) { */
+ /* return lhs.value.as_long < rhs.value.as_long ? -1 */
+ /* : lhs.value.as_long > rhs.value.as_long ? 1 */
+ /* : 0; */
+ /* } */
+
+ return 0;
+}
+
// Reading and Writing
ExessResult
diff --git a/test/meson.build b/test/meson.build
index 501f8b13..1b36e695 100644
--- a/test/meson.build
+++ b/test/meson.build
@@ -127,7 +127,7 @@ if get_option('utils')
test('empty', files('test_empty.py'),
args: script_args + [serd_ttl],
suite: 'output')
-
+
# FIXME: Old base URI argument?
# IO errors
diff --git a/test/run_validation_test_suite.py b/test/run_validation_test_suite.py
index a27e55de..34a213f0 100755
--- a/test/run_validation_test_suite.py
+++ b/test/run_validation_test_suite.py
@@ -2,12 +2,7 @@
"""Run the serd RDF validation test suite."""
-import serd_test_util
-
import argparse
-import datetime
-import difflib
-import itertools
import os
import re
import shlex
@@ -16,11 +11,24 @@ import sys
import tempfile
import urllib.parse
+import serd_test_util
+
+NS_CHECKS = "http://drobilla.net/ns/serd/checks#"
+NS_MF = "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#"
+NS_SERD = "http://drobilla.net/ns/serd#"
+
+
+def log_error(message):
+ """Log an error message to stderr"""
+
+ sys.stderr.write("error: ")
+ sys.stderr.write(message)
+
def _uri_path(uri):
path = urllib.parse.urlparse(uri).path
drive = os.path.splitdrive(path[1:])[0]
- return path if not drive else path[1:]
+ return os.path.realpath(path) if not drive else path[1:]
def _load_rdf(filename, base_uri, command_prefix):
@@ -54,57 +62,55 @@ def _load_rdf(filename, base_uri, command_prefix):
return model, instances
-def _option_combinations(options):
- """Return an iterator that cycles through all combinations of options."""
-
- combinations = []
- for count in range(len(options) + 1):
- combinations += list(itertools.combinations(options, count))
-
- return itertools.cycle(combinations)
+def _run_positive_test(command, out_filename):
+ command_string = " ".join([shlex.quote(c) for c in command])
+ with open(out_filename, "w") as stdout:
+ proc = subprocess.run(command, check=False, stdout=stdout)
+ if proc.returncode != 0:
+ log_error("Unexpected command failure failure\n")
+ sys.stderr.write(command_string + "\n")
+ return 1
-def _show_diff(from_lines, to_lines, from_filename, to_filename):
- same = True
- for line in difflib.unified_diff(
- from_lines,
- to_lines,
- fromfile=os.path.abspath(from_filename),
- tofile=os.path.abspath(to_filename),
- ):
- sys.stderr.write(line)
- same = False
+ return proc.returncode
- return same
+ return 1
-def _file_equals(patha, pathb):
+def _run_negative_test(command, check_name, out_filename):
+ command_string = " ".join([shlex.quote(c) for c in command])
- for path in (patha, pathb):
- if not os.access(path, os.F_OK):
- sys.stderr.write("error: missing file {}\n".format(path))
- return False
-
- with open(patha, "r", encoding="utf-8") as fa:
- with open(pathb, "r", encoding="utf-8") as fb:
- return _show_diff(fa.readlines(), fb.readlines(), patha, pathb)
+ with open(out_filename, "w") as stdout:
+ with tempfile.TemporaryFile() as stderr:
+ proc = subprocess.run(
+ command, check=False, stdout=stdout, stderr=stderr
+ )
+ # Check that serdi returned with status SERD_ERR_INVALID
+ if proc.returncode != 16:
+ log_error("Unexpected status {}\n".format(proc.returncode))
+ sys.stderr.write(command_string + "\n")
+ return 1
-def _file_lines_equal(patha, pathb, subst_from="", subst_to=""):
- import io
+ # Check that an error message was printed
+ stderr.seek(0, 2) # Seek to end
+ if stderr.tell() == 0: # Empty
+ log_error("No error message printed\n")
+ sys.stderr.write(command_string + "\n")
+ return 1
- for path in (patha, pathb):
- if not os.access(path, os.F_OK):
- sys.stderr.write("error: missing file %s\n" % path)
- return False
+ # Check that the expected check printed an error message
+ stderr.seek(0) # Seek to start
+ err_output = stderr.read().decode("utf-8")
+ if check_name and "[{}]".format(check_name) not in err_output:
+ log_error("Test didn't trigger {}\n".format(check_name))
+ sys.stderr.write(command_string + "\n")
+ sys.stderr.write(err_output + "\n")
+ return 1
- la = sorted(set(io.open(patha, encoding="utf-8").readlines()))
- lb = sorted(set(io.open(pathb, encoding="utf-8").readlines()))
- if la != lb:
- _show_diff(la, lb, patha, pathb)
- return False
+ return 0
- return True
+ return 1
def validation_test_suite(
@@ -112,12 +118,10 @@ def validation_test_suite(
schemas,
base_uri,
report_filename,
- isyntax,
command_prefix,
):
"""Run all tests in a test suite manifest."""
- mf = "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#"
test_dir = os.path.dirname(manifest_path)
model, instances = serd_test_util.load_rdf(
manifest_path, base_uri, command_prefix
@@ -133,71 +137,53 @@ def validation_test_suite(
asserter = "http://drobilla.net/drobilla#me"
class Results:
+ """Aggregated count of all tests and results."""
def __init__(self):
self.n_tests = 0
self.n_failures = 0
- def run_tests(test_class, tests, expected_return, results):
+ def run_tests(tests, expected_return, results):
for test in sorted(tests):
- test_uri = model[test][mf + "action"][0]
+ test_uri = model[test][NS_MF + "action"][0]
test_uri_path = _uri_path(test_uri)
test_name = os.path.basename(test_uri_path)
test_path = os.path.join(test_dir, test_name)
-
- command = (
- command_prefix
- + [
- "-V",
- "-I",
- test_uri,
- test_path,
- ]
- + schemas
- )
out_filename = os.path.join(out_test_dir, test_name + ".out")
results.n_tests += 1
if expected_return == 0: # Positive test
+ options = ["-V", "all", "-I", test_uri]
+ command = command_prefix + options + schemas + [test_path]
- with open(out_filename, "w") as stdout:
- proc = subprocess.run(command, check=False, stdout=stdout)
- if proc.returncode == 0:
- passed = True
- else:
- results.n_failures += 1
- sys.stderr.write(
- "error: Unexpected failure of command: {}\n".format(
- " ".join(shlex.quote(c) for c in command)
- )
- )
+ status = _run_positive_test(command, out_filename)
+ passed = status == 0
+ results.n_failures += status
else: # Negative test
- with open(out_filename, "w") as stdout:
- with tempfile.TemporaryFile() as stderr:
- proc = subprocess.run(
- command, check=False, stdout=stdout, stderr=stderr
- )
-
- if proc.returncode != 0:
- passed = True
- else:
- results.n_failures += 1
- sys.stderr.write(
- "error: Unexpected success of command: {}\n".format(
- " ".join(shlex.quote(c) for c in command)
- )
- )
-
- # Check that an error message was printed
- stderr.seek(0, 2) # Seek to end
- if stderr.tell() == 0: # Empty
- sys.stderr.write(
- "error: No error message printed by command: {}\n".format(
- " ".join(shlex.quote(c) for c in command)
- )
- )
- result = 1
+ if NS_SERD + "triggersCheck" not in model[test]:
+ log_error("{} has no serd:triggersCheck".format(test_name))
+
+ check_names = []
+ if NS_SERD + "triggersCheck" in model[test]:
+ for check in model[test][NS_SERD + "triggersCheck"]:
+ check_names += [check[len(NS_CHECKS) :]]
+
+ # FIXME: doesn't work
+ # options = ["-I", test_uri]
+ options = []
+ for check_name in check_names:
+ options += ["-V", check_name]
+
+ options += ["-I", test_uri]
+
+ # options = ["-V", "instanceType", "-V", "propertyRange", "-V", "propertyDomain", "-V", check_name, "-I", test_uri]
+ # options = ["-V", "all", "-I", test_uri]
+ command = command_prefix + options + schemas + [test_path]
+
+ status = _run_negative_test(command, check_name, out_filename)
+ passed = status == 0
+ results.n_failures += status
# Write test report entry
if report_filename:
@@ -212,14 +198,12 @@ def validation_test_suite(
for test_class, instances in instances.items():
if test_class.startswith(ns_serd):
expected = 1 if "Negative" in test_class else 0
- run_tests(test_class, instances, expected, results)
+ run_tests(instances, expected, results)
# Print result summary
if results.n_failures > 0:
- sys.stderr.write(
- "error: {}/{} tests failed\n".format(
- results.n_failures, results.n_tests
- )
+ log_error(
+ "{}/{} tests failed\n".format(results.n_failures, results.n_tests)
)
else:
sys.stdout.write("All {} tests passed\n".format(results.n_tests))
@@ -238,7 +222,6 @@ def main():
parser.add_argument("--report", help="path to write result report to")
parser.add_argument("--serdi", default="serdi", help="path to serdi")
- parser.add_argument("--syntax", default="turtle", help="input syntax")
parser.add_argument("--wrapper", default="", help="executable wrapper")
parser.add_argument("manifest", help="test suite manifest.ttl file")
parser.add_argument("base_uri", help="base URI for tests")
@@ -252,7 +235,6 @@ def main():
args.schema,
args.base_uri,
args.report,
- args.syntax,
command_prefix,
)
@@ -260,9 +242,9 @@ def main():
if __name__ == "__main__":
try:
sys.exit(main())
- except subprocess.CalledProcessError as e:
- if e.stderr is not None:
- sys.stderr.write(e.stderr.decode("utf-8"))
+ except subprocess.CalledProcessError as error:
+ if error.stderr is not None:
+ sys.stderr.write(error.stderr.decode("utf-8"))
- sys.stderr.write("error: %s\n" % e)
- sys.exit(e.returncode)
+ log_error(str(error) + "\n")
+ sys.exit(error.returncode)
diff --git a/test/test_model.c b/test/test_model.c
index cd1728e8..dce0f0b8 100644
--- a/test/test_model.c
+++ b/test/test_model.c
@@ -359,7 +359,7 @@ test_all_begin(SerdWorld* world, const unsigned n_quads)
(void)n_quads;
SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);
- SerdRange* all = serd_model_all(model);
+ SerdRange* all = serd_model_all(model, SERD_ORDER_SPO);
SerdIter* begin = serd_model_find(model, NULL, NULL, NULL, NULL);
assert(serd_iter_equals(serd_range_begin(all), begin));
assert(serd_iter_equals(serd_range_cbegin(all), begin));
@@ -569,8 +569,8 @@ test_range(SerdWorld* world, const unsigned n_quads)
SerdModel* model = serd_model_new(world, SERD_INDEX_SPO);
generate(world, model, n_quads, NULL);
- SerdRange* range1 = serd_model_all(model);
- SerdRange* range2 = serd_model_all(model);
+ SerdRange* range1 = serd_model_all(model, SERD_ORDER_SPO);
+ SerdRange* range2 = serd_model_all(model, SERD_ORDER_SPO);
assert(!serd_range_empty(range1));
assert(serd_range_empty(NULL));
@@ -757,7 +757,7 @@ test_write_bad_list(SerdWorld* world, const unsigned n_quads)
SerdByteSink* out = serd_byte_sink_new_buffer(&buffer);
SerdWriter* writer = serd_writer_new(world, SERD_TURTLE, 0, env, out);
- SerdRange* all = serd_model_all(model);
+ SerdRange* all = serd_model_all(model, SERD_ORDER_SPO);
serd_range_serialise(all, serd_writer_sink(writer), 0);
serd_range_free(all);
@@ -840,7 +840,7 @@ test_write_error_in_list(SerdWorld* world, const unsigned n_quads)
SerdWriter* writer = serd_writer_new(world, SERD_TURTLE, 0, env, out);
const SerdSink* const sink = serd_writer_sink(writer);
- SerdRange* const all = serd_model_all(model);
+ SerdRange* const all = serd_model_all(model, SERD_ORDER_SPO);
const SerdStatus st = serd_range_serialise(all, sink, 0);
serd_range_free(all);
diff --git a/test/test_quiet.py b/test/test_quiet.py
index e27cb9ea..4d6dfaa5 100755
--- a/test/test_quiet.py
+++ b/test/test_quiet.py
@@ -17,5 +17,7 @@ args = parser.parse_args(sys.argv[1:])
command = shlex.split(args.wrapper) + [args.serdi, "-q", args.input]
proc = subprocess.run(command, check=False, capture_output=True)
+print(' '.join([shlex.quote(c) for c in command]))
assert proc.returncode != 0
+print(proc.stderr)
assert len(proc.stderr) == 0
diff --git a/test/validate/bad-all-values-from.ttl b/test/validate/bad-all-values-from.ttl
index e8243423..4d82bf3d 100644
--- a/test/validate/bad-all-values-from.ttl
+++ b/test/validate/bad-all-values-from.ttl
@@ -2,17 +2,23 @@
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:index
+ a rdf:Property ;
+ rdfs:label "index" .
eg:Thing
a rdfs:Class ;
+ rdfs:label "Thing" ;
rdfs:subClassOf [
a owl:Restriction ;
- owl:onProperty rdfs:label ;
- owl:allValuesFrom rdf:PlainLiteral
+ owl:onProperty eg:index ;
+ owl:allValuesFrom xsd:nonNegativeInteger
] .
eg:s
a eg:Thing ;
- rdfs:label "plain" ,
- "not plain"^^rdf:XMLLiteral .
+ eg:index 1.2 ,
+ 3 .
diff --git a/test/validate/bad-anyuri.ttl b/test/validate/bad-anyuri.ttl
new file mode 100644
index 00000000..ae5e88f0
--- /dev/null
+++ b/test/validate/bad-anyuri.ttl
@@ -0,0 +1,13 @@
+@prefix eg: <http://example.org/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:uri
+ a rdf:Property ;
+ rdfs:label "uri" ;
+ rdfs:range xsd:anyURI .
+
+eg:s
+ eg:uri _:blank .
+
diff --git a/test/validate/bad-cardinality-high.ttl b/test/validate/bad-cardinality-high.ttl
index 7e1605c3..2ff8ede3 100644
--- a/test/validate/bad-cardinality-high.ttl
+++ b/test/validate/bad-cardinality-high.ttl
@@ -5,6 +5,7 @@
eg:Thing
a rdfs:Class ;
+ rdfs:label "Thing" ;
rdfs:subClassOf [
a owl:Restriction ;
owl:onProperty rdf:value ;
diff --git a/test/validate/bad-cardinality-low.ttl b/test/validate/bad-cardinality-low.ttl
index 93dd0051..60bfc9f8 100644
--- a/test/validate/bad-cardinality-low.ttl
+++ b/test/validate/bad-cardinality-low.ttl
@@ -5,6 +5,7 @@
eg:Thing
a rdfs:Class ;
+ rdfs:label "Thing" ;
rdfs:subClassOf [
a owl:Restriction ;
owl:onProperty rdf:value ;
diff --git a/test/validate/bad-cardinality.ttl b/test/validate/bad-cardinality.ttl
index 481fe456..5300e566 100644
--- a/test/validate/bad-cardinality.ttl
+++ b/test/validate/bad-cardinality.ttl
@@ -5,6 +5,7 @@
eg:Thing
a rdfs:Class ;
+ rdfs:label "Thing" ;
rdfs:subClassOf [
a owl:Restriction ;
owl:onProperty rdf:value ;
diff --git a/test/validate/bad-class-type-undefined.ttl b/test/validate/bad-class-type-undefined.ttl
new file mode 100644
index 00000000..1e3c5eba
--- /dev/null
+++ b/test/validate/bad-class-type-undefined.ttl
@@ -0,0 +1,5 @@
+@prefix eg: <http://example.org/> .
+
+eg:s
+ a eg:Undefined .
+
diff --git a/test/validate/bad-class-type.ttl b/test/validate/bad-class-type.ttl
new file mode 100644
index 00000000..a0ddf454
--- /dev/null
+++ b/test/validate/bad-class-type.ttl
@@ -0,0 +1,9 @@
+@prefix eg: <http://example.org/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+
+eg:nonClass
+ a rdf:Bag .
+
+eg:s
+ a eg:nonClass .
+
diff --git a/test/validate/bad-datatype-property.ttl b/test/validate/bad-datatype-property.ttl
index a3e993f3..3c2f7a9f 100644
--- a/test/validate/bad-datatype-property.ttl
+++ b/test/validate/bad-datatype-property.ttl
@@ -3,16 +3,17 @@
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
-eg:value
- rdfs:label "value" ;
- a owl:DatatypeProperty .
+eg:name
+ a owl:DatatypeProperty ;
+ rdfs:label "name" .
eg:Thing
- a rdfs:Class .
+ a rdfs:Class ;
+ rdfs:label "Thing" .
eg:s1
a eg:Thing .
eg:s2
- eg:value eg:s1 .
+ eg:name eg:s1 .
diff --git a/test/validate/bad-deprecated-class.ttl b/test/validate/bad-deprecated-class.ttl
new file mode 100644
index 00000000..51d76d8f
--- /dev/null
+++ b/test/validate/bad-deprecated-class.ttl
@@ -0,0 +1,12 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:Square
+ a rdfs:Class ;
+ owl:deprecated true ;
+ rdfs:label "Square" .
+
+eg:square
+ a eg:Square .
+
diff --git a/test/validate/bad-deprecated-property.ttl b/test/validate/bad-deprecated-property.ttl
new file mode 100644
index 00000000..b2bd392c
--- /dev/null
+++ b/test/validate/bad-deprecated-property.ttl
@@ -0,0 +1,13 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:stuff
+ a rdf:Property ;
+ owl:deprecated true ;
+ rdfs:label "stuff" .
+
+eg:s
+ eg:stuff eg:o .
+
diff --git a/test/validate/bad-domain.ttl b/test/validate/bad-domain.ttl
index d36b5652..52c1849c 100644
--- a/test/validate/bad-domain.ttl
+++ b/test/validate/bad-domain.ttl
@@ -1,19 +1,27 @@
@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
eg:Thing
- a rdfs:Class .
+ a rdfs:Class ;
+ rdfs:subClassOf [
+ a owl:Restriction ;
+ owl:onProperty rdf:value ;
+ owl:cardinality 1
+ ] ;
+ rdfs:label "Thing" .
eg:NonThing
- a rdfs:Class .
+ a rdfs:Class ;
+ rdfs:label "NonThing" .
-eg:value
+eg:thingName
a rdf:Property ;
- rdfs:label "value" ;
+ rdfs:label "thing name" ;
rdfs:domain eg:Thing .
eg:nonthing
a eg:NonThing ;
- eg:value 42 .
+ eg:thingName "nonthing" .
diff --git a/test/validate/bad-literal-pattern.ttl b/test/validate/bad-literal-pattern.ttl
index 40f9eec0..fda954ed 100644
--- a/test/validate/bad-literal-pattern.ttl
+++ b/test/validate/bad-literal-pattern.ttl
@@ -1,7 +1,22 @@
@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
-eg:s
- rdf:value "no"^^xsd:boolean .
+eg:CapitalLiteral
+ a rdfs:Datatype ;
+ rdfs:label "Capital Literal" ;
+ owl:withRestrictions (
+ [
+ xsd:pattern "[A-Z][a-z]*"
+ ]
+ ) .
+
+eg:value
+ a rdf:Property ;
+ rdfs:label "value" ;
+ rdfs:range eg:CapitalLiteral .
+eg:s
+ eg:value "lowercase"^^eg:CapitalLiteral .
diff --git a/test/validate/bad-literal-value-high-exclusive.ttl b/test/validate/bad-literal-value-high-exclusive.ttl
index f83d2216..fafc74c8 100644
--- a/test/validate/bad-literal-value-high-exclusive.ttl
+++ b/test/validate/bad-literal-value-high-exclusive.ttl
@@ -6,7 +6,7 @@
eg:Normal
a rdfs:Datatype ;
- rdfs:label "normal" ;
+ rdfs:label "Normal" ;
owl:onDatatype xsd:double ;
owl:withRestrictions (
[
diff --git a/test/validate/bad-literal-value-high-inclusive.ttl b/test/validate/bad-literal-value-high-inclusive.ttl
index c0753250..db19f1cd 100644
--- a/test/validate/bad-literal-value-high-inclusive.ttl
+++ b/test/validate/bad-literal-value-high-inclusive.ttl
@@ -6,7 +6,7 @@
eg:Normal
a rdfs:Datatype ;
- rdfs:label "normal" ;
+ rdfs:label "Normal" ;
owl:onDatatype xsd:double ;
owl:withRestrictions (
[
diff --git a/test/validate/bad-literal-value-low-exclusive.ttl b/test/validate/bad-literal-value-low-exclusive.ttl
index 09ca9f93..082ae9d9 100644
--- a/test/validate/bad-literal-value-low-exclusive.ttl
+++ b/test/validate/bad-literal-value-low-exclusive.ttl
@@ -6,7 +6,7 @@
eg:Normal
a rdfs:Datatype ;
- rdfs:label "normal" ;
+ rdfs:label "Normal" ;
owl:onDatatype xsd:double ;
owl:withRestrictions (
[
diff --git a/test/validate/bad-literal-value-low-inclusive.ttl b/test/validate/bad-literal-value-low-inclusive.ttl
index 6ae5758b..c7203c81 100644
--- a/test/validate/bad-literal-value-low-inclusive.ttl
+++ b/test/validate/bad-literal-value-low-inclusive.ttl
@@ -6,7 +6,7 @@
eg:Normal
a rdfs:Datatype ;
- rdfs:label "normal" ;
+ rdfs:label "Normal" ;
owl:onDatatype xsd:double ;
owl:withRestrictions (
[
diff --git a/test/validate/bad-literal-value.ttl b/test/validate/bad-literal-value.ttl
new file mode 100644
index 00000000..4c33175f
--- /dev/null
+++ b/test/validate/bad-literal-value.ttl
@@ -0,0 +1,7 @@
+@prefix eg: <http://example.org/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:s
+ rdf:value "one"^^xsd:integer .
+
diff --git a/test/validate/bad-object-property.ttl b/test/validate/bad-object-property.ttl
index b4a31f9d..335db339 100644
--- a/test/validate/bad-object-property.ttl
+++ b/test/validate/bad-object-property.ttl
@@ -3,10 +3,10 @@
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
-eg:value
- rdfs:label "value" ;
+eg:child
+ rdfs:label "child" ;
a owl:ObjectProperty .
eg:s
- eg:value "literal" .
+ eg:child "literal" .
diff --git a/test/validate/bad-pattern.ttl b/test/validate/bad-pattern.ttl
index 1b764c78..fef79aeb 100644
--- a/test/validate/bad-pattern.ttl
+++ b/test/validate/bad-pattern.ttl
@@ -6,18 +6,18 @@
eg:BrokenLiteral
a rdfs:Datatype ;
- rdfs:label "broken literal" ;
+ rdfs:label "Broken Literal" ;
owl:withRestrictions (
[
xsd:pattern "["
]
) .
-eg:value
- a rdf:Property ;
- rdfs:label "value" ;
- rdfs:range eg:BinaryLiteral .
+# eg:value
+# a rdf:Property ;
+# rdfs:label "value" ;
+# rdfs:range eg:BrokenLiteral .
eg:s
- eg:value "no match"^^eg:BrokenLiteral .
+ rdf:value "no match"^^eg:BrokenLiteral .
diff --git a/test/validate/bad-plain-literal.ttl b/test/validate/bad-plain-literal.ttl
index 116faac0..c6f60c2c 100644
--- a/test/validate/bad-plain-literal.ttl
+++ b/test/validate/bad-plain-literal.ttl
@@ -8,5 +8,5 @@ eg:value
rdfs:range rdf:PlainLiteral .
eg:s
- eg:value "literal"^^rdf:XMLLiteral .
+ eg:value "typed"^^rdf:XMLLiteral .
diff --git a/test/validate/bad-unknown-property.ttl b/test/validate/bad-predicate-type-undefined.ttl
index 0db1e85c..246594ee 100644
--- a/test/validate/bad-unknown-property.ttl
+++ b/test/validate/bad-predicate-type-undefined.ttl
@@ -1,5 +1,4 @@
@prefix eg: <http://example.org/> .
-@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
eg:s
eg:undefined 0 .
diff --git a/test/validate/bad-predicate-type.ttl b/test/validate/bad-predicate-type.ttl
new file mode 100644
index 00000000..84163d64
--- /dev/null
+++ b/test/validate/bad-predicate-type.ttl
@@ -0,0 +1,9 @@
+@prefix eg: <http://example.org/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+
+eg:nonProperty
+ a rdf:Bag .
+
+eg:s
+ eg:nonProperty 0 .
+
diff --git a/test/validate/bad-range-instance-not-literal.ttl b/test/validate/bad-range-instance-not-literal.ttl
index ea7803f6..5132a70f 100644
--- a/test/validate/bad-range-instance-not-literal.ttl
+++ b/test/validate/bad-range-instance-not-literal.ttl
@@ -3,7 +3,8 @@
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
eg:Thing
- a rdfs:Class .
+ a rdfs:Class ;
+ rdfs:label "Thing" .
eg:value
a rdf:Property ;
diff --git a/test/validate/bad-range-instance.ttl b/test/validate/bad-range-instance.ttl
index a04a5476..74403d49 100644
--- a/test/validate/bad-range-instance.ttl
+++ b/test/validate/bad-range-instance.ttl
@@ -3,10 +3,12 @@
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
eg:Thing
- a rdfs:Class .
+ a rdfs:Class ;
+ rdfs:label "Thing" .
eg:NonThing
- a rdfs:Class .
+ a rdfs:Class ;
+ rdfs:label "NonThing" .
eg:value
a rdf:Property ;
diff --git a/test/validate/bad-range-literal-not-instance.ttl b/test/validate/bad-range-literal-not-instance.ttl
index f46de8ce..039fcea8 100644
--- a/test/validate/bad-range-literal-not-instance.ttl
+++ b/test/validate/bad-range-literal-not-instance.ttl
@@ -3,7 +3,8 @@
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
eg:Thing
- a rdfs:Class .
+ a rdfs:Class ;
+ rdfs:label "Thing" .
eg:value
a rdf:Property ;
diff --git a/test/validate/bad-range-literal.ttl b/test/validate/bad-range-literal.ttl
index 10750391..d656aa16 100644
--- a/test/validate/bad-range-literal.ttl
+++ b/test/validate/bad-range-literal.ttl
@@ -6,22 +6,22 @@
eg:Normal
a rdfs:Datatype ;
- rdfs:label "normal" ;
+ rdfs:label "Normal" ;
owl:onDatatype xsd:double ;
owl:withRestrictions (
[
- xsd:maxExclusive 1.0
+ xsd:maxInclusive 1.0
] [
- xsd:minExclusive 0.0
+ xsd:minInclusive 0.0
]
) .
-eg:value
+eg:scaled
a rdf:Property ;
- rdfs:label "value" ;
+ rdfs:label "scaled" ;
rdfs:range eg:Normal .
eg:s
- eg:value 2.0 .
+ eg:scaled 2.0 .
diff --git a/test/validate/bad-some-values-from.ttl b/test/validate/bad-some-values-from.ttl
index 259bfb88..9a8ee849 100644
--- a/test/validate/bad-some-values-from.ttl
+++ b/test/validate/bad-some-values-from.ttl
@@ -5,6 +5,7 @@
eg:Thing
a rdfs:Class ;
+ rdfs:label "Thing" ;
rdfs:subClassOf [
a owl:Restriction ;
owl:onProperty rdfs:label ;
diff --git a/test/validate/bad-string-literal-value-high.ttl b/test/validate/bad-string-literal-value-high.ttl
index 6622c35b..93e675ef 100644
--- a/test/validate/bad-string-literal-value-high.ttl
+++ b/test/validate/bad-string-literal-value-high.ttl
@@ -6,7 +6,7 @@
eg:startsWithC
a rdfs:Datatype ;
- rdfs:label "starts with C" ;
+ rdfs:label "Starts With C" ;
owl:onDatatype xsd:string ;
owl:withRestrictions (
[
diff --git a/test/validate/bad-string-literal-value-low.ttl b/test/validate/bad-string-literal-value-low.ttl
index 06833a46..2b6985f8 100644
--- a/test/validate/bad-string-literal-value-low.ttl
+++ b/test/validate/bad-string-literal-value-low.ttl
@@ -6,7 +6,7 @@
eg:betweenBAndD
a rdfs:Datatype ;
- rdfs:label "between B and D" ;
+ rdfs:label "Between B and D" ;
owl:onDatatype xsd:string ;
owl:withRestrictions (
[
diff --git a/test/validate/bad-subclass-cycle.ttl b/test/validate/bad-subclass-cycle.ttl
new file mode 100644
index 00000000..1e702832
--- /dev/null
+++ b/test/validate/bad-subclass-cycle.ttl
@@ -0,0 +1,13 @@
+@prefix eg: <http://example.org/> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:Square
+ a rdfs:Class ;
+ rdfs:subClassOf eg:Rectangle ;
+ rdfs:label "Square" .
+
+eg:Rectangle
+ a rdfs:Class ;
+ rdfs:subClassOf eg:Square ;
+ rdfs:label "Rectangle" .
+
diff --git a/test/validate/bad-subproperty-cycle.ttl b/test/validate/bad-subproperty-cycle.ttl
new file mode 100644
index 00000000..eb3bbee4
--- /dev/null
+++ b/test/validate/bad-subproperty-cycle.ttl
@@ -0,0 +1,14 @@
+@prefix eg: <http://example.org/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:stuff
+ a rdf:Property ;
+ rdfs:subPropertyOf eg:things ;
+ rdfs:label "stuff" .
+
+eg:things
+ a rdf:Property ;
+ rdfs:subPropertyOf eg:stuff ;
+ rdfs:label "things" .
+
diff --git a/test/validate/bad-superclass-restriction.ttl b/test/validate/bad-superclass-restriction.ttl
new file mode 100644
index 00000000..bd820de4
--- /dev/null
+++ b/test/validate/bad-superclass-restriction.ttl
@@ -0,0 +1,22 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:SuperThing
+ a rdfs:Class ;
+ rdfs:label "SuperThing" ;
+ rdfs:subClassOf [
+ a owl:Restriction ;
+ owl:onProperty rdf:value ;
+ owl:minCardinality 1
+ ] .
+
+eg:Thing
+ a rdfs:Class ;
+ rdfs:label "Thing" ;
+ rdfs:subClassOf eg:SuperThing .
+
+eg:s
+ a eg:Thing .
+
diff --git a/test/validate/good-anyuri.ttl b/test/validate/good-anyuri.ttl
new file mode 100644
index 00000000..e05f8b71
--- /dev/null
+++ b/test/validate/good-anyuri.ttl
@@ -0,0 +1,13 @@
+@prefix eg: <http://example.org/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
+
+eg:uri
+ a rdf:Property ;
+ rdfs:label "uri" ;
+ rdfs:range xsd:anyURI .
+
+eg:s
+ eg:uri <http://example.org> .
+
diff --git a/test/validate/good-cardinality.ttl b/test/validate/good-cardinality.ttl
index 6b0b87da..74615fdc 100644
--- a/test/validate/good-cardinality.ttl
+++ b/test/validate/good-cardinality.ttl
@@ -5,6 +5,7 @@
eg:Thing
a rdfs:Class ;
+ rdfs:label "Thing" ;
rdfs:subClassOf [
a owl:Restriction ;
owl:onProperty rdf:value ;
diff --git a/test/validate/good-literal-value-high-inclusive.ttl b/test/validate/good-literal-value-high-inclusive.ttl
index bbaa84a2..18cbed0e 100644
--- a/test/validate/good-literal-value-high-inclusive.ttl
+++ b/test/validate/good-literal-value-high-inclusive.ttl
@@ -6,7 +6,7 @@
eg:Normal
a rdfs:Datatype ;
- rdfs:label "normal" ;
+ rdfs:label "Normal" ;
owl:onDatatype xsd:double ;
owl:withRestrictions (
[
diff --git a/test/validate/good-literal-value-low-inclusive.ttl b/test/validate/good-literal-value-low-inclusive.ttl
index 61943a36..e6ad334c 100644
--- a/test/validate/good-literal-value-low-inclusive.ttl
+++ b/test/validate/good-literal-value-low-inclusive.ttl
@@ -6,7 +6,7 @@
eg:Normal
a rdfs:Datatype ;
- rdfs:label "normal" ;
+ rdfs:label "Normal" ;
owl:onDatatype xsd:double ;
owl:withRestrictions (
[
diff --git a/test/validate/good-owl-thing.ttl b/test/validate/good-owl-thing.ttl
new file mode 100644
index 00000000..9c4b570d
--- /dev/null
+++ b/test/validate/good-owl-thing.ttl
@@ -0,0 +1,16 @@
+@prefix eg: <http://example.org/> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:someThing
+ a rdf:Property ;
+ rdfs:label "some thing" ;
+ rdfs:range owl:Thing .
+
+eg:thisThing
+ rdfs:label "this thing" .
+
+eg:s
+ eg:someThing eg:thisThing .
+
diff --git a/test/validate/good-pattern.ttl b/test/validate/good-pattern.ttl
index 569cd424..740ec22c 100644
--- a/test/validate/good-pattern.ttl
+++ b/test/validate/good-pattern.ttl
@@ -6,7 +6,7 @@
eg:CapitalLiteral
a rdfs:Datatype ;
- rdfs:label "capital literal" ;
+ rdfs:label "Capital Literal" ;
owl:withRestrictions (
[
xsd:pattern "[A-Z][a-z]*"
diff --git a/test/validate/good-rdfs-resource.ttl b/test/validate/good-rdfs-resource.ttl
new file mode 100644
index 00000000..26310553
--- /dev/null
+++ b/test/validate/good-rdfs-resource.ttl
@@ -0,0 +1,12 @@
+@prefix eg: <http://example.org/> .
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
+
+eg:resource
+ a rdf:Property ;
+ rdfs:label "resource" ;
+ rdfs:range rdfs:Resource .
+
+eg:s
+ eg:resource <http://example.org> .
+
diff --git a/test/validate/good-some-values-from.ttl b/test/validate/good-some-values-from.ttl
index 1da49270..23f977fd 100644
--- a/test/validate/good-some-values-from.ttl
+++ b/test/validate/good-some-values-from.ttl
@@ -5,6 +5,7 @@
eg:Thing
a rdfs:Class ;
+ rdfs:label "Thing" ;
rdfs:subClassOf [
a owl:Restriction ;
owl:onProperty rdfs:label ;
diff --git a/test/validate/good-string-literal-value-low.ttl b/test/validate/good-string-literal-value-low.ttl
index 5bfd6a9e..7d71856b 100644
--- a/test/validate/good-string-literal-value-low.ttl
+++ b/test/validate/good-string-literal-value-low.ttl
@@ -6,7 +6,7 @@
eg:betweenBAndD
a rdfs:Datatype ;
- rdfs:label "between B and D" ;
+ rdfs:label "Between B and D" ;
owl:onDatatype xsd:string ;
owl:withRestrictions (
[
diff --git a/test/validate/manifest.ttl b/test/validate/manifest.ttl
index 68853073..05c0a10d 100644
--- a/test/validate/manifest.ttl
+++ b/test/validate/manifest.ttl
@@ -1,205 +1,349 @@
+@prefix checks: <http://drobilla.net/ns/serd/checks#> .
@prefix mf: <http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#> .
+@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix rdft: <http://www.w3.org/ns/rdftest#> .
@prefix serd: <http://drobilla.net/ns/serd#> .
+rdft:Test
+ rdfs:subClassOf mf:ManifestEntry .
+
serd:TestTurtleNegativeValidate
a rdfs:Class ;
- rdfs:subClassOf rdft:Test .
+ rdfs:label "Turtle Negative Validation" ;
+ rdfs:subClassOf rdft:Test ,
+ [
+ rdf:type owl:Restriction ;
+ owl:onProperty serd:triggersCheck ;
+ owl:minCardinality 1
+ ] .
serd:TestTurtlePositiveValidate
a rdfs:Class ;
+ rdfs:label "Turtle Positive Validation" ;
rdfs:subClassOf rdft:Test .
+serd:triggersCheck
+ a rdf:Property ;
+ rdfs:label "triggers check" ;
+ rdfs:range serd:ValidatorCheck .
+
<>
- rdf:type mf:Manifest ;
+ a mf:Manifest ;
rdfs:comment "Serd validation test cases" ;
mf:entries (
<#bad-all-values-from>
+ <#bad-anyuri>
<#bad-cardinality-high>
<#bad-cardinality-low>
<#bad-cardinality>
+ <#bad-class-type-undefined>
+ <#bad-class-type>
<#bad-datatype-property>
+ <#bad-deprecated-class>
+ <#bad-deprecated-property>
<#bad-domain>
<#bad-functional-property>
<#bad-inverse-functional-property>
<#bad-literal-pattern>
- <#bad-literal-value-high-inclusive>
- <#bad-literal-value-low-inclusive>
<#bad-literal-value-high-exclusive>
+ <#bad-literal-value-high-inclusive>
<#bad-literal-value-low-exclusive>
- <#bad-string-literal-value-high>
- <#bad-string-literal-value-low>
+ <#bad-literal-value-low-inclusive>
+ <#bad-literal-value>
<#bad-object-property>
<#bad-pattern>
<#bad-plain-literal>
+ <#bad-predicate-type-undefined>
+ <#bad-predicate-type>
<#bad-range-instance-not-literal>
<#bad-range-instance>
<#bad-range-literal-not-instance>
<#bad-range-literal>
<#bad-some-values-from>
+ <#bad-string-literal-value-high>
+ <#bad-string-literal-value-low>
+ <#bad-subclass-cycle>
+ <#bad-subproperty-cycle>
+ <#bad-superclass-restriction>
<#bad-unknown-datatype>
- <#bad-unknown-property>
+ <#good-anyuri>
<#good-cardinality>
<#good-literal-value-high-inclusive>
<#good-literal-value-low-inclusive>
+ <#good-owl-thing>
<#good-pattern>
+ <#good-rdfs-resource>
<#good-some-values-from>
<#good-string-literal-value-low>
) .
<#bad-all-values-from>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:allValuesFrom ,
+ checks:instanceType ;
mf:name "bad-all-values-from" ;
mf:action <bad-all-values-from.ttl> .
+<#bad-anyuri>
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:anyUri ,
+ checks:propertyRange ;
+ mf:name "bad-anyuri" ;
+ mf:action <bad-anyuri.ttl> .
+
+<#bad-class-type-undefined>
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:classType ;
+ mf:name "bad-class-type-undefined" ;
+ mf:action <bad-class-type-undefined.ttl> .
+
+<#bad-class-type>
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:classType ;
+ mf:name "bad-class-type" ;
+ mf:action <bad-class-type.ttl> .
+
<#bad-cardinality-low>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:cardinalityMin ,
+ checks:instanceType ;
mf:name "bad-cardinality-low" ;
mf:action <bad-cardinality-low.ttl> .
<#bad-cardinality-high>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:cardinalityMax ,
+ checks:instanceType ;
mf:name "bad-cardinality-high" ;
mf:action <bad-cardinality-high.ttl> .
<#bad-cardinality>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:cardinalityEqual ,
+ checks:instanceType ;
mf:name "bad-cardinality" ;
mf:action <bad-cardinality.ttl> .
<#bad-datatype-property>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:datatypeProperty ;
mf:name "bad-datatype-property" ;
mf:action <bad-datatype-property.ttl> .
+<#bad-deprecated-class>
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:deprecatedClass ;
+ mf:name "bad-deprecated-class" ;
+ mf:action <bad-deprecated-class.ttl> .
+
+<#bad-deprecated-property>
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:deprecatedProperty ;
+ mf:name "bad-deprecated-property" ;
+ mf:action <bad-deprecated-property.ttl> .
+
<#bad-domain>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:cardinalityEqual ,
+ checks:propertyDomain ;
mf:name "bad-domain" ;
mf:action <bad-domain.ttl> .
<#bad-functional-property>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:functionalProperty ;
mf:name "bad-functional-property" ;
mf:action <bad-functional-property.ttl> .
<#bad-inverse-functional-property>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:inverseFunctionalProperty ;
mf:name "bad-inverse-functional-property" ;
mf:action <bad-inverse-functional-property.ttl> .
<#bad-literal-pattern>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:literalPattern ;
mf:name "bad-literal-pattern" ;
mf:action <bad-literal-pattern.ttl> .
<#bad-literal-value-low-inclusive>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:literalMinInclusive ;
mf:name "bad-literal-value-low-inclusive" ;
mf:action <bad-literal-value-low-inclusive.ttl> .
<#bad-literal-value-high-inclusive>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:literalMaxInclusive ;
mf:name "bad-literal-value-high-inclusive" ;
mf:action <bad-literal-value-high-inclusive.ttl> .
<#bad-literal-value-low-exclusive>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:literalMinExclusive ;
mf:name "bad-literal-value-low-exclusive" ;
mf:action <bad-literal-value-low-exclusive.ttl> .
<#bad-literal-value-high-exclusive>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:literalMaxExclusive ;
mf:name "bad-literal-value-high-exclusive" ;
mf:action <bad-literal-value-high-exclusive.ttl> .
+<#bad-literal-value>
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:literalValue ;
+ mf:name "bad-literal-value" ;
+ mf:action <bad-literal-value.ttl> .
+
<#bad-string-literal-value-low>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:literalMinExclusive ;
mf:name "bad-string-literal-value-low" ;
mf:action <bad-string-literal-value-low.ttl> .
<#bad-string-literal-value-high>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:literalMaxExclusive ;
mf:name "bad-string-literal-value-high" ;
mf:action <bad-string-literal-value-high.ttl> .
+<#bad-subclass-cycle>
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:classCycle ;
+ mf:name "bad-subclass-cycle" ;
+ mf:action <bad-subclass-cycle.ttl> .
+
+<#bad-subproperty-cycle>
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:propertyCycle ;
+ mf:name "bad-subproperty-cycle" ;
+ mf:action <bad-subproperty-cycle.ttl> .
+
+<#bad-superclass-restriction>
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:instanceType ,
+ checks:cardinalityMin ;
+ mf:name "bad-superclass-restriction" ;
+ mf:action <bad-superclass-restriction.ttl> .
+
<#bad-object-property>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:objectProperty ;
mf:name "bad-object-property" ;
mf:action <bad-object-property.ttl> .
<#bad-pattern>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:literalPattern ;
mf:name "bad-pattern" ;
mf:action <bad-pattern.ttl> .
<#bad-plain-literal>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:plainLiteralDatatype ,
+ checks:propertyRange ;
mf:name "bad-plain-literal" ;
mf:action <bad-plain-literal.ttl> .
<#bad-range-instance-not-literal>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:instanceLiteral ,
+ checks:propertyRange ;
mf:name "bad-range-instance-not-literal" ;
mf:action <bad-range-instance-not-literal.ttl> .
<#bad-range-instance>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:instanceType ,
+ checks:propertyRange ;
mf:name "bad-range-instance" ;
mf:action <bad-range-instance.ttl> .
<#bad-range-literal-not-instance>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:literalInstance ,
+ checks:propertyRange ;
mf:name "bad-range-literal-not-instance" ;
mf:action <bad-range-literal-not-instance.ttl> .
<#bad-range-literal>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:literalMaxInclusive ,
+ checks:propertyRange ;
mf:name "bad-range-literal" ;
mf:action <bad-range-literal.ttl> .
<#bad-some-values-from>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:someValuesFrom ,
+ checks:instanceType ;
mf:name "bad-some-values-from" ;
mf:action <bad-some-values-from.ttl> .
<#bad-unknown-datatype>
- rdf:type serd:TestTurtleNegativeValidate ;
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:datatypeType ;
mf:name "bad-unknown-datatype" ;
mf:action <bad-unknown-datatype.ttl> .
-<#bad-unknown-property>
- rdf:type serd:TestTurtleNegativeValidate ;
- mf:name "bad-unknown-property" ;
- mf:action <bad-unknown-property.ttl> .
+<#bad-predicate-type-undefined>
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:predicateType ;
+ mf:name "bad-predicate-type-undefined" ;
+ mf:action <bad-predicate-type-undefined.ttl> .
+
+<#bad-predicate-type>
+ a serd:TestTurtleNegativeValidate ;
+ serd:triggersCheck checks:predicateType ;
+ mf:name "bad-predicate-type" ;
+ mf:action <bad-predicate-type.ttl> .
+
+<#good-anyuri>
+ a serd:TestTurtlePositiveValidate ;
+ mf:name "good-anyuri" ;
+ mf:action <good-anyuri.ttl> .
<#good-cardinality>
- rdf:type serd:TestTurtlePositiveValidate ;
+ a serd:TestTurtlePositiveValidate ;
mf:name "good-cardinality" ;
mf:action <good-cardinality.ttl> .
<#good-literal-value-low-inclusive>
- rdf:type serd:TestTurtlePositiveValidate ;
+ a serd:TestTurtlePositiveValidate ;
mf:name "good-literal-value-low-inclusive" ;
mf:action <good-literal-value-low-inclusive.ttl> .
<#good-literal-value-high-inclusive>
- rdf:type serd:TestTurtlePositiveValidate ;
+ a serd:TestTurtlePositiveValidate ;
mf:name "good-literal-value-high-inclusive" ;
mf:action <good-literal-value-high-inclusive.ttl> .
<#good-some-values-from>
- rdf:type serd:TestTurtlePositiveValidate ;
+ a serd:TestTurtlePositiveValidate ;
mf:name "good-some-values-from" ;
mf:action <good-some-values-from.ttl> .
+<#good-owl-thing>
+ a serd:TestTurtlePositiveValidate ;
+ mf:name "good-owl-thing" ;
+ mf:action <good-owl-thing.ttl> .
+
<#good-pattern>
- rdf:type serd:TestTurtlePositiveValidate ;
+ a serd:TestTurtlePositiveValidate ;
mf:name "good-pattern" ;
mf:action <good-pattern.ttl> .
+<#good-rdfs-resource>
+ a serd:TestTurtlePositiveValidate ;
+ mf:name "good-rdfs-resource" ;
+ mf:action <good-rdfs-resource.ttl> .
+
<#good-string-literal-value-low>
- rdf:type serd:TestTurtlePositiveValidate ;
+ a serd:TestTurtlePositiveValidate ;
mf:name "good-string-literal-value-low" ;
mf:action <good-string-literal-value-low.ttl> .
+