aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Robillard <d@drobilla.net>2019-04-28 16:36:02 +0200
committerDavid Robillard <d@drobilla.net>2021-03-08 23:23:05 -0500
commit01daa5914169d57a6a6c4925d3f8d76db80a2bd8 (patch)
tree468a3371fe17adc082e93f7894147c5d67ed47fe
parent36e2f27502524155e6475a75ffcab4999fce166a (diff)
downloadserd-01daa5914169d57a6a6c4925d3f8d76db80a2bd8.tar.gz
serd-01daa5914169d57a6a6c4925d3f8d76db80a2bd8.tar.bz2
serd-01daa5914169d57a6a6c4925d3f8d76db80a2bd8.zip
Report writer errors and add strict write mode
-rw-r--r--NEWS1
-rw-r--r--include/serd/serd.h7
-rw-r--r--src/serdi.c3
-rw-r--r--src/string.c4
-rw-r--r--src/writer.c429
-rw-r--r--test/test_reader_writer.c37
-rw-r--r--test/test_string.c2
7 files changed, 303 insertions, 180 deletions
diff --git a/NEWS b/NEWS
index 4b40e14b..0a2d5e45 100644
--- a/NEWS
+++ b/NEWS
@@ -10,6 +10,7 @@ serd (1.0.1) unstable;
* Remove serd_uri_to_path()
* Remove useless character counting from API
* Rename SerdChunk to SerdStringView
+ * Report writer errors and add strict write mode
* Simplify streaming API and improve pretty printing
* Simplify writer style options
* Use a fixed-size reader stack
diff --git a/include/serd/serd.h b/include/serd/serd.h
index 91994912..c6300721 100644
--- a/include/serd/serd.h
+++ b/include/serd/serd.h
@@ -120,7 +120,9 @@ typedef enum {
SERD_ERR_BAD_CURIE, ///< Invalid CURIE (e.g. prefix does not exist)
SERD_ERR_INTERNAL, ///< Unexpected internal error (should not happen)
SERD_ERR_OVERFLOW, ///< Stack overflow
- SERD_ERR_NO_DATA ///< Unexpected end of input
+ SERD_ERR_NO_DATA, ///< Unexpected end of input
+ SERD_ERR_BAD_TEXT, ///< Invalid text encoding
+ SERD_ERR_BAD_WRITE, ///< Error writing to file/stream
} SerdStatus;
/// RDF syntax type
@@ -302,7 +304,8 @@ typedef enum {
SERD_WRITE_ASCII = 1u << 0u, ///< Escape all non-ASCII characters
SERD_WRITE_UNQUALIFIED = 1u << 1u, ///< Do not shorten URIs into CURIEs
SERD_WRITE_UNRESOLVED = 1u << 2u, ///< Do not make URIs relative
- SERD_WRITE_TERSE = 1u << 3u ///< Write terser output without newlines
+ SERD_WRITE_TERSE = 1u << 3u, ///< Write terser output without newlines
+ SERD_WRITE_STRICT = 1u << 4u ///< Abort with error on lossy output
} SerdWriterFlag;
/// Bitwise OR of SerdWriterFlag values
diff --git a/src/serdi.c b/src/serdi.c
index 986971e8..64206d7f 100644
--- a/src/serdi.c
+++ b/src/serdi.c
@@ -97,7 +97,7 @@ main(int argc, char** argv)
SerdSyntax input_syntax = (SerdSyntax)0;
SerdSyntax output_syntax = (SerdSyntax)0;
- SerdWriterFlags writer_flags = 0u;
+ SerdWriterFlags writer_flags = SERD_WRITE_STRICT;
bool from_string = false;
bool from_stdin = false;
bool bulk_read = true;
@@ -126,6 +126,7 @@ main(int argc, char** argv)
} else if (argv[a][1] == 'h') {
return print_usage(argv[0], false);
} else if (argv[a][1] == 'l') {
+ writer_flags &= ~(unsigned)SERD_WRITE_STRICT;
lax = true;
} else if (argv[a][1] == 'q') {
quiet = true;
diff --git a/src/string.c b/src/string.c
index e3d20b15..f0aca005 100644
--- a/src/string.c
+++ b/src/string.c
@@ -58,6 +58,10 @@ serd_strerror(SerdStatus status)
return "Stack overflow";
case SERD_ERR_NO_DATA:
return "Unexpectd end of input";
+ case SERD_ERR_BAD_TEXT:
+ return "Invalid text encoding";
+ case SERD_ERR_BAD_WRITE:
+ return "Error writing to file";
default:
break;
}
diff --git a/src/writer.c b/src/writer.c
index 7e3033a8..3ac8f4d9 100644
--- a/src/writer.c
+++ b/src/writer.c
@@ -26,12 +26,26 @@
#include "serd/serd.h"
#include <assert.h>
+#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#ifndef _MSC_VER
+# define SERD_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
+#else
+# define SERD_WARN_UNUSED_RESULT
+#endif
+
+#define TRY(st, exp) \
+ do { \
+ if (((st) = (exp))) { \
+ return (st); \
+ } \
+ } while (0)
+
typedef enum {
CTX_NAMED, ///< Normal non-anonymous context
CTX_BLANK, ///< Anonymous blank node
@@ -134,7 +148,7 @@ serd_writer_set_prefix(SerdWriter* writer,
const SerdNode* name,
const SerdNode* uri);
-static bool
+SERD_WARN_UNUSED_RESULT static SerdStatus
write_node(SerdWriter* writer,
const SerdNode* node,
SerdField field,
@@ -221,16 +235,37 @@ ctx(SerdWriter* writer, const SerdField field)
return node && node->type ? node : NULL;
}
-static inline size_t
+SERD_WARN_UNUSED_RESULT static inline size_t
sink(const void* buf, size_t len, SerdWriter* writer)
{
- return writer->write_func(buf, 1, len, writer->stream);
+ const size_t written = writer->write_func(buf, 1, len, writer->stream);
+ if (written != len) {
+ if (errno) {
+ serd_world_errorf(writer->world,
+ SERD_ERR_BAD_WRITE,
+ "write error (%s)\n",
+ strerror(errno));
+ } else {
+ serd_world_errorf(writer->world, SERD_ERR_BAD_WRITE, "write error\n");
+ }
+ }
+
+ return written;
+}
+
+SERD_WARN_UNUSED_RESULT static inline SerdStatus
+esink(const void* buf, size_t len, SerdWriter* writer)
+{
+ return sink(buf, len, writer) == len ? SERD_SUCCESS : SERD_ERR_BAD_WRITE;
}
// Write a single character, as an escape for single byte characters
// (Caller prints any single byte characters that don't need escaping)
-static size_t
-write_character(SerdWriter* writer, const uint8_t* utf8, size_t* size)
+SERD_WARN_UNUSED_RESULT static size_t
+write_character(SerdWriter* writer,
+ const uint8_t* utf8,
+ size_t* size,
+ SerdStatus* st)
{
char escape[11] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
const uint32_t c = parse_utf8_char(utf8, size);
@@ -238,6 +273,7 @@ write_character(SerdWriter* writer, const uint8_t* utf8, size_t* size)
case 0:
serd_world_errorf(
writer->world, SERD_ERR_BAD_ARG, "invalid UTF-8 start: %X\n", utf8[0]);
+ *st = SERD_ERR_BAD_TEXT;
return 0;
case 1:
snprintf(escape, sizeof(escape), "\\u%04X", utf8[0]);
@@ -281,7 +317,7 @@ uri_must_escape(const char c)
}
static size_t
-write_uri(SerdWriter* writer, const char* utf8, size_t n_bytes)
+write_uri(SerdWriter* writer, const char* utf8, size_t n_bytes, SerdStatus* st)
{
size_t len = 0;
for (size_t i = 0; i < n_bytes;) {
@@ -293,15 +329,24 @@ write_uri(SerdWriter* writer, const char* utf8, size_t n_bytes)
}
// Bulk write all characters up to this special one
- len += sink(&utf8[i], j - i, writer);
+ const size_t n_bulk = sink(&utf8[i], j - i, writer);
+ len += n_bulk;
+ if (n_bulk != j - i) {
+ return len;
+ }
+
if ((i = j) == n_bytes) {
break; // Reached end
}
// Write UTF-8 character
size_t size = 0;
- len += write_character(writer, (const uint8_t*)utf8 + i, &size);
+ len += write_character(writer, (const uint8_t*)utf8 + i, &size, st);
i += size;
+ if (*st && (writer->flags & SERD_WRITE_STRICT)) {
+ break;
+ }
+
if (size == 0) {
// Corrupt input, write percent-encoded byts and scan to next start
char escape[4] = {0, 0, 0, 0};
@@ -311,13 +356,23 @@ write_uri(SerdWriter* writer, const char* utf8, size_t n_bytes)
}
}
}
+
return len;
}
-static size_t
+SERD_WARN_UNUSED_RESULT static SerdStatus
+ewrite_uri(SerdWriter* writer, const char* utf8, size_t n_bytes)
+{
+ SerdStatus st = SERD_SUCCESS;
+ write_uri(writer, utf8, n_bytes, &st);
+
+ return (writer->flags & SERD_WRITE_STRICT) ? st : SERD_SUCCESS;
+}
+
+SERD_WARN_UNUSED_RESULT static SerdStatus
write_uri_from_node(SerdWriter* writer, const SerdNode* node)
{
- return write_uri(writer, serd_node_string(node), node->n_bytes);
+ return ewrite_uri(writer, serd_node_string(node), node->n_bytes);
}
static bool
@@ -357,10 +412,10 @@ lname_must_escape(const char c)
return false;
}
-static size_t
+SERD_WARN_UNUSED_RESULT static SerdStatus
write_lname(SerdWriter* writer, const char* utf8, size_t n_bytes)
{
- size_t len = 0;
+ SerdStatus st = SERD_SUCCESS;
for (size_t i = 0; i < n_bytes; ++i) {
size_t j = i; // Index of next character that must be escaped
for (; j < n_bytes; ++j) {
@@ -370,27 +425,28 @@ write_lname(SerdWriter* writer, const char* utf8, size_t n_bytes)
}
// Bulk write all characters up to this special one
- len += sink(&utf8[i], j - i, writer);
+ TRY(st, esink(&utf8[i], j - i, writer));
if ((i = j) == n_bytes) {
break; // Reached end
}
// Write escape
- len += sink("\\", 1, writer);
- len += sink(&utf8[i], 1, writer);
+ TRY(st, esink("\\", 1, writer));
+ TRY(st, esink(&utf8[i], 1, writer));
}
- return len;
+ return st;
}
-static size_t
+SERD_WARN_UNUSED_RESULT static SerdStatus
write_text(SerdWriter* writer,
TextContext ctx,
const char* utf8,
size_t n_bytes)
{
- size_t len = 0;
- size_t n_consecutive_quotes = 0;
+ size_t len = 0;
+ size_t n_consecutive_quotes = 0;
+ SerdStatus st = SERD_SUCCESS;
for (size_t i = 0; i < n_bytes;) {
if (utf8[i] != '"') {
n_consecutive_quotes = 0;
@@ -474,7 +530,10 @@ write_text(SerdWriter* writer,
// Write UTF-8 character
size_t size = 0;
- len += write_character(writer, (const uint8_t*)utf8 + i - 1, &size);
+ len += write_character(writer, (const uint8_t*)utf8 + i - 1, &size, &st);
+ if (st && (writer->flags & SERD_WRITE_STRICT)) {
+ return st;
+ }
if (size == 0) {
// Corrupt input, write repacement character and scan to the next start
@@ -485,41 +544,55 @@ write_text(SerdWriter* writer,
i += size - 1;
}
}
- return len;
+
+ return (writer->flags & SERD_WRITE_STRICT) ? st : SERD_SUCCESS;
}
-static size_t
+typedef struct {
+ SerdWriter* writer;
+ SerdStatus status;
+} UriSinkContext;
+
+SERD_WARN_UNUSED_RESULT static size_t
uri_sink(const void* buf, size_t size, size_t nmemb, void* stream)
{
(void)size;
assert(size == 1);
- return write_uri((SerdWriter*)stream, (const char*)buf, nmemb);
+
+ UriSinkContext* const ctx = (UriSinkContext*)stream;
+ SerdWriter* const writer = ctx->writer;
+
+ return write_uri(writer, (const char*)buf, nmemb, &ctx->status);
}
-static void
+SERD_WARN_UNUSED_RESULT static SerdStatus
write_newline(SerdWriter* writer, bool terse)
{
if (terse || (writer->flags & SERD_WRITE_TERSE)) {
- sink(" ", 1, writer);
- } else {
- sink("\n", 1, writer);
- for (int i = 0; i < writer->indent; ++i) {
- sink("\t", 1, writer);
- }
+ return esink(" ", 1, writer);
+ }
+
+ SerdStatus st = SERD_SUCCESS;
+ TRY(st, esink("\n", 1, writer));
+ for (int i = 0; i < writer->indent; ++i) {
+ TRY(st, esink("\t", 1, writer));
}
+
+ return st;
}
-static void
+SERD_WARN_UNUSED_RESULT static SerdStatus
write_top_level_sep(SerdWriter* writer)
{
- if (!writer->empty && !(writer->flags & SERD_WRITE_TERSE)) {
- write_newline(writer, false);
- }
+ return ((!writer->empty && !(writer->flags & SERD_WRITE_TERSE))
+ ? write_newline(writer, false)
+ : SERD_SUCCESS);
}
-static bool
+SERD_WARN_UNUSED_RESULT static SerdStatus
write_sep(SerdWriter* writer, const SerdStatementFlags flags, Sep sep)
{
+ SerdStatus st = SERD_SUCCESS;
const SepRule* rule = &rules[sep];
const bool terse = (((flags & SERD_TERSE_S) && (flags & SERD_LIST_S)) ||
((flags & SERD_TERSE_O) && (flags & SERD_LIST_O)));
@@ -537,19 +610,19 @@ write_sep(SerdWriter* writer, const SerdStatementFlags flags, Sep sep)
// Write newline or space before separator if necessary
if (rule->pre_line_after & (1u << writer->last_sep)) {
- write_newline(writer, terse);
+ TRY(st, write_newline(writer, terse));
} else if (rule->pre_space_after & (1u << writer->last_sep)) {
- sink(" ", 1, writer);
+ TRY(st, esink(" ", 1, writer));
}
// Write actual separator string
if (rule->len > 0) {
- sink(rule->str, rule->len, writer);
+ TRY(st, esink(rule->str, rule->len, writer));
}
// Write newline after separator if necessary
if (rule->post_line_after & (1u << writer->last_sep)) {
- write_newline(writer, terse);
+ TRY(st, write_newline(writer, terse));
writer->last_sep = SEP_NONE;
} else {
writer->last_sep = sep;
@@ -559,10 +632,10 @@ write_sep(SerdWriter* writer, const SerdStatementFlags flags, Sep sep)
writer->indent = 0;
}
- return true;
+ return st;
}
-static SerdStatus
+static void
reset_context(SerdWriter* writer, bool graph)
{
// Free any lingering contexts in case there was an error
@@ -586,8 +659,6 @@ reset_context(SerdWriter* writer, bool graph)
writer->empty = false;
serd_stack_clear(&writer->anon_stack);
-
- return SERD_SUCCESS;
}
static bool
@@ -600,7 +671,7 @@ is_inline_start(const SerdWriter* writer,
(field == SERD_OBJECT && (flags & SERD_ANON_O))));
}
-static bool
+SERD_WARN_UNUSED_RESULT static SerdStatus
write_literal(SerdWriter* writer,
const SerdNode* node,
SerdStatementFlags flags)
@@ -614,8 +685,7 @@ write_literal(SerdWriter* writer,
if (supports_abbrev(writer) && type_uri) {
if (serd_node_equals(datatype, writer->world->xsd_boolean) ||
serd_node_equals(datatype, writer->world->xsd_integer)) {
- sink(node_str, node->n_bytes, writer);
- return true;
+ return esink(node_str, node->n_bytes, writer);
}
if (serd_node_equals(datatype, writer->world->xsd_decimal) &&
@@ -624,29 +694,29 @@ write_literal(SerdWriter* writer,
not be written bare in Turtle. We could add a 0 which is
prettier, but changes the text and breaks round tripping.
*/
- sink(node_str, node->n_bytes, writer);
- return true;
+ return esink(node_str, node->n_bytes, writer);
}
}
+ SerdStatus st = SERD_SUCCESS;
if (supports_abbrev(writer) &&
(node->flags & (SERD_HAS_NEWLINE | SERD_HAS_QUOTE))) {
- sink("\"\"\"", 3, writer);
- write_text(writer, WRITE_LONG_STRING, node_str, node->n_bytes);
- sink("\"\"\"", 3, writer);
+ TRY(st, esink("\"\"\"", 3, writer));
+ TRY(st, write_text(writer, WRITE_LONG_STRING, node_str, node->n_bytes));
+ TRY(st, esink("\"\"\"", 3, writer));
} else {
- sink("\"", 1, writer);
- write_text(writer, WRITE_STRING, node_str, node->n_bytes);
- sink("\"", 1, writer);
+ TRY(st, esink("\"", 1, writer));
+ TRY(st, write_text(writer, WRITE_STRING, node_str, node->n_bytes));
+ TRY(st, esink("\"", 1, writer));
}
if (lang && serd_node_string(lang)) {
- sink("@", 1, writer);
- sink(serd_node_string(lang), lang->n_bytes, writer);
+ TRY(st, esink("@", 1, writer));
+ TRY(st, esink(serd_node_string(lang), lang->n_bytes, writer));
} else if (type_uri) {
- sink("^^", 2, writer);
+ TRY(st, esink("^^", 2, writer));
return write_node(writer, datatype, (SerdField)-1, flags);
}
- return true;
+ return st;
}
// Return true iff `buf` is a valid prefixed name prefix or suffix
@@ -663,15 +733,17 @@ is_name(const char* buf, const size_t len)
return true;
}
-static bool
+SERD_WARN_UNUSED_RESULT static SerdStatus
write_uri_node(SerdWriter* const writer,
const SerdNode* node,
const SerdField field,
const SerdStatementFlags flags)
{
+ SerdStatus st = SERD_SUCCESS;
+
if (is_inline_start(writer, field, flags)) {
- write_sep(writer, flags, SEP_ANON_BEGIN);
- sink(" == ", 4, writer);
+ TRY(st, write_sep(writer, flags, SEP_ANON_BEGIN));
+ TRY(st, esink(" == ", 4, writer));
}
const SerdNode* prefix = NULL;
@@ -681,21 +753,20 @@ write_uri_node(SerdWriter* const writer,
if (supports_abbrev(writer)) {
if (field == SERD_PREDICATE &&
serd_node_equals(node, writer->world->rdf_type)) {
- return sink("a", 1, writer) == 1;
+ return esink("a", 1, writer);
}
if (serd_node_equals(node, writer->world->rdf_type)) {
- return sink("()", 2, writer) == 2;
+ return esink("()", 2, writer);
}
if (has_scheme && !(writer->flags & SERD_WRITE_UNQUALIFIED) &&
serd_env_qualify_in_place(writer->env, node, &prefix, &suffix) &&
is_name(serd_node_string(prefix), serd_node_length(prefix)) &&
is_name(suffix.buf, suffix.len)) {
- write_uri_from_node(writer, prefix);
- sink(":", 1, writer);
- write_uri(writer, suffix.buf, suffix.len);
- return true;
+ TRY(st, write_uri_from_node(writer, prefix));
+ TRY(st, esink(":", 1, writer));
+ return ewrite_uri(writer, suffix.buf, suffix.len);
}
}
@@ -705,10 +776,10 @@ write_uri_node(SerdWriter* const writer,
SERD_ERR_BAD_ARG,
"syntax does not support URI reference <%s>\n",
node_str);
- return false;
+ return SERD_ERR_BAD_ARG;
}
- sink("<", 1, writer);
+ TRY(st, esink("<", 1, writer));
if (!(writer->flags & SERD_WRITE_UNRESOLVED) &&
serd_env_base_uri(writer->env)) {
const SerdURIView base_uri = serd_env_base_uri_view(writer->env);
@@ -716,26 +787,27 @@ write_uri_node(SerdWriter* const writer,
SerdURIView abs_uri = serd_resolve_uri(uri, base_uri);
bool rooted = uri_is_under(&base_uri, &writer->root_uri);
const SerdURIView* root = rooted ? &writer->root_uri : &base_uri;
+ UriSinkContext ctx = {writer, SERD_SUCCESS};
+ const bool write_abs =
+ (!supports_abbrev(writer) || !uri_is_under(&abs_uri, root));
- if (writer->syntax == SERD_NTRIPLES || writer->syntax == SERD_NQUADS ||
- !uri_is_under(&abs_uri, root) || !uri_is_related(&abs_uri, &base_uri)) {
- serd_write_uri(abs_uri, uri_sink, writer);
- } else {
- serd_write_uri(serd_relative_uri(uri, base_uri), uri_sink, writer);
+ write_abs
+ ? serd_write_uri(abs_uri, uri_sink, &ctx)
+ : serd_write_uri(serd_relative_uri(uri, base_uri), uri_sink, &ctx);
+
+ st = ctx.status;
+ if (st && (writer->flags & SERD_WRITE_STRICT)) {
+ return st;
}
} else {
- write_uri_from_node(writer, node);
- }
- sink(">", 1, writer);
-
- if (is_inline_start(writer, field, flags)) {
- sink(" ;", 2, writer);
+ TRY(st, write_uri_from_node(writer, node));
}
+ TRY(st, esink(">", 1, writer));
- return true;
+ return is_inline_start(writer, field, flags) ? esink(" ;", 2, writer) : st;
}
-static bool
+SERD_WARN_UNUSED_RESULT static SerdStatus
write_curie(SerdWriter* const writer,
const SerdNode* node,
const SerdField field,
@@ -754,28 +826,28 @@ write_curie(SerdWriter* const writer,
st,
"undefined namespace prefix `%s'\n",
serd_node_string(node));
- return false;
+ return st;
}
- sink("<", 1, writer);
- write_uri(writer, prefix.buf, prefix.len);
- write_uri(writer, suffix.buf, suffix.len);
- sink(">", 1, writer);
+ TRY(st, esink("<", 1, writer));
+ TRY(st, ewrite_uri(writer, prefix.buf, prefix.len));
+ TRY(st, ewrite_uri(writer, suffix.buf, suffix.len));
+ TRY(st, esink(">", 1, writer));
break;
case SERD_TURTLE:
case SERD_TRIG:
if (is_inline_start(writer, field, flags)) {
- write_sep(writer, flags, SEP_ANON_BEGIN);
- sink(" == ", 4, writer);
+ TRY(st, write_sep(writer, flags, SEP_ANON_BEGIN));
+ TRY(st, esink(" == ", 4, writer));
}
- write_lname(writer, serd_node_string(node), node->n_bytes);
+ TRY(st, write_lname(writer, serd_node_string(node), node->n_bytes));
if (is_inline_start(writer, field, flags)) {
- sink(" ;", 2, writer);
+ TRY(st, esink(" ;", 2, writer));
}
}
- return true;
+ return st;
}
-static bool
+SERD_WARN_UNUSED_RESULT static SerdStatus
write_blank(SerdWriter* const writer,
const SerdNode* node,
const SerdField field,
@@ -794,25 +866,26 @@ write_blank(SerdWriter* const writer,
if (field == SERD_SUBJECT && (flags & SERD_EMPTY_S)) {
writer->last_sep = SEP_NONE; // Treat "[]" like a node
- return sink("[]", 2, writer) == 2;
+ return esink("[]", 2, writer);
}
}
- sink("_:", 2, writer);
- if (writer->bprefix &&
+ SerdStatus st = esink("_:", 2, writer);
+ if (!st && writer->bprefix &&
!strncmp(node_str, writer->bprefix, writer->bprefix_len)) {
- sink(node_str + writer->bprefix_len,
- node->n_bytes - writer->bprefix_len,
- writer);
+ TRY(st,
+ esink(node_str + writer->bprefix_len,
+ node->n_bytes - writer->bprefix_len,
+ writer));
} else {
- sink(node_str, node->n_bytes, writer);
+ TRY(st, esink(node_str, node->n_bytes, writer));
}
writer->last_sep = SEP_NONE;
- return true;
+ return st;
}
-static bool
+SERD_WARN_UNUSED_RESULT static SerdStatus
write_node(SerdWriter* writer,
const SerdNode* node,
const SerdField field,
@@ -829,7 +902,7 @@ write_node(SerdWriter* writer,
return write_blank(writer, node, field, flags);
}
- return false;
+ return SERD_ERR_INTERNAL;
}
static inline bool
@@ -838,32 +911,31 @@ is_resource(const SerdNode* node)
return node && node->type > SERD_LITERAL;
}
-static void
+SERD_WARN_UNUSED_RESULT static SerdStatus
write_pred(SerdWriter* writer, SerdStatementFlags flags, const SerdNode* pred)
{
- write_node(writer, pred, SERD_PREDICATE, flags);
- write_sep(writer, flags, SEP_P_O);
+ SerdStatus st = SERD_SUCCESS;
+ TRY(st, write_node(writer, pred, SERD_PREDICATE, flags));
+ TRY(st, write_sep(writer, flags, SEP_P_O));
serd_node_set(&writer->context.predicate, pred);
+ return st;
}
-static bool
+SERD_WARN_UNUSED_RESULT static SerdStatus
write_list_obj(SerdWriter* writer,
SerdStatementFlags flags,
const SerdNode* predicate,
- const SerdNode* object)
+ const SerdNode* object,
+ bool* is_end)
{
if (serd_node_equals(object, writer->world->rdf_nil)) {
- write_sep(writer, writer->context.flags, SEP_LIST_END);
- return true;
+ *is_end = true;
+ return write_sep(writer, writer->context.flags, SEP_LIST_END);
}
- if (serd_node_equals(predicate, writer->world->rdf_first)) {
- write_node(writer, object, SERD_OBJECT, flags);
- } else {
- write_sep(writer, writer->context.flags, SEP_LIST_SEP);
- }
-
- return false;
+ return (serd_node_equals(predicate, writer->world->rdf_first)
+ ? write_node(writer, object, SERD_OBJECT, flags)
+ : write_sep(writer, writer->context.flags, SEP_LIST_SEP));
}
static SerdStatus
@@ -888,49 +960,44 @@ serd_writer_write_statement(SerdWriter* writer,
return SERD_ERR_BAD_ARG;
}
-#define TRY(write_result) \
- do { \
- if (!(write_result)) { \
- return SERD_ERR_UNKNOWN; \
- } \
- } while (0)
-
if (writer->syntax == SERD_NTRIPLES || writer->syntax == SERD_NQUADS) {
- TRY(write_node(writer, subject, SERD_SUBJECT, flags));
- sink(" ", 1, writer);
- TRY(write_node(writer, predicate, SERD_PREDICATE, flags));
- sink(" ", 1, writer);
- TRY(write_node(writer, object, SERD_OBJECT, flags));
+ TRY(st, write_node(writer, subject, SERD_SUBJECT, flags));
+ TRY(st, esink(" ", 1, writer));
+ TRY(st, write_node(writer, predicate, SERD_PREDICATE, flags));
+ TRY(st, esink(" ", 1, writer));
+ TRY(st, write_node(writer, object, SERD_OBJECT, flags));
if (writer->syntax == SERD_NQUADS && graph) {
- sink(" ", 1, writer);
- TRY(write_node(writer, graph, SERD_GRAPH, flags));
+ TRY(st, esink(" ", 1, writer));
+ TRY(st, write_node(writer, graph, SERD_GRAPH, flags));
}
- sink(" .\n", 3, writer);
+ TRY(st, esink(" .\n", 3, writer));
return SERD_SUCCESS;
}
if ((graph && !serd_node_equals(graph, writer->context.graph)) ||
(!graph && ctx(writer, SERD_GRAPH))) {
if (ctx(writer, SERD_SUBJECT)) {
- write_sep(writer, writer->context.flags, SEP_END_S);
+ TRY(st, write_sep(writer, writer->context.flags, SEP_END_S));
}
if (ctx(writer, SERD_GRAPH)) {
- write_sep(writer, writer->context.flags, SEP_GRAPH_END);
+ TRY(st, write_sep(writer, writer->context.flags, SEP_GRAPH_END));
}
- write_top_level_sep(writer);
+ TRY(st, write_top_level_sep(writer));
reset_context(writer, true);
if (graph) {
- TRY(write_node(writer, graph, SERD_GRAPH, flags));
- write_sep(writer, flags, SEP_GRAPH_BEGIN);
+ TRY(st, write_node(writer, graph, SERD_GRAPH, flags));
+ TRY(st, write_sep(writer, flags, SEP_GRAPH_BEGIN));
serd_node_set(&writer->context.graph, graph);
}
}
if (writer->context.type == CTX_LIST) {
- if (write_list_obj(writer, flags, predicate, object)) {
+ bool is_end = false;
+ TRY(st, write_list_obj(writer, flags, predicate, object, &is_end));
+ if (is_end) {
// Reached end of list
pop_context(writer);
return SERD_SUCCESS;
@@ -944,8 +1011,8 @@ serd_writer_write_statement(SerdWriter* writer,
writer->context.indented_object = true;
}
- write_sep(writer, writer->context.flags, SEP_END_O);
- write_node(writer, object, SERD_OBJECT, flags);
+ TRY(st, write_sep(writer, writer->context.flags, SEP_END_O));
+ TRY(st, write_node(writer, object, SERD_OBJECT, flags));
} else {
// Abbreviate S
if (writer->context.indented_object && writer->indent > 0) {
@@ -953,10 +1020,10 @@ serd_writer_write_statement(SerdWriter* writer,
writer->context.indented_object = false;
}
- Sep sep = ctx(writer, SERD_PREDICATE) ? SEP_END_P : SEP_S_P;
- write_sep(writer, writer->context.flags, sep);
- write_pred(writer, writer->context.flags, predicate);
- write_node(writer, object, SERD_OBJECT, flags);
+ const Sep sep = ctx(writer, SERD_PREDICATE) ? SEP_END_P : SEP_S_P;
+ TRY(st, write_sep(writer, writer->context.flags, sep));
+ TRY(st, write_pred(writer, writer->context.flags, predicate));
+ TRY(st, write_node(writer, object, SERD_OBJECT, flags));
}
} else {
// No abbreviation
@@ -967,30 +1034,30 @@ serd_writer_write_statement(SerdWriter* writer,
if (serd_stack_is_empty(&writer->anon_stack)) {
if (ctx(writer, SERD_SUBJECT)) { // Terminate last subject
- write_sep(writer, writer->context.flags, SEP_END_S);
+ TRY(st, write_sep(writer, writer->context.flags, SEP_END_S));
}
- write_top_level_sep(writer);
+ TRY(st, write_top_level_sep(writer));
}
if (serd_stack_is_empty(&writer->anon_stack)) {
- write_node(writer, subject, SERD_SUBJECT, flags);
+ TRY(st, write_node(writer, subject, SERD_SUBJECT, flags));
if (!(flags & (SERD_ANON_S | SERD_LIST_S))) {
- write_sep(writer, writer->context.flags, SEP_S_P);
+ TRY(st, write_sep(writer, writer->context.flags, SEP_S_P));
} else if (flags & SERD_ANON_S) {
- write_sep(writer, writer->context.flags, SEP_ANON_S_P);
+ TRY(st, write_sep(writer, writer->context.flags, SEP_ANON_S_P));
}
} else {
- write_sep(writer, writer->context.flags, SEP_ANON_S_P);
+ TRY(st, write_sep(writer, writer->context.flags, SEP_ANON_S_P));
}
reset_context(writer, false);
serd_node_set(&writer->context.subject, subject);
if (!(flags & SERD_LIST_S)) {
- write_pred(writer, flags, predicate);
+ TRY(st, write_pred(writer, flags, predicate));
}
- write_node(writer, object, SERD_OBJECT, flags);
+ TRY(st, write_node(writer, object, SERD_OBJECT, flags));
}
// Push context for list or anonymous subject if necessary
@@ -1023,7 +1090,7 @@ serd_writer_write_statement(SerdWriter* writer,
return st;
}
-static SerdStatus
+SERD_WARN_UNUSED_RESULT static SerdStatus
serd_writer_end_anon(SerdWriter* writer, const SerdNode* node)
{
if (writer->syntax == SERD_NTRIPLES || writer->syntax == SERD_NQUADS) {
@@ -1037,7 +1104,7 @@ serd_writer_end_anon(SerdWriter* writer, const SerdNode* node)
serd_node_string(node));
}
- write_sep(writer, writer->context.flags, SEP_ANON_END);
+ SerdStatus st = write_sep(writer, writer->context.flags, SEP_ANON_END);
pop_context(writer);
if (writer->context.predicate &&
@@ -1046,7 +1113,7 @@ serd_writer_end_anon(SerdWriter* writer, const SerdNode* node)
memset(writer->context.predicate, 0, sizeof(SerdNode));
}
- return SERD_SUCCESS;
+ return st;
}
static SerdStatus
@@ -1071,12 +1138,13 @@ serd_writer_on_event(SerdWriter* writer, const SerdEvent* event)
SerdStatus
serd_writer_finish(SerdWriter* writer)
{
+ SerdStatus st = SERD_SUCCESS;
if (ctx(writer, SERD_SUBJECT)) {
- write_sep(writer, writer->context.flags, SEP_END_S);
+ st = write_sep(writer, writer->context.flags, SEP_END_S);
}
- if (ctx(writer, SERD_GRAPH)) {
- write_sep(writer, writer->context.flags, SEP_GRAPH_END);
+ if (!st && ctx(writer, SERD_GRAPH)) {
+ st = write_sep(writer, writer->context.flags, SEP_GRAPH_END);
}
// Free any lingering contexts in case there was an error
@@ -1088,7 +1156,7 @@ serd_writer_finish(SerdWriter* writer)
writer->indent = 0;
writer->context = WRITE_CONTEXT_NULL;
writer->empty = true;
- return SERD_SUCCESS;
+ return st;
}
SerdWriter*
@@ -1142,20 +1210,24 @@ serd_writer_set_base_uri(SerdWriter* writer, const SerdNode* uri)
return SERD_ERR_BAD_ARG;
}
- if (!serd_env_set_base_uri(writer->env, serd_node_string_view(uri))) {
+ SerdStatus st =
+ serd_env_set_base_uri(writer->env, serd_node_string_view(uri));
+
+ if (!st) {
if (writer->syntax == SERD_TURTLE || writer->syntax == SERD_TRIG) {
if (ctx(writer, SERD_GRAPH) || ctx(writer, SERD_SUBJECT)) {
- sink(" .\n\n", 4, writer);
+ TRY(st, esink(" .\n\n", 4, writer));
reset_context(writer, true);
}
- sink("@base <", 7, writer);
- sink(serd_node_string(uri), uri->n_bytes, writer);
- sink("> .\n", 4, writer);
+ TRY(st, esink("@base <", 7, writer));
+ TRY(st, esink(serd_node_string(uri), uri->n_bytes, writer));
+ TRY(st, esink("> .\n", 4, writer));
}
writer->indent = 0;
- return reset_context(writer, true);
+ reset_context(writer, true);
}
- return SERD_ERR_UNKNOWN;
+
+ return st;
}
SerdStatus
@@ -1181,23 +1253,28 @@ serd_writer_set_prefix(SerdWriter* writer,
return SERD_ERR_BAD_ARG;
}
- if (!serd_env_set_prefix(
- writer->env, serd_node_string_view(name), serd_node_string_view(uri))) {
+ SerdStatus st = serd_env_set_prefix(
+ writer->env, serd_node_string_view(name), serd_node_string_view(uri));
+
+ if (!st) {
if (writer->syntax == SERD_TURTLE || writer->syntax == SERD_TRIG) {
if (ctx(writer, SERD_GRAPH) || ctx(writer, SERD_SUBJECT)) {
- sink(" .\n\n", 4, writer);
+ TRY(st, esink(" .\n\n", 4, writer));
reset_context(writer, true);
}
- sink("@prefix ", 8, writer);
- sink(serd_node_string(name), name->n_bytes, writer);
- sink(": <", 3, writer);
- write_uri_from_node(writer, uri);
- sink("> .\n", 4, writer);
+
+ TRY(st, esink("@prefix ", 8, writer));
+ TRY(st, esink(serd_node_string(name), name->n_bytes, writer));
+ TRY(st, esink(": <", 3, writer));
+ TRY(st, write_uri_from_node(writer, uri));
+ TRY(st, esink("> .\n", 4, writer));
}
+
writer->indent = 0;
- return reset_context(writer, true);
+ reset_context(writer, true);
}
- return SERD_ERR_UNKNOWN;
+
+ return st;
}
void
diff --git a/test/test_reader_writer.c b/test/test_reader_writer.c
index 8cbce04e..fec09ff8 100644
--- a/test/test_reader_writer.c
+++ b/test/test_reader_writer.c
@@ -145,6 +145,42 @@ test_get_blank(void)
return 0;
}
+static int
+test_strict_write(void)
+{
+ SerdWorld* world = serd_world_new();
+ const char* path = "serd_strict_write_test.ttl";
+ FILE* fd = fopen(path, "wb");
+ SerdEnv* env = serd_env_new(SERD_EMPTY_STRING());
+ SerdWriter* writer = serd_writer_new(
+ world, SERD_TURTLE, SERD_WRITE_STRICT, env, (SerdWriteFunc)fwrite, fd);
+
+ assert(fd);
+ assert(writer);
+
+ const SerdSink* sink = serd_writer_sink(writer);
+ const uint8_t bad_str[] = {0xFF, 0x90, 'h', 'i', 0};
+ const SerdStringView bad_view = {(const char*)bad_str, 4};
+
+ SerdNode* s = serd_new_uri(SERD_STATIC_STRING("http://example.org/s"));
+ SerdNode* p = serd_new_uri(SERD_STATIC_STRING("http://example.org/s"));
+ SerdNode* bad_lit = serd_new_string(bad_view);
+ SerdNode* bad_uri = serd_new_uri(bad_view);
+
+ assert(serd_sink_write(sink, 0, s, p, bad_lit, 0) == SERD_ERR_BAD_TEXT);
+ assert(serd_sink_write(sink, 0, s, p, bad_uri, 0) == SERD_ERR_BAD_TEXT);
+ serd_node_free(bad_uri);
+ serd_node_free(bad_lit);
+ serd_node_free(s);
+ serd_node_free(p);
+
+ serd_writer_free(writer);
+ serd_env_free(env);
+ fclose(fd);
+ serd_world_free(world);
+ return 0;
+}
+
static void
test_read_string(void)
{
@@ -361,6 +397,7 @@ main(void)
test_read_chunks();
test_read_string();
test_get_blank();
+ test_strict_write();
const char* const path = "serd_test.ttl";
test_writer(path);
diff --git a/test/test_string.c b/test/test_string.c
index 472b464a..2456e17a 100644
--- a/test/test_string.c
+++ b/test/test_string.c
@@ -39,7 +39,7 @@ test_strerror(void)
{
const char* msg = serd_strerror(SERD_SUCCESS);
assert(!strcmp(msg, "Success"));
- for (int i = SERD_FAILURE; i <= SERD_ERR_NO_DATA; ++i) {
+ for (int i = SERD_FAILURE; i <= SERD_ERR_BAD_WRITE; ++i) {
msg = serd_strerror((SerdStatus)i);
assert(strcmp(msg, "Success"));
}