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); + } } }