aboutsummaryrefslogtreecommitdiffstats
path: root/src/validate.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/validate.c')
-rw-r--r--src/validate.c2139
1 files changed, 1582 insertions, 557 deletions
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);
}