diff options
author | David Robillard <d@drobilla.net> | 2019-04-28 16:36:02 +0200 |
---|---|---|
committer | David Robillard <d@drobilla.net> | 2021-03-08 23:23:05 -0500 |
commit | 01daa5914169d57a6a6c4925d3f8d76db80a2bd8 (patch) | |
tree | 468a3371fe17adc082e93f7894147c5d67ed47fe | |
parent | 36e2f27502524155e6475a75ffcab4999fce166a (diff) | |
download | serd-01daa5914169d57a6a6c4925d3f8d76db80a2bd8.tar.gz serd-01daa5914169d57a6a6c4925d3f8d76db80a2bd8.tar.bz2 serd-01daa5914169d57a6a6c4925d3f8d76db80a2bd8.zip |
Report writer errors and add strict write mode
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | include/serd/serd.h | 7 | ||||
-rw-r--r-- | src/serdi.c | 3 | ||||
-rw-r--r-- | src/string.c | 4 | ||||
-rw-r--r-- | src/writer.c | 429 | ||||
-rw-r--r-- | test/test_reader_writer.c | 37 | ||||
-rw-r--r-- | test/test_string.c | 2 |
7 files changed, 303 insertions, 180 deletions
@@ -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")); } |