Commit 3dbab589e32d3ed5bd98e0634255ba7dfab4c892
1 parent
0ad4e190
Add C API functions for using custom loggers
Expose functions to the C API to create new loggers and to setLogger and getLogger for QPDF and QPDFJob.
Showing
13 changed files
with
163 additions
and
24 deletions
ChangeLog
| 1 | +2022-09-09 Jay Berkenbilt <ejb@ql.org> | ||
| 2 | + | ||
| 3 | + * Expose ability to create custom loggers and to get and set the | ||
| 4 | + logger for QPDF and QPDFJob through the C API. | ||
| 5 | + | ||
| 1 | 2022-09-08 Jay Berkenbilt <ejb@ql.org> | 6 | 2022-09-08 Jay Berkenbilt <ejb@ql.org> |
| 2 | 7 | ||
| 3 | * Added new functions to the C API to support qpdf JSON: | 8 | * Added new functions to the C API to support qpdf JSON: |
TODO
| @@ -8,12 +8,7 @@ Always | @@ -8,12 +8,7 @@ Always | ||
| 8 | Next | 8 | Next |
| 9 | ==== | 9 | ==== |
| 10 | 10 | ||
| 11 | -Pending changes: | ||
| 12 | - | ||
| 13 | -* Consider also exposing a way to set a new logger and to get the | ||
| 14 | - logger from QPDF and QPDFJob in the C API. | ||
| 15 | - | ||
| 16 | -Soon: Break ground on "Document-level work" | 11 | +* Break ground on "Document-level work" |
| 17 | 12 | ||
| 18 | Possible future JSON enhancements | 13 | Possible future JSON enhancements |
| 19 | ================================= | 14 | ================================= |
include/qpdf/qpdf-c.h
| @@ -140,6 +140,7 @@ | @@ -140,6 +140,7 @@ | ||
| 140 | #include <qpdf/Constants.h> | 140 | #include <qpdf/Constants.h> |
| 141 | #include <qpdf/DLL.h> | 141 | #include <qpdf/DLL.h> |
| 142 | #include <qpdf/Types.h> | 142 | #include <qpdf/Types.h> |
| 143 | +#include <qpdf/qpdflogger-c.h> | ||
| 143 | #include <string.h> | 144 | #include <string.h> |
| 144 | 145 | ||
| 145 | #ifdef __cplusplus | 146 | #ifdef __cplusplus |
| @@ -249,6 +250,20 @@ extern "C" { | @@ -249,6 +250,20 @@ extern "C" { | ||
| 249 | QPDF_DLL | 250 | QPDF_DLL |
| 250 | void qpdf_set_suppress_warnings(qpdf_data qpdf, QPDF_BOOL value); | 251 | void qpdf_set_suppress_warnings(qpdf_data qpdf, QPDF_BOOL value); |
| 251 | 252 | ||
| 253 | + /* LOG FUNCTIONS */ | ||
| 254 | + | ||
| 255 | + /* Set or get the current logger. You need to call | ||
| 256 | + * qpdflogger_cleanup on the logger handles when you are done with | ||
| 257 | + * the handles. The underlying logger is cleaned up automatically | ||
| 258 | + * and persists if needed after the logger handle is destroyed. | ||
| 259 | + * See comments in qpdflogger-c.h for details. | ||
| 260 | + */ | ||
| 261 | + | ||
| 262 | + QPDF_DLL | ||
| 263 | + void qpdf_set_logger(qpdf_data qpdf, qpdflogger_handle logger); | ||
| 264 | + QPDF_DLL | ||
| 265 | + qpdflogger_handle qpdf_get_logger(qpdf_data qpdf); | ||
| 266 | + | ||
| 252 | /* CHECK FUNCTIONS */ | 267 | /* CHECK FUNCTIONS */ |
| 253 | 268 | ||
| 254 | /* Attempt to read the entire PDF file to see if there are any | 269 | /* Attempt to read the entire PDF file to see if there are any |
include/qpdf/qpdfjob-c.h
| @@ -32,6 +32,7 @@ | @@ -32,6 +32,7 @@ | ||
| 32 | */ | 32 | */ |
| 33 | 33 | ||
| 34 | #include <qpdf/DLL.h> | 34 | #include <qpdf/DLL.h> |
| 35 | +#include <qpdf/qpdflogger-c.h> | ||
| 35 | #include <string.h> | 36 | #include <string.h> |
| 36 | #ifndef QPDF_NO_WCHAR_T | 37 | #ifndef QPDF_NO_WCHAR_T |
| 37 | # include <wchar.h> | 38 | # include <wchar.h> |
| @@ -92,6 +93,18 @@ extern "C" { | @@ -92,6 +93,18 @@ extern "C" { | ||
| 92 | QPDF_DLL | 93 | QPDF_DLL |
| 93 | void qpdfjob_cleanup(qpdfjob_handle* j); | 94 | void qpdfjob_cleanup(qpdfjob_handle* j); |
| 94 | 95 | ||
| 96 | + /* Set or get the current logger. You need to call | ||
| 97 | + * qpdflogger_cleanup on the logger handles when you are done with | ||
| 98 | + * the handles. The underlying logger is cleaned up automatically | ||
| 99 | + * and persists if needed after the logger handle is destroyed. | ||
| 100 | + * See comments in qpdflogger-c.h for details. | ||
| 101 | + */ | ||
| 102 | + | ||
| 103 | + QPDF_DLL | ||
| 104 | + void qpdfjob_set_logger(qpdfjob_handle j, qpdflogger_handle logger); | ||
| 105 | + QPDF_DLL | ||
| 106 | + qpdflogger_handle qpdfjob_get_logger(qpdfjob_handle j); | ||
| 107 | + | ||
| 95 | /* This function wraps QPDFJob::initializeFromArgv. The return | 108 | /* This function wraps QPDFJob::initializeFromArgv. The return |
| 96 | * value is the same as qpdfjob_run. If this returns an error, it | 109 | * value is the same as qpdfjob_run. If this returns an error, it |
| 97 | * is invalid to call any other functions this job handle. | 110 | * is invalid to call any other functions this job handle. |
include/qpdf/qpdflogger-c.h
| @@ -38,16 +38,28 @@ extern "C" { | @@ -38,16 +38,28 @@ extern "C" { | ||
| 38 | 38 | ||
| 39 | /* To operate on a logger, you need a handle to it. call | 39 | /* To operate on a logger, you need a handle to it. call |
| 40 | * qpdflogger_default_logger to get a handle for the default | 40 | * qpdflogger_default_logger to get a handle for the default |
| 41 | - * logger. The qpdf and qpdfjob functions may offer ways to get | ||
| 42 | - * other logger handles. When you're done with the logger handler, | ||
| 43 | - * call qpdflogger_cleanup. This does not destroy the underlying | ||
| 44 | - * log object. It just cleans up the handle to it. | 41 | + * logger. There are functions in qpdf-c.h and qpdfjob-c.h that |
| 42 | + * also take or return logger handles. When you're done with the | ||
| 43 | + * logger handler, call qpdflogger_cleanup. This cleans up the | ||
| 44 | + * handle but leaves the underlying log object intact. (It uses a | ||
| 45 | + * shared pointer and will be cleaned up automatically when it is | ||
| 46 | + * no longer in use.) That means you can create a logger with | ||
| 47 | + * qpdflogger_create(), pass the logger handle to a function in | ||
| 48 | + * qpdf-c.h or qpdfjob-c.h, and then clean it up, subject to | ||
| 49 | + * constraints imposed by the other function. | ||
| 45 | */ | 50 | */ |
| 46 | 51 | ||
| 47 | typedef struct _qpdflogger_handle* qpdflogger_handle; | 52 | typedef struct _qpdflogger_handle* qpdflogger_handle; |
| 48 | QPDF_DLL | 53 | QPDF_DLL |
| 49 | qpdflogger_handle qpdflogger_default_logger(); | 54 | qpdflogger_handle qpdflogger_default_logger(); |
| 50 | 55 | ||
| 56 | + /* Calling cleanup on the handle returned by qpdflogger_create | ||
| 57 | + * destroys the handle but not the underlying logger. See comments | ||
| 58 | + * above. | ||
| 59 | + */ | ||
| 60 | + QPDF_DLL | ||
| 61 | + qpdflogger_handle qpdflogger_create(); | ||
| 62 | + | ||
| 51 | QPDF_DLL | 63 | QPDF_DLL |
| 52 | void qpdflogger_cleanup(qpdflogger_handle* l); | 64 | void qpdflogger_cleanup(qpdflogger_handle* l); |
| 53 | 65 | ||
| @@ -95,6 +107,10 @@ extern "C" { | @@ -95,6 +107,10 @@ extern "C" { | ||
| 95 | void qpdflogger_save_to_standard_output( | 107 | void qpdflogger_save_to_standard_output( |
| 96 | qpdflogger_handle l, int only_if_not_set); | 108 | qpdflogger_handle l, int only_if_not_set); |
| 97 | 109 | ||
| 110 | + /* For testing */ | ||
| 111 | + QPDF_DLL | ||
| 112 | + int qpdflogger_equal(qpdflogger_handle l1, qpdflogger_handle l2); | ||
| 113 | + | ||
| 98 | #ifdef __cplusplus | 114 | #ifdef __cplusplus |
| 99 | } | 115 | } |
| 100 | #endif | 116 | #endif |
libqpdf/qpdf-c.cc
| @@ -12,6 +12,7 @@ | @@ -12,6 +12,7 @@ | ||
| 12 | #include <qpdf/QPDFWriter.hh> | 12 | #include <qpdf/QPDFWriter.hh> |
| 13 | #include <qpdf/QTC.hh> | 13 | #include <qpdf/QTC.hh> |
| 14 | #include <qpdf/QUtil.hh> | 14 | #include <qpdf/QUtil.hh> |
| 15 | +#include <qpdf/qpdflogger-c_impl.hh> | ||
| 15 | 16 | ||
| 16 | #include <cstring> | 17 | #include <cstring> |
| 17 | #include <functional> | 18 | #include <functional> |
| @@ -280,6 +281,18 @@ qpdf_check_pdf(qpdf_data qpdf) | @@ -280,6 +281,18 @@ qpdf_check_pdf(qpdf_data qpdf) | ||
| 280 | } | 281 | } |
| 281 | 282 | ||
| 282 | void | 283 | void |
| 284 | +qpdf_set_logger(qpdf_data qpdf, qpdflogger_handle logger) | ||
| 285 | +{ | ||
| 286 | + qpdf->qpdf->setLogger(logger->l); | ||
| 287 | +} | ||
| 288 | + | ||
| 289 | +qpdflogger_handle | ||
| 290 | +qpdf_get_logger(qpdf_data qpdf) | ||
| 291 | +{ | ||
| 292 | + return new _qpdflogger_handle(qpdf->qpdf->getLogger()); | ||
| 293 | +} | ||
| 294 | + | ||
| 295 | +void | ||
| 283 | qpdf_set_suppress_warnings(qpdf_data qpdf, QPDF_BOOL value) | 296 | qpdf_set_suppress_warnings(qpdf_data qpdf, QPDF_BOOL value) |
| 284 | { | 297 | { |
| 285 | QTC::TC("qpdf", "qpdf-c called qpdf_set_suppress_warnings"); | 298 | QTC::TC("qpdf", "qpdf-c called qpdf_set_suppress_warnings"); |
libqpdf/qpdf/qpdflogger-c_impl.hh
0 โ 100644
libqpdf/qpdfjob-c.cc
| @@ -4,6 +4,7 @@ | @@ -4,6 +4,7 @@ | ||
| 4 | #include <qpdf/QPDFLogger.hh> | 4 | #include <qpdf/QPDFLogger.hh> |
| 5 | #include <qpdf/QPDFUsage.hh> | 5 | #include <qpdf/QPDFUsage.hh> |
| 6 | #include <qpdf/QUtil.hh> | 6 | #include <qpdf/QUtil.hh> |
| 7 | +#include <qpdf/qpdflogger-c_impl.hh> | ||
| 7 | 8 | ||
| 8 | #include <cstdio> | 9 | #include <cstdio> |
| 9 | #include <cstring> | 10 | #include <cstring> |
| @@ -41,6 +42,18 @@ wrap_qpdfjob(qpdfjob_handle j, std::function<int(qpdfjob_handle j)> fn) | @@ -41,6 +42,18 @@ wrap_qpdfjob(qpdfjob_handle j, std::function<int(qpdfjob_handle j)> fn) | ||
| 41 | return QPDFJob::EXIT_ERROR; | 42 | return QPDFJob::EXIT_ERROR; |
| 42 | } | 43 | } |
| 43 | 44 | ||
| 45 | +void | ||
| 46 | +qpdfjob_set_logger(qpdfjob_handle j, qpdflogger_handle logger) | ||
| 47 | +{ | ||
| 48 | + j->j.setLogger(logger->l); | ||
| 49 | +} | ||
| 50 | + | ||
| 51 | +qpdflogger_handle | ||
| 52 | +qpdfjob_get_logger(qpdfjob_handle j) | ||
| 53 | +{ | ||
| 54 | + return new _qpdflogger_handle(j->j.getLogger()); | ||
| 55 | +} | ||
| 56 | + | ||
| 44 | int | 57 | int |
| 45 | qpdfjob_initialize_from_argv(qpdfjob_handle j, char const* const argv[]) | 58 | qpdfjob_initialize_from_argv(qpdfjob_handle j, char const* const argv[]) |
| 46 | { | 59 | { |
libqpdf/qpdflogger-c.cc
| 1 | #include <qpdf/qpdflogger-c.h> | 1 | #include <qpdf/qpdflogger-c.h> |
| 2 | 2 | ||
| 3 | +#include <qpdf/qpdflogger-c_impl.hh> | ||
| 4 | + | ||
| 3 | #include <qpdf/Pipeline.hh> | 5 | #include <qpdf/Pipeline.hh> |
| 4 | #include <qpdf/Pl_Function.hh> | 6 | #include <qpdf/Pl_Function.hh> |
| 5 | #include <qpdf/QIntC.hh> | 7 | #include <qpdf/QIntC.hh> |
| @@ -7,14 +9,6 @@ | @@ -7,14 +9,6 @@ | ||
| 7 | #include <functional> | 9 | #include <functional> |
| 8 | #include <memory> | 10 | #include <memory> |
| 9 | 11 | ||
| 10 | -struct _qpdflogger_handle | ||
| 11 | -{ | ||
| 12 | - _qpdflogger_handle(std::shared_ptr<QPDFLogger> l); | ||
| 13 | - ~_qpdflogger_handle() = default; | ||
| 14 | - | ||
| 15 | - std::shared_ptr<QPDFLogger> l; | ||
| 16 | -}; | ||
| 17 | - | ||
| 18 | _qpdflogger_handle::_qpdflogger_handle(std::shared_ptr<QPDFLogger> l) : | 12 | _qpdflogger_handle::_qpdflogger_handle(std::shared_ptr<QPDFLogger> l) : |
| 19 | l(l) | 13 | l(l) |
| 20 | { | 14 | { |
| @@ -26,6 +20,12 @@ qpdflogger_default_logger() | @@ -26,6 +20,12 @@ qpdflogger_default_logger() | ||
| 26 | return new _qpdflogger_handle(QPDFLogger::defaultLogger()); | 20 | return new _qpdflogger_handle(QPDFLogger::defaultLogger()); |
| 27 | } | 21 | } |
| 28 | 22 | ||
| 23 | +qpdflogger_handle | ||
| 24 | +qpdflogger_create() | ||
| 25 | +{ | ||
| 26 | + return new _qpdflogger_handle(QPDFLogger::create()); | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | void | 29 | void |
| 30 | qpdflogger_cleanup(qpdflogger_handle* l) | 30 | qpdflogger_cleanup(qpdflogger_handle* l) |
| 31 | { | 31 | { |
| @@ -125,3 +125,9 @@ qpdflogger_save_to_standard_output(qpdflogger_handle l, int only_if_not_set) | @@ -125,3 +125,9 @@ qpdflogger_save_to_standard_output(qpdflogger_handle l, int only_if_not_set) | ||
| 125 | qpdflogger_set_save( | 125 | qpdflogger_set_save( |
| 126 | l, qpdf_log_dest_stdout, nullptr, nullptr, only_if_not_set); | 126 | l, qpdf_log_dest_stdout, nullptr, nullptr, only_if_not_set); |
| 127 | } | 127 | } |
| 128 | + | ||
| 129 | +int | ||
| 130 | +qpdflogger_equal(qpdflogger_handle l1, qpdflogger_handle l2) | ||
| 131 | +{ | ||
| 132 | + return l1->l.get() == l2->l.get(); | ||
| 133 | +} |
qpdf/qpdf-ctest.c
| @@ -140,6 +140,15 @@ write_to_file(char const* data, size_t size, void* udata) | @@ -140,6 +140,15 @@ write_to_file(char const* data, size_t size, void* udata) | ||
| 140 | return fwrite(data, 1, size, f) != size; | 140 | return fwrite(data, 1, size, f) != size; |
| 141 | } | 141 | } |
| 142 | 142 | ||
| 143 | +static int | ||
| 144 | +custom_log(char const* data, size_t size, void* udata) | ||
| 145 | +{ | ||
| 146 | + fprintf(stderr, "|custom|"); | ||
| 147 | + fwrite(data, 1, size, stderr); | ||
| 148 | + fflush(stderr); | ||
| 149 | + return 0; | ||
| 150 | +} | ||
| 151 | + | ||
| 143 | static void | 152 | static void |
| 144 | test01( | 153 | test01( |
| 145 | char const* infile, | 154 | char const* infile, |
| @@ -583,6 +592,20 @@ test23( | @@ -583,6 +592,20 @@ test23( | ||
| 583 | char const* outfile, | 592 | char const* outfile, |
| 584 | char const* xarg) | 593 | char const* xarg) |
| 585 | { | 594 | { |
| 595 | + /* Test check and also exercise custom logger */ | ||
| 596 | + qpdflogger_handle l1 = qpdf_get_logger(qpdf); | ||
| 597 | + qpdflogger_handle l2 = qpdflogger_default_logger(); | ||
| 598 | + assert(qpdflogger_equal(l1, l2)); | ||
| 599 | + qpdflogger_cleanup(&l1); | ||
| 600 | + qpdflogger_cleanup(&l2); | ||
| 601 | + qpdflogger_handle l = qpdflogger_create(); | ||
| 602 | + qpdflogger_set_warn(l, qpdf_log_dest_custom, custom_log, NULL); | ||
| 603 | + qpdf_set_logger(qpdf, l); | ||
| 604 | + qpdflogger_handle l3 = qpdf_get_logger(qpdf); | ||
| 605 | + assert(qpdflogger_equal(l, l3)); | ||
| 606 | + qpdflogger_cleanup(&l); | ||
| 607 | + qpdflogger_cleanup(&l3); | ||
| 608 | + | ||
| 586 | QPDF_ERROR_CODE status = 0; | 609 | QPDF_ERROR_CODE status = 0; |
| 587 | qpdf_read(qpdf, infile, password); | 610 | qpdf_read(qpdf, infile, password); |
| 588 | status = qpdf_check_pdf(qpdf); | 611 | status = qpdf_check_pdf(qpdf); |
qpdf/qpdfjob-ctest.c
| @@ -26,6 +26,15 @@ custom_progress(int progress, void* data) | @@ -26,6 +26,15 @@ custom_progress(int progress, void* data) | ||
| 26 | printf("%s: write progress: %d%%\n", (char const*)data, progress); | 26 | printf("%s: write progress: %d%%\n", (char const*)data, progress); |
| 27 | } | 27 | } |
| 28 | 28 | ||
| 29 | +static int | ||
| 30 | +custom_log(char const* data, size_t size, void* udata) | ||
| 31 | +{ | ||
| 32 | + fprintf(stderr, "|custom|"); | ||
| 33 | + fwrite(data, 1, size, stderr); | ||
| 34 | + fflush(stderr); | ||
| 35 | + return 0; | ||
| 36 | +} | ||
| 37 | + | ||
| 29 | static void | 38 | static void |
| 30 | run_tests() | 39 | run_tests() |
| 31 | { | 40 | { |
| @@ -55,6 +64,7 @@ run_tests() | @@ -55,6 +64,7 @@ run_tests() | ||
| 55 | \"objectStreams\": \"generate\"\n\ | 64 | \"objectStreams\": \"generate\"\n\ |
| 56 | }") == 0); | 65 | }") == 0); |
| 57 | printf("json test passed\n"); | 66 | printf("json test passed\n"); |
| 67 | + fflush(stdout); | ||
| 58 | 68 | ||
| 59 | assert(qpdfjob_run_from_json("{\n\ | 69 | assert(qpdfjob_run_from_json("{\n\ |
| 60 | \"inputFile\": \"xref-with-short-size.pdf\",\n\ | 70 | \"inputFile\": \"xref-with-short-size.pdf\",\n\ |
| @@ -64,10 +74,28 @@ run_tests() | @@ -64,10 +74,28 @@ run_tests() | ||
| 64 | \"objectStreams\": \"generate\"\n\ | 74 | \"objectStreams\": \"generate\"\n\ |
| 65 | }") == 3); | 75 | }") == 3); |
| 66 | printf("json warn test passed\n"); | 76 | printf("json warn test passed\n"); |
| 77 | + fflush(stdout); | ||
| 67 | 78 | ||
| 68 | - assert(qpdfjob_run_from_json("{\n\ | 79 | + /* Also exercise custom logger */ |
| 80 | + j = qpdfjob_init(); | ||
| 81 | + qpdflogger_handle l1 = qpdfjob_get_logger(j); | ||
| 82 | + qpdflogger_handle l2 = qpdflogger_default_logger(); | ||
| 83 | + assert(qpdflogger_equal(l1, l2)); | ||
| 84 | + qpdflogger_cleanup(&l1); | ||
| 85 | + qpdflogger_cleanup(&l2); | ||
| 86 | + qpdflogger_handle l = qpdflogger_create(); | ||
| 87 | + qpdflogger_set_error(l, qpdf_log_dest_custom, custom_log, NULL); | ||
| 88 | + qpdfjob_set_logger(j, l); | ||
| 89 | + qpdflogger_handle l3 = qpdfjob_get_logger(j); | ||
| 90 | + assert(qpdflogger_equal(l, l3)); | ||
| 91 | + qpdflogger_cleanup(&l); | ||
| 92 | + qpdflogger_cleanup(&l3); | ||
| 93 | + | ||
| 94 | + qpdfjob_initialize_from_json(j, "{\n\ | ||
| 69 | \"inputFile\": \"nothing-there.pdf\"\n\ | 95 | \"inputFile\": \"nothing-there.pdf\"\n\ |
| 70 | -}") == 2); | 96 | +}"); |
| 97 | + assert(qpdfjob_run(j) == 2); | ||
| 98 | + qpdfjob_cleanup(&j); | ||
| 71 | printf("json error test passed\n"); | 99 | printf("json error test passed\n"); |
| 72 | } | 100 | } |
| 73 | 101 |
qpdf/qtest/qpdf/c-check-warn.out
| 1 | -WARNING: c-check-warn-in.pdf: file is damaged | ||
| 2 | -WARNING: c-check-warn-in.pdf (offset 1556): xref not found | ||
| 3 | -WARNING: c-check-warn-in.pdf: Attempting to reconstruct cross-reference table | 1 | +|custom|WARNING: |custom|c-check-warn-in.pdf: file is damaged|custom| |
| 2 | +|custom|WARNING: |custom|c-check-warn-in.pdf (offset 1556): xref not found|custom| | ||
| 3 | +|custom|WARNING: |custom|c-check-warn-in.pdf: Attempting to reconstruct cross-reference table|custom| | ||
| 4 | status: 1 | 4 | status: 1 |
| 5 | warning: c-check-warn-in.pdf: file is damaged | 5 | warning: c-check-warn-in.pdf: file is damaged |
| 6 | code: 5 | 6 | code: 5 |
qpdf/qtest/qpdf/qpdfjob-ctest.out
| @@ -6,5 +6,6 @@ json test passed | @@ -6,5 +6,6 @@ json test passed | ||
| 6 | WARNING: xref-with-short-size.pdf (xref stream, offset 16227): Cross-reference stream data has the wrong size; expected = 52; actual = 56 | 6 | WARNING: xref-with-short-size.pdf (xref stream, offset 16227): Cross-reference stream data has the wrong size; expected = 52; actual = 56 |
| 7 | qpdfjob json: operation succeeded with warnings; resulting file may have some problems | 7 | qpdfjob json: operation succeeded with warnings; resulting file may have some problems |
| 8 | json warn test passed | 8 | json warn test passed |
| 9 | -qpdfjob json: an output file name is required; use - for standard output | 9 | +|custom|qpdfjob json|custom|: |custom|an output file name is required; use - for standard output|custom| |
| 10 | +|custom|qpdfjob json|custom|: |custom|an output file name is required; use - for standard output|custom| | ||
| 10 | json error test passed | 11 | json error test passed |