diff --git a/doxygen/lang/900_release_notes.dox.tmpl b/doxygen/lang/900_release_notes.dox.tmpl
index a0794a4dc6..e056ee1c7d 100644
--- a/doxygen/lang/900_release_notes.dox.tmpl
+++ b/doxygen/lang/900_release_notes.dox.tmpl
@@ -8,13 +8,23 @@
- DebugUtil module
- fixed bugs in log handling
(issue 4635)
- - Schema module
- - allow the user to determine if a schema alignment operation was a first-time creation or an update
- (issue 4637)
+ - RestHandler module
+ - implemented the @ref RestHandler::RestHandler::errorResponseHeaders() to allow subclasses to return custom
+ headers in error responses, such as when a request cannot be deserialized
+ (issue 4646)
- RestSchemaValidator module
+ - REST handlers must throw a \c DESERIALIZATION-ERROR when deserialization errors occur, so that a
+ 400 Bad Request response is returned
+ (issue 4647)
- allow for the client's time zone locale to be set for data serialization / deserialization
(issue 4639)
+ - Schema module
+ - allow the user to determine if a schema alignment operation was a first-time creation or an update
+ (issue 4637)
- Swagger module
+ - REST handlers must throw a \c DESERIALIZATION-ERROR when deserialization errors occur, so that a
+ 400 Bad Request response is returned
+ (issue 4647)
- do not reserialize already deserialized data when processing responses
(issue 4640)
- allow for the client's time zone locale to be set for data serialization / deserialization
diff --git a/examples/test/qlib/RestHandler/RestHandler.qtest b/examples/test/qlib/RestHandler/RestHandler.qtest
index 3da99f726b..c5e2c1acea 100755
--- a/examples/test/qlib/RestHandler/RestHandler.qtest
+++ b/examples/test/qlib/RestHandler/RestHandler.qtest
@@ -146,6 +146,12 @@ class MyRestHandler inherits RestHandler {
hash getAction(hash cx, *hash ah) {
return RestHandler::makeResponse(200, exists ah.action);
}
+
+ private *hash errorResponseHeaders() {
+ return {
+ "Error": "True",
+ };
+ }
}
class SwaggerRestHandler inherits RestHandler {
@@ -318,6 +324,7 @@ public class RestHandlerTest inherits QUnit::Test {
}
constructor() : Test("RestHandlerTest", "1.0") {
+ addTestCase("test bad request", \badRequestTest());
addTestCase("Test direct interface", \directTest());
addTestCase("Test external serialization", \serializationTest());
addTestCase("Test xml", \xmlTest());
@@ -364,6 +371,30 @@ public class RestHandlerTest inherits QUnit::Test {
delete mServer;
}
+ badRequestTest() {
+ HTTPClient client({"url": mClient.getURL()});
+
+ hash info;
+ on_error printf("info: %N\n", info);
+
+ string body = make_yaml(LargerHashValue);
+ hash hdr = {
+ "Content-Type": MimeTypeYaml,
+ "Accept": MimeTypeYaml,
+ };
+ hash h = client.send(body, "PUT", "test?action=echo", hdr, False, \info);
+ assertEq(body, h.body);
+
+ hdr = {
+ "Content-Type": MimeTypeJson,
+ "Accept": MimeTypeJson,
+ };
+ assertThrows("HTTP-CLIENT-RECEIVE-ERROR", \client.send(),
+ ("[x x]", "PUT", "test?action=echo", hdr, False, \info));
+ assertEq(400, info."response-headers".status_code);
+ assertEq("True", info."response-headers".error);
+ }
+
directTest() {
# test direct interface
auto val = mHandler.handleExternalRequest("GET", "test?action=echo", LargerHashValue);
diff --git a/include/qore/intern/qore_program_private.h b/include/qore/intern/qore_program_private.h
index 768b6357e2..3eb22f9bd6 100644
--- a/include/qore/intern/qore_program_private.h
+++ b/include/qore/intern/qore_program_private.h
@@ -205,71 +205,71 @@ struct ThreadLocalProgramData {
*/
/**
- * Data local for each program and thread. dbgXXX function are called from
- * AbstractStatement places when particular action related to debugging is taken.
- * xsink is passed as debugger can raise exception to be passed to program.
- * When dbgXXX function is executed then runState is tested unless is DBG_RS_STOPPED
- * then is set to DBG_RS_STOPPED. It's simple lock and debugging is disabled
- * till returns from this event handler. To be precise it should be
- * locked by an atomic lock but it is good enough not to break performance.
- */
+ Data local for each program and thread. dbgXXX function are called from
+ AbstractStatement places when particular action related to debugging is taken.
+ xsink is passed as debugger can raise exception to be passed to program.
+ When dbgXXX function is executed then runState is tested unless is DBG_RS_STOPPED
+ then is set to DBG_RS_STOPPED. It's simple lock and debugging is disabled
+ till returns from this event handler. To be precise it should be
+ locked by an atomic lock but it is good enough not to break performance.
+ */
/**
- * Executed when starting thread or thread context first time for given program
- */
+ Executed when starting thread or thread context first time for given program
+ */
DLLLOCAL void dbgAttach(ExceptionSink* xsink);
/**
- * Executed from any thread when terminated to detach program
- */
+ Executed from any thread when terminated to detach program
+ */
DLLLOCAL void dbgDetach(ExceptionSink* xsink);
/**
- * Executed every step in BlockStatement.
- * @param statement is step being processed
- * @return 0 as neutral value or RC_RETURN/BREAK/CONTINUE to terminate block
- */
+ Executed every step in BlockStatement.
+ @param statement is step being processed
+ @return 0 as neutral value or RC_RETURN/BREAK/CONTINUE to terminate block
+ */
DLLLOCAL int dbgStep(const StatementBlock* blockStatement, const AbstractStatement* statement, ExceptionSink* xsink);
/**
- * Executed when a function is entered. If step-over is requested then flag is cleared not to break
- */
+ Executed when a function is entered. If step-over is requested then flag is cleared not to break
+ */
DLLLOCAL void dbgFunctionEnter(const StatementBlock* statement, ExceptionSink* xsink);
/**
- * Executed when a function is exited.
- */
+ Executed when a function is exited.
+ */
DLLLOCAL void dbgFunctionExit(const StatementBlock* statement, QoreValue& returnValue, ExceptionSink* xsink);
/**
- * Executed when an exception is raised.
- */
+ Executed when an exception is raised.
+ */
DLLLOCAL void dbgException(const AbstractStatement* statement, ExceptionSink* xsink);
/**
- * Executed when a thread or program is exited.
- */
+ Executed when a thread or program is exited.
+ */
DLLLOCAL void dbgExit(const StatementBlock* statement, QoreValue& returnValue, ExceptionSink* xsink);
/**
- * Executed from any thread to break running program
- */
+ Executed from any thread to break running program
+ */
DLLLOCAL void dbgBreak() {
printd(5, "ThreadLocalProgramData::dbgBreak(), this: %p\n", this);
breakFlag = true;
}
/**
- * Executed from any thread to set pending attach flag
- */
+ Executed from any thread to set pending attach flag
+ */
DLLLOCAL void dbgPendingAttach() {
printd(5, "ThreadLocalProgramData::dbgPendingAttach(), this: %p\n", this);
attachFlag = 1;
}
/**
- * Executed from any thread to set pending detach flag
- */
+ Executed from any thread to set pending detach flag
+ */
DLLLOCAL void dbgPendingDetach() {
printd(5, "ThreadLocalProgramData::dbgPendingDetach(), this: %p\n", this);
attachFlag = -1;
}
/**
- * Check if attached to debugger
- */
+ Check if attached to debugger
+ */
DLLLOCAL bool dbgIsAttached() {
return /*runState != DBG_RS_STOPPED &&*/ runState != DBG_RS_DETACH;
}
@@ -328,11 +328,11 @@ class qore_program_private_base {
// for the thread counter, used only with plock
QoreCondition pcond;
- ptid_map_t tidmap; // map of tids -> thread count in program object
- unsigned thread_count; // number of threads currently running in this Program
- unsigned thread_waiting; // number of threads waiting on all threads to terminate or parsing to complete
- unsigned parse_count = 0; // recursive parse count
- int parse_tid = -1; // thread with the parse lock
+ ptid_map_t tidmap; // map of tids -> thread count in program object
+ unsigned thread_count = 0; // number of threads currently running in this Program
+ unsigned thread_waiting = 0; // number of threads waiting on all threads to terminate or parsing to complete
+ unsigned parse_count = 0; // recursive parse count
+ int parse_tid = -1; // thread with the parse lock
// file name and unique string storage
cstr_vector_t str_vec;
@@ -363,9 +363,11 @@ class qore_program_private_base {
// weak reference dependency counter, when this hits zero, the object is deleted
QoreReferenceCounter dc;
- ExceptionSink* parseSink, *warnSink, *pendingParseSink;
- RootQoreNamespace* RootNS;
- QoreNamespace* QoreNS;
+ ExceptionSink* parseSink = nullptr,
+ * warnSink = nullptr,
+ * pendingParseSink = nullptr;
+ RootQoreNamespace* RootNS = nullptr;
+ QoreNamespace* QoreNS = nullptr;
// top level statements
TopLevelStatementBlock sb;
@@ -388,10 +390,10 @@ class qore_program_private_base {
q_exp_set_t exp_set;
q_exp_t new_expression = nullptr;
- int tclear; // clearing thread-local variables in progress? if so, this is the TID
+ int tclear = 0; // clearing thread-local variables in progress? if so, this is the TID
- int exceptions_raised,
- ptid; // TID of thread destroying the program's private data
+ int exceptions_raised = 0,
+ ptid = 0; // TID of thread destroying the program's private data
ParseWarnOptions pwo;
@@ -429,27 +431,29 @@ class qore_program_private_base {
QoreProgram* pgm;
DLLLOCAL qore_program_private_base(QoreProgram* n_pgm, int64 n_parse_options, QoreProgram* p_pgm = nullptr)
- : thread_count(0), thread_waiting(0), plock(&ma_recursive), parseSink(nullptr), warnSink(nullptr),
- pendingParseSink(nullptr), RootNS(nullptr), QoreNS(nullptr),
+ : plock(&ma_recursive),
sb(this),
- only_first_except(false), po_locked(false), po_allow_restrict(true), exec_class(false), base_object(false),
+ only_first_except(false),
+ po_locked(false),
+ po_allow_restrict(true),
+ exec_class(false),
+ base_object(false),
requires_exception(false),
parsing_done(false),
parsing_in_progress(false),
ns_const(false),
ns_vars(false),
expression_mode(false),
- tclear(0),
- exceptions_raised(0), ptid(0), pwo(n_parse_options), dom(0), pend_dom(0), thread_local_storage(nullptr), twaiting(0),
+ pwo(n_parse_options), dom(0), pend_dom(0), thread_local_storage(nullptr), twaiting(0),
thr_init(nullptr), pgm(n_pgm) {
printd(QPP_DBG_LVL, "qore_program_private_base::qore_program_private_base() this: %p pgm: %p po: " QLLD "\n", this, pgm, n_parse_options);
// must set priv before calling setParent()
pgm->priv = (qore_program_private*)this;
- if (p_pgm)
+ if (p_pgm) {
setParent(p_pgm, n_parse_options);
- else {
+ } else {
TZ = QTZM.getLocalZoneInfo();
newProgram();
}
@@ -662,7 +666,8 @@ class qore_program_private : public qore_program_private_base {
AutoLocker al(plock);
if (ptid) {
- xsink->raiseException("PROGRAM-ERROR", "the Program accessed has already been deleted and therefore no new threads can be started in it");
+ xsink->raiseException("PROGRAM-ERROR", "the Program accessed has already been deleted and therefore no "
+ "new threads can be started in it");
return -1;
}
@@ -692,7 +697,8 @@ class qore_program_private : public qore_program_private_base {
/*
DLLLOCAL int checkValid(ExceptionSink* xsink) {
if (ptid && ptid != q_gettid()) {
- xsink->raiseException("PROGRAM-ERROR", "the Program accessed has already been deleted and therefore cannot be accessed at runtime");
+ xsink->raiseException("PROGRAM-ERROR", "the Program accessed has already been deleted and therefore "
+ "cannot be accessed at runtime");
return -1;
}
return 0;
@@ -707,11 +713,13 @@ class qore_program_private : public qore_program_private_base {
AutoLocker al(plock);
if (ptid && ptid != tid) {
- xsink->raiseException("PROGRAM-ERROR", "the Program accessed has already been deleted and therefore cannot be accessed at runtime");
+ xsink->raiseException("PROGRAM-ERROR", "the Program accessed has already been deleted and therefore "
+ "cannot be accessed at runtime");
return -1;
}
if (parsing_in_progress) {
- xsink->raiseException("PROGRAM-ERROR", "the Program accessed is currently undergoing parsing and cannot be accessed at runtime");
+ xsink->raiseException("PROGRAM-ERROR", "the Program accessed is currently undergoing parsing and cannot "
+ "be accessed at runtime");
}
++tidmap[tid];
@@ -727,10 +735,12 @@ class qore_program_private : public qore_program_private_base {
AutoLocker al(plock);
if (ptid && ptid != tid) {
- throw QoreStandardException("PROGRAM-ERROR", "the Program accessed has already been deleted and therefore cannot be accessed at runtime");
+ throw QoreStandardException("PROGRAM-ERROR", "the Program accessed has already been deleted and "
+ "therefore cannot be accessed at runtime");
}
if (parsing_in_progress) {
- throw QoreStandardException("PROGRAM-ERROR", "the Program accessed is currently undergoing parsing and cannot be accessed at runtime");
+ throw QoreStandardException("PROGRAM-ERROR", "the Program accessed is currently undergoing parsing and "
+ "cannot be accessed at runtime");
}
++tidmap[tid];
@@ -773,9 +783,10 @@ class qore_program_private : public qore_program_private_base {
}
if (ptid && ptid != q_gettid()) {
- if (xsink)
- xsink->raiseException("PROGRAM-ERROR", "the Program accessed has already been deleted and " \
+ if (xsink) {
+ xsink->raiseException("PROGRAM-ERROR", "the Program accessed has already been deleted and "
"therefore cannot be accessed");
+ }
return -1;
}
@@ -1153,8 +1164,68 @@ class qore_program_private : public qore_program_private_base {
warnSink = nullptr;
}
+#if 0
+ // for REPL support
+ // FIXME: first parse rollback needs to be implemented
+ DLLLOCAL int parseStatement(const QoreString& str, const QoreString& lstr, ExceptionSink* xsink,
+ ExceptionSink* wS = nullptr, int wm = 0, const QoreString* source = nullptr, int offset = 0) {
+ assert(xsink);
+ if (!str.strlen()) {
+ xsink->raiseException("STATEMENT-ERROR", "the statement cannot be empty");
+ return -1;
+ }
+
+ // ensure code string has correct character set encoding
+ TempEncodingHelper tstr(str, QCS_DEFAULT, xsink);
+ if (*xsink) {
+ return -1;
+ }
+
+ // ensure label string has correct character set encoding
+ TempEncodingHelper tlstr(lstr, QCS_DEFAULT, xsink);
+ if (*xsink) {
+ return -1;
+ }
+
+ TempEncodingHelper src;
+ if (source && !source->empty() && !src.set(source, QCS_DEFAULT, xsink)) {
+ return -1;
+ }
+
+ return parseStatement(tstr->c_str(), tlstr->c_str(), xsink, wS, wm, source ? src->c_str() : nullptr, offset);
+ }
+
+ DLLLOCAL int parseStatement(const char* code, const char* label, ExceptionSink* xsink, ExceptionSink* wS,
+ int wm, const char* orig_src = nullptr, int offset = 0) {
+ //printd(5, "qore_program_private::parseStatement(%s) pgm: %p po: %lld\n", label, pgm, pwo.parse_options);
+
+ assert(code && code[0]);
+ assert(xsink);
+
+ ProgramRuntimeParseCommitContextHelper pch(xsink, pgm);
+ if (*xsink) {
+ return -1;
+ }
+
+ startParsing(xsink, wS, wm);
+
+ // parse text given
+ if (!internParsePending(xsink, code, label, orig_src, offset, false)) {
+ internParseCommit(false); // finalize parsing, back out or commit all changes
+ } else {
+ parsing_in_progress = false;
+ }
+
+#ifdef DEBUG
+ parseSink = nullptr;
+#endif
+ warnSink = nullptr;
+ return *xsink ? -1 : 0;
+ }
+#endif
+
DLLLOCAL q_exp_t parseExpression(const QoreString& str, const QoreString& lstr, ExceptionSink* xsink,
- ExceptionSink* wS = nullptr, int wm = 0, const QoreString* source = nullptr, int offset = 0) {
+ ExceptionSink* wS = nullptr, int wm = 0, const QoreString* source = nullptr, int offset = 0) {
assert(xsink);
if (!str.strlen()) {
xsink->raiseException("EXPRESSION-ERROR", "the expression cannot be empty");
@@ -1178,8 +1249,9 @@ class qore_program_private : public qore_program_private_base {
return parseExpression(tstr->c_str(), tlstr->c_str(), xsink, wS, wm, source ? src->c_str() : nullptr, offset);
}
- DLLLOCAL q_exp_t parseExpression(const char* code, const char* label, ExceptionSink* xsink, ExceptionSink* wS, int wm, const char* orig_src = nullptr, int offset = 0) {
- //printd(5, "qore_program_private::parse(%s) pgm: %p po: %lld\n", label, pgm, pwo.parse_options);
+ DLLLOCAL q_exp_t parseExpression(const char* code, const char* label, ExceptionSink* xsink, ExceptionSink* wS,
+ int wm, const char* orig_src = nullptr, int offset = 0) {
+ //printd(5, "qore_program_private::parseExpression(%s) pgm: %p po: %lld\n", label, pgm, pwo.parse_options);
assert(code && code[0]);
assert(xsink);
@@ -1773,7 +1845,8 @@ class qore_program_private : public qore_program_private_base {
DLLLOCAL void addStatement(AbstractStatement* s);
- DLLLOCAL q_exp_t createExpression(const QoreStringNode& source, const QoreStringNode& label, ExceptionSink* xsink) {
+ DLLLOCAL q_exp_t createExpression(const QoreStringNode& source, const QoreStringNode& label,
+ ExceptionSink* xsink) {
return parseExpression(source, label, xsink);
}
diff --git a/lib/QoreProgram.cpp b/lib/QoreProgram.cpp
index c43da6efe6..039f0de1d5 100644
--- a/lib/QoreProgram.cpp
+++ b/lib/QoreProgram.cpp
@@ -486,7 +486,10 @@ void qore_program_private_base::newProgram() {
}
void qore_program_private_base::setParent(QoreProgram* p_pgm, int64 n_parse_options) {
- printd(5, "qore_program_private_base::setParent() this: %p parent: %p (parent lvl: %p) this: %p (this pgm: %p) parent po: %lld new po: %lld parent no_child_po_restrictions: %d\n", this, p_pgm, p_pgm->priv->sb.getLVList(), this, pgm, p_pgm->priv->pwo.parse_options, n_parse_options, p_pgm->priv->pwo.parse_options & PO_NO_CHILD_PO_RESTRICTIONS);
+ printd(5, "qore_program_private_base::setParent() this: %p parent: %p (parent lvl: %p) this: %p (this pgm: %p) "
+ "parent po: %lld new po: %lld parent no_child_po_restrictions: %d\n", this, p_pgm,
+ p_pgm->priv->sb.getLVList(), this, pgm, p_pgm->priv->pwo.parse_options, n_parse_options,
+ p_pgm->priv->pwo.parse_options & PO_NO_CHILD_PO_RESTRICTIONS);
TZ = p_pgm->currentTZ();
@@ -575,15 +578,19 @@ void qore_program_private::internParseRollback(ExceptionSink* xsink) {
void qore_program_private::waitForTerminationAndClear(ExceptionSink* xsink) {
// detach itself from debug
if (dpgm) {
- // if the object is being destroyed when thread is terminating via a deref then we send sync detach event to this thread
- // the thread can be stopped.
+ // if the object is being destroyed when thread is terminating via a deref then we send sync detach event to
+ // this thread the thread can be stopped.
ThreadLocalProgramData *tlpd = get_thread_local_program_data();
if (tlpd) {
tlpd->dbgDetach(xsink);
}
dpgm->removeProgram(pgm);
}
- debug_program_counter.waitForZero(xsink, 0); // it is probably obsolete as the next waiting for thread termination will wait for the same threads as well
+
+ // this is probably not necessary, as the next waiting for thread
+ // termination will wait for the same threads as well
+ debug_program_counter.waitForZero(xsink, 0);
+
// we only clear the internal data structures once
bool clr = false;
{
@@ -596,7 +603,8 @@ void qore_program_private::waitForTerminationAndClear(ExceptionSink* xsink) {
if (!ns_const) {
l = new QoreListNode(autoTypeInfo);
qore_root_ns_private::clearConstants(*RootNS, **l);
- //printd(5, "qore_program_private::waitForTerminationAndClear() this: %p cleared constants\n", this);
+ //printd(5, "qore_program_private::waitForTerminationAndClear() this: %p cleared constants\n",
+ // this);
ns_const = true;
}
// mark the program so that only code from this thread can run during data destruction
@@ -783,11 +791,13 @@ void qore_program_private::addStatement(AbstractStatement* s) {
return;
}
- sb.addStatement(s);
-
// see if top level statements are allowed
- if (pwo.parse_options & PO_NO_TOP_LEVEL_STATEMENTS && !s->isDeclaration())
+ if (pwo.parse_options & PO_NO_TOP_LEVEL_STATEMENTS && !s->isDeclaration()) {
parse_error(*s->loc, "illegal top-level statement (conflicts with parse option NO_TOP_LEVEL_STATEMENTS)");
+ delete s;
+ } else {
+ sb.addStatement(s);
+ }
}
void qore_program_private::runtimeImportSystemClassesIntern(const qore_program_private& spgm, ExceptionSink* xsink) {
diff --git a/qlib/RestHandler.qm b/qlib/RestHandler.qm
index c8c8ba924a..52611f354b 100644
--- a/qlib/RestHandler.qm
+++ b/qlib/RestHandler.qm
@@ -42,7 +42,7 @@
%requires reflection
module RestHandler {
- version = "1.6";
+ version = "1.6.1";
desc = "user module for implementing REST services with the Qore HTTP server";
author = "David Nichols ";
url = "http://qore.org";
@@ -374,8 +374,14 @@ Content-Length: 16
@section resthandler_relnotes RestHandler Release Notes
+ @subsection rh_1_6_1 RestHandler v1.6.1
+ - implemented the @ref RestHandler::RestHandler::errorResponseHeaders() to allow subclasses to return custom
+ headers in error responses, such as when a request cannot be deserialized
+ (issue 4646)
+
@subsection rh_1_6 RestHandler v1.6
- - added request arguments to the @ref RestHandler::RestHandler::unknownSubClassError() "RestHandler::unknownSubClassError()"
+ - added request arguments to the
+ @ref RestHandler::RestHandler::unknownSubClassError() "RestHandler::unknownSubClassError()"
and @ref RestHandler::RestHandler::doGetPossibleSubClasses() "RestHandler::doGetPossibleSubClasses()" methods
(issue 4578)
@@ -1250,11 +1256,13 @@ public namespace RestHandler {
|| ex.err == "ENCODING-CONVERSION-ERROR"
|| ex.err == "DESERIALIZATION-ERROR"
|| ex.err == "SCHEMA-VALIDATION-ERROR")
- return AbstractHttpRequestHandler::make400("%s: %s", ex.err, ex.desc);
+ return AbstractHttpRequestHandler::makeResponse(400, sprintf("%s: %s", ex.err, ex.desc),
+ errorResponseHeaders());
if (ex.err == "INVALID-METHOD") {
string ml = (foldl $1 + "," + $2, ex.arg) ?? "";
return AbstractHttpRequestHandler::makeResponse(405, sprintf("HTTP method %y is not supported "
- "with URI path %y; supported methods: %s", hdr.method, path, ml ?* ""), ("Allow": ml));
+ "with URI path %y; supported methods: %s", hdr.method, path, ml ?* ""),
+ errorResponseHeaders() + {"Allow": ml});
}
rethrow;
}
@@ -1406,6 +1414,12 @@ public namespace RestHandler {
return cast>(rv);
}
+ #! Retrieves headers for an error response
+ /** @return the base method returns @ref NOTHING; override to return additional headers for an error response
+ */
+ private *hash errorResponseHeaders() {
+ }
+
#! Dispatches the request and returns the response
private hash dispatchRequest(HttpListenerInterface listener, Socket s,
*list class_list, string method_name, string path, hash cx, *hash args) {
diff --git a/qlib/RestSchemaValidator.qm b/qlib/RestSchemaValidator.qm
index 885b8ea3ee..59e538ae84 100644
--- a/qlib/RestSchemaValidator.qm
+++ b/qlib/RestSchemaValidator.qm
@@ -77,6 +77,9 @@ module RestSchemaValidator {
@section restschemavalidator_relnotes RestSchemaValidator Module Release Notes
@subsection restschemavalidator_2_1.3 RestSchemaValidator v2.1.3
+ - REST handlers must throw a \c DESERIALIZATION-ERROR when deserialization errors occur, so that a
+ 400 Bad Request response is returned
+ (issue 4647)
- allow for the client's time zone locale to be set for data serialization / deserialization
(issue 4639)
@@ -781,9 +784,13 @@ public namespace RestSchemaValidator {
keys DataDeserializationSupport, http_body);
string ds_code = dh.code;
- rv.body = dh.arg
- ? dh.in(http_body, \ds_code)
- : dh.in(http_body);
+ try {
+ rv.body = dh.arg
+ ? dh.in(http_body, \ds_code)
+ : dh.in(http_body);
+ } catch (hash ex) {
+ rethrow "DESERIALIZATION-ERROR", sprintf("%s: %s", ex.err, ex.desc);
+ }
rv.info.code = ds_code;
}
@@ -920,12 +927,16 @@ public namespace RestSchemaValidator {
"body type is %y", ct, response_body.type());
}
- string ds_code = dh.code;
- rv.body = dh.arg
- ? dh.in(response_body, \ds_code)
- : dh.in(response_body);
+ string ds_code = dh."code";
+ try {
+ rv.body = dh.arg
+ ? dh.in(response_body, \ds_code)
+ : dh.in(response_body);
+ } catch (hash ex) {
+ rethrow "DESERIALIZATION-ERROR", sprintf("%s: %s", ex.err, ex.desc);
+ }
- rv.info.code = ds_code;
+ rv.info."code" = ds_code;
}
return rv;
diff --git a/qlib/SqlUtil/SqlUtil.qm b/qlib/SqlUtil/SqlUtil.qm
index 286e225235..affd1a5d05 100644
--- a/qlib/SqlUtil/SqlUtil.qm
+++ b/qlib/SqlUtil/SqlUtil.qm
@@ -69,23 +69,29 @@ module SqlUtil {
All the public symbols in the module are defined in the SqlUtil namespace
Major sections of this documentation:
- - @subpage sql_operations "SQL Operations": working with database data (finding, updating, inserting, deleting, merging data, etc)
- - @subpage schema_management "Schema Management": working schema definitions (creating, modifying, aligning tables, functions, types, triggers, etc)
+ - @subpage sql_operations "SQL Operations": working with database data (finding, updating, inserting, deleting,
+ merging data, etc)
+ - @subpage schema_management "Schema Management": working schema definitions (creating, modifying, aligning
+ tables, functions, types, triggers, etc)
- @subpage dba_management "DBA Management": special database administrator tools
- The %SqlUtil module provides generic functionality and a framework for SQL operations and schema management, and in order
- to use the %SqlUtil module with a particular database, a driver-specific module has to be available as well.
+ The %SqlUtil module provides generic functionality and a framework for SQL operations and schema management, and
+ in order to use the %SqlUtil module with a particular database, a driver-specific module has to be available as
+ well.
Currently the following driver-specific modules are available:
- - FreetdsSqlUtil: for working with MS SQL Server and Sybase databases through the freetds DBI driver
+ - FreetdsSqlUtil: for working with MS SQL Server and Sybase
+ databases through the freetds driver
- MysqlSqlUtil: for working with MySQL databases
- OracleSqlUtil: for working with Oracle databases
- PgsqlSqlUtil: for working with PostgreSQL databases
- Sqlite3SqlUtil: for working with SQLite3 databases
+ - OdbcFirebirdSqlUtil: for working with Firebird databases
+ through the odbc driver
The underlying driver-specific module is automatically loaded and used when required; the classes provided in
- the %SqlUtil module provide a generic API that uses the driver-specific implementations for the underlying driver-specific
- implementation.
+ the %SqlUtil module provide a generic API that uses the driver-specific implementations for the underlying
+ driver-specific implementation.
@section sqlutil_overview Overview of Functionality
diff --git a/qlib/Swagger.qm b/qlib/Swagger.qm
index 35cfc20be1..423a925d42 100644
--- a/qlib/Swagger.qm
+++ b/qlib/Swagger.qm
@@ -151,6 +151,9 @@ RestHandler handler(NOTHING, swagger);
@section swagger_relnotes Swagger Module Release Notes
@subsection swagger_2_0_10 Swagger v2.0.10
+ - REST handlers must throw a \c DESERIALIZATION-ERROR when deserialization errors occur, so that a
+ 400 Bad Request response is returned
+ (issue 4647)
- do not reserialize already deserialized data when processing responses
(issue 4640)
- allow for the client's time zone locale to be set for data serialization / deserialization
@@ -1425,9 +1428,13 @@ public class SwaggerSchema inherits ObjectBase, AbstractRestSchemaValidator {
if (!ds) {
body = http_body;
} else {
- auto any_body = ds(http_body);
- if (exists any_body) {
- body = any_body;
+ try {
+ auto any_body = ds(http_body);
+ if (exists any_body) {
+ body = any_body;
+ }
+ } catch (hash ex) {
+ rethrow "DESERIALIZATION-ERROR", sprintf("%s: %s", ex.err, ex.desc);
}
}
}
@@ -1517,7 +1524,11 @@ public class SwaggerSchema inherits ObjectBase, AbstractRestSchemaValidator {
*code ds = MimeDataTypes{content}.deserialize;
# if the content-type is unknown, then assume it is a type that cannot be deserialized
# the validity of the type is checked below in any case
- rv.body = ds ? ds(response_body) : response_body;
+ try {
+ rv.body = ds ? ds(response_body) : response_body;
+ } catch (hash ex) {
+ rethrow "DESERIALIZATION-ERROR", sprintf("%s: %s", ex.err, ex.desc);
+ }
}
}