aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-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
10 files changed, 1760 insertions, 646 deletions
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);