QCborValue: move the toDiagnosticNotation() function to its own file

If we ever need to add QCborValue to the bootstrap library, it's
unlikely that we'll need this part. And by splitting it, I can make the
code handle more cases, that hadn't been properly handled before.

Change-Id: I2f630efbbce54f14bfa9fffd154160c0ad893695
Reviewed-by: Mårten Nordheim <marten.nordheim@qt.io>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Thiago Macieira 2018-07-14 16:42:08 -07:00
parent 3cd9d6d19b
commit 7cb5b324f0
7 changed files with 505 additions and 229 deletions

View File

@ -163,6 +163,58 @@ Q_DECL_CONST_FUNCTION static inline bool qt_is_finite(float f)
#ifndef Q_CLANG_QDOC
namespace {
/*!
Returns true if the double \a v can be converted to type \c T, false if
it's out of range. If the conversion is successful, the converted value is
stored in \a value; if it was not successful, \a value will contain the
minimum or maximum of T, depending on the sign of \a d. If \c T is
unsigned, then \a value contains the absolute value of \a v.
This function works for v containing infinities, but not NaN. It's the
caller's responsibility to exclude that possibility before calling it.
*/
template <typename T> static inline bool convertDoubleTo(double v, T *value)
{
Q_STATIC_ASSERT(std::numeric_limits<T>::is_integer);
// The [conv.fpint] (7.10 Floating-integral conversions) section of the C++
// standard says only exact conversions are guaranteed. Converting
// integrals to floating-point with loss of precision has implementation-
// defined behavior whether the next higher or next lower is returned;
// converting FP to integral is UB if it can't be represented.
//
// That means we can't write UINT64_MAX+1. Writing ldexp(1, 64) would be
// correct, but Clang, ICC and MSVC don't realize that it's a constant and
// the math call stays in the compiled code.
double supremum;
if (std::numeric_limits<T>::is_signed) {
supremum = -1.0 * std::numeric_limits<T>::min(); // -1 * (-2^63) = 2^63, exact (for T = qint64)
*value = std::numeric_limits<T>::min();
if (v < std::numeric_limits<T>::min())
return false;
} else {
using ST = typename std::make_signed<T>::type;
supremum = -2.0 * std::numeric_limits<ST>::min(); // -2 * (-2^63) = 2^64, exact (for T = quint64)
v = fabs(v);
}
*value = std::numeric_limits<T>::max();
if (v >= supremum)
return false;
// Now we can convert, these two conversions cannot be UB
*value = T(v);
QT_WARNING_PUSH
QT_WARNING_DISABLE_GCC("-Wfloat-equal")
QT_WARNING_DISABLE_CLANG("-Wfloat-equal")
return *value == v;
QT_WARNING_POP
}
// Overflow math.
// This provides efficient implementations for int, unsigned, qsizetype and
// size_t. Implementations for 8- and 16-bit types will work but may not be as

View File

@ -0,0 +1,281 @@
/****************************************************************************
**
** Copyright (C) 2018 Intel Corporation.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtCore module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qcborvalue.h"
#include "qcborvalue_p.h"
#include "qcborarray.h"
#include "qcbormap.h"
#include <private/qnumeric_p.h>
#include <qstack.h>
QT_BEGIN_NAMESPACE
namespace {
class DiagnosticNotation
{
public:
static QString create(const QCborValue &v, QCborValue::DiagnosticNotationOptions opts)
{
DiagnosticNotation dn(opts);
dn.appendValue(v);
return dn.result;
}
private:
QStack<int> byteArrayFormatStack;
QString separator;
QString result;
QCborValue::DiagnosticNotationOptions opts;
int nestingLevel = 0;
struct Nest {
enum { IndentationWidth = 4 };
DiagnosticNotation *dn;
Nest(DiagnosticNotation *that) : dn(that)
{
++dn->nestingLevel;
static const char indent[IndentationWidth + 1] = " ";
if (dn->opts & QCborValue::LineWrapped)
dn->separator += QLatin1String(indent, IndentationWidth);
}
~Nest()
{
--dn->nestingLevel;
if (dn->opts & QCborValue::LineWrapped)
dn->separator.chop(IndentationWidth);
}
};
DiagnosticNotation(QCborValue::DiagnosticNotationOptions opts_)
: separator(QLatin1String(opts_ & QCborValue::LineWrapped ? "\n" : "")), opts(opts_)
{
byteArrayFormatStack.push(int(QCborKnownTags::ExpectedBase16));
}
void appendString(const QString &s);
void appendArray(const QCborArray &a);
void appendMap(const QCborMap &m);
void appendValue(const QCborValue &v);
};
}
static QString makeFpString(double d)
{
QString s;
quint64 v;
if (qt_is_inf(d)) {
s = (d < 0) ? QStringLiteral("-inf") : QStringLiteral("inf");
} else if (qt_is_nan(d)) {
s = QStringLiteral("nan");
} else if (convertDoubleTo(d, &v)) {
s = QString::fromLatin1("%1.0").arg(v);
if (d < 0)
s.prepend(QLatin1Char('-'));
} else {
s = QString::number(d, 'g', QLocale::FloatingPointShortest);
if (!s.contains(QLatin1Char('.')) && !s.contains('e'))
s += QLatin1Char('.');
}
return s;
}
static bool isByteArrayEncodingTag(QCborTag tag)
{
switch (quint64(tag)) {
case quint64(QCborKnownTags::ExpectedBase16):
case quint64(QCborKnownTags::ExpectedBase64):
case quint64(QCborKnownTags::ExpectedBase64url):
return true;
}
return false;
}
void DiagnosticNotation::appendString(const QString &s)
{
result += QLatin1Char('"')
+ QString(s)
.replace(QLatin1Char('\\'), QLatin1String("\\\\"))
.replace(QLatin1Char('"'), QLatin1String("\\\""))
+ QLatin1Char('"');
}
void DiagnosticNotation::appendArray(const QCborArray &a)
{
result += QLatin1Char('[');
// length 2 (including the space) when not line wrapping
QLatin1String commaValue(", ", opts & QCborValue::LineWrapped ? 1 : 2);
{
Nest n(this);
QLatin1String comma;
for (auto v : a) {
result += comma + separator;
comma = commaValue;
appendValue(v);
}
}
result += separator + QLatin1Char(']');
}
void DiagnosticNotation::appendMap(const QCborMap &m)
{
result += QLatin1Char('{');
// length 2 (including the space) when not line wrapping
QLatin1String commaValue(", ", opts & QCborValue::LineWrapped ? 1 : 2);
{
Nest n(this);
QLatin1String comma;
for (auto v : m) {
result += comma + separator;
comma = commaValue;
appendValue(v.first);
result += QLatin1String(": ");
appendValue(v.second);
}
}
result += separator + QLatin1Char('}');
};
void DiagnosticNotation::appendValue(const QCborValue &v)
{
switch (v.type()) {
case QCborValue::Integer:
result += QString::number(v.toInteger());
return;
case QCborValue::ByteArray:
switch (byteArrayFormatStack.top()) {
case int(QCborKnownTags::ExpectedBase16):
result += QString::fromLatin1("h'" +
v.toByteArray().toHex(opts & QCborValue::ExtendedFormat ? ' ' : '\0') +
'\'');
return;
case int(QCborKnownTags::ExpectedBase64):
result += QString::fromLatin1("b64'" + v.toByteArray().toBase64() + '\'');
return;
default:
case int(QCborKnownTags::ExpectedBase64url):
result += QString::fromLatin1("b64'" +
v.toByteArray().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) +
'\'');
return;
}
case QCborValue::String:
return appendString(v.toString());
case QCborValue::Array:
return appendArray(v.toArray());
case QCborValue::Map:
return appendMap(v.toMap());
case QCborValue::False:
result += QLatin1String("false");
return;
case QCborValue::True:
result += QLatin1String("true");
return;
case QCborValue::Null:
result += QLatin1String("null");
return;
case QCborValue::Undefined:
result += QLatin1String("undefined");
return;
case QCborValue::Double:
result += makeFpString(v.toDouble());
return;
case QCborValue::Invalid:
result += QStringLiteral("<invalid>");
return;
default:
// Only tags, extended types, and simple types remain; see below.
break;
}
if (v.isTag()) {
// We handle all extended types as regular tags, so it won't matter
// whether we understand that tag or not.
bool byteArrayFormat = opts & QCborValue::ExtendedFormat && isByteArrayEncodingTag(v.tag());
if (byteArrayFormat)
byteArrayFormatStack.push(int(v.tag()));
result += QString::number(quint64(v.tag())) + QLatin1Char('(');
appendValue(v.taggedValue());
result += QLatin1Char(')');
if (byteArrayFormat)
byteArrayFormatStack.pop();
} else {
// must be a simple type
result += QString::fromLatin1("simple(%1)").arg(quint8(v.toSimpleType()));
}
}
/*!
Creates the diagnostic notation equivalent of this CBOR object and returns
it. The \a opts parameter controls the dialect of the notation. Diagnostic
notation is useful in debugging, to aid the developer in understanding what
value is stored in the QCborValue or in a CBOR stream. For that reason, the
Qt API provides no support for parsing the diagnostic back into the
in-memory format or CBOR stream, though the representation is unique and it
would be possible.
CBOR diagnostic notation is specified by
\l{https://tools.ietf.org/html/rfc7049#section-6}{section 6} of RFC 7049.
It is a text representation of the CBOR stream and it is very similar to
JSON, but it supports the CBOR types not found in JSON. The extended format
enabled by the \l{DiagnosticNotationOption}{ExtendedFormat} flag is
currently in some IETF drafts and its format is subject to change.
This function produces the equivalent representation of the stream that
toCbor() would produce, without any transformation option provided there.
This also implies this function may not produce a representation of the
stream that was used to create the object, if it was created using
fromCbor(), as that function may have applied transformations. For a
high-fidelity notation of a stream, without transformation, see the \c
cbordump example.
\sa toCbor(), toJsonDocument(), QJsonDocument::toJson()
*/
QString QCborValue::toDiagnosticNotation(DiagnosticNotationOptions opts) const
{
return DiagnosticNotation::create(*this, opts);
}
QT_END_NAMESPACE

View File

@ -47,9 +47,7 @@
#include <qendian.h>
#include <qlocale.h>
#include <private/qnumeric_p.h>
#include <qscopedvaluerollback.h>
#include <private/qsimd_p.h>
#include <qstack.h>
#include <new>
@ -768,58 +766,6 @@ using namespace QtCbor;
// in qcborstream.cpp
extern void qt_cbor_stream_set_error(QCborStreamReaderPrivate *d, QCborError error);
/*!
Returns true if the double \a v can be converted to type \c T, false if
it's out of range. If the conversion is successful, the converted value is
stored in \a value; if it was not successful, \a value will contain the
minimum or maximum of T, depending on the sign of \a d. If \c T is
unsigned, then \a value contains the absolute value of \a v.
This function works for v containing infinities, but not NaN. It's the
caller's responsibility to exclude that possibility before calling it.
*/
template <typename T> static inline bool convertDoubleTo(double v, T *value)
{
Q_STATIC_ASSERT(std::numeric_limits<T>::is_integer);
// The [conv.fpint] (7.10 Floating-integral conversions) section of the C++
// standard says only exact conversions are guaranteed. Converting
// integrals to floating-point with loss of precision has implementation-
// defined behavior whether the next higher or next lower is returned;
// converting FP to integral is UB if it can't be represented.
//
// That means we can't write UINT64_MAX+1. Writing ldexp(1, 64) would be
// correct, but only Clang, ICC and MSVC don't realize that it's a constant
// and the math call stays in the compiled code.
double supremum;
if (std::numeric_limits<T>::is_signed) {
supremum = -1.0 * std::numeric_limits<T>::min(); // -1 * (-2^63) = 2^63, exact (for T = qint64)
*value = std::numeric_limits<T>::min();
if (v < std::numeric_limits<T>::min())
return false;
} else {
using ST = typename std::make_signed<T>::type;
supremum = -2.0 * std::numeric_limits<ST>::min(); // -2 * (-2^63) = 2^64, exact (for T = quint64)
v = fabs(v);
}
*value = std::numeric_limits<T>::max();
if (v >= supremum)
return false;
// Now we can convert, these two conversions cannot be UB
*value = T(v);
QT_WARNING_PUSH
QT_WARNING_DISABLE_GCC("-Wfloat-equal")
QT_WARNING_DISABLE_CLANG("-Wfloat-equal")
return *value == v;
QT_WARNING_POP
}
static void writeDoubleToCbor(QCborStreamWriter &writer, double d, QCborValue::EncodingOptions opt)
{
if (qt_is_nan(d)) {
@ -869,146 +815,6 @@ static inline int typeOrder(Element e1, Element e2)
return comparable(e1) - comparable(e2);
}
namespace {
class DiagnosticNotation
{
public:
static QString create(const QCborValue &v, QCborValue::DiagnosticNotationOptions opts)
{
DiagnosticNotation dn(opts);
return dn.createFromValue(v);
}
private:
QStack<int> byteArrayFormatStack;
QCborValue::DiagnosticNotationOptions opts;
int nestingLevel = 0;
DiagnosticNotation(QCborValue::DiagnosticNotationOptions opts_)
: opts(opts_)
{
byteArrayFormatStack.push(int(QCborKnownTags::ExpectedBase16));
}
QString createFromValue(const QCborValue &v);
};
}
QString DiagnosticNotation::createFromValue(const QCborValue &v)
{
QString indent(1, QLatin1Char(' '));
QString indented = indent;
if (opts & QCborValue::LineWrapped) {
indent = QLatin1Char('\n') + QString(4 * nestingLevel, QLatin1Char(' '));
indented = indent + QLatin1String(" ");
}
QScopedValueRollback<int> rollback(nestingLevel);
++nestingLevel;
auto createFromArray = [=](const QCborArray &a) {
QString result;
QLatin1String comma;
for (auto v : a) {
result += comma + indented + createFromValue(v);
comma = QLatin1String(",");
}
return result;
};
auto createFromMap = [=](const QCborMap &m) {
QString result;
QLatin1String comma;
for (auto v : m) {
result += comma + indented + createFromValue(v.first) +
QLatin1String(": ") + createFromValue(v.second);
comma = QLatin1String(",");
}
return result;
};
auto makeFpString = [](double d) {
QString s;
quint64 v;
if (qt_is_inf(d)) {
s = (d < 0) ? QStringLiteral("-inf") : QStringLiteral("inf");
} else if (qt_is_nan(d)) {
s = QStringLiteral("nan");
} else if (convertDoubleTo(d, &v)) {
s = QString::fromLatin1("%1.0").arg(v);
if (d < 0)
s.prepend(QLatin1Char('-'));
} else {
s = QString::number(d, 'g', QLocale::FloatingPointShortest);
if (!s.contains(QLatin1Char('.')) && !s.contains('e'))
s += QLatin1Char('.');
}
return s;
};
auto isByteArrayEncodingTag = [](QCborTag tag) {
switch (quint64(tag)) {
case quint64(QCborKnownTags::ExpectedBase16):
case quint64(QCborKnownTags::ExpectedBase64):
case quint64(QCborKnownTags::ExpectedBase64url):
return true;
}
return false;
};
switch (v.type()) {
case QCborValue::Integer:
return QString::number(v.toInteger());
case QCborValue::ByteArray:
switch (byteArrayFormatStack.top()) {
case int(QCborKnownTags::ExpectedBase16):
return QString::fromLatin1("h'" +
v.toByteArray().toHex(opts & QCborValue::ExtendedFormat ? ' ' : '\0') +
'\'');
case int(QCborKnownTags::ExpectedBase64):
return QString::fromLatin1("b64'" + v.toByteArray().toBase64() + '\'');
default:
case int(QCborKnownTags::ExpectedBase64url):
return QString::fromLatin1("b64'" +
v.toByteArray().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals) +
'\'');
}
case QCborValue::String:
// ### TODO: Needs escaping!
return QLatin1Char('"') + v.toString() + QLatin1Char('"');
case QCborValue::Array:
return QLatin1Char('[') + createFromArray(v.toArray()) + indent + QLatin1Char(']');
case QCborValue::Map:
return QLatin1Char('{') + createFromMap(v.toMap()) + indent + QLatin1Char('}');
case QCborValue::False:
return QStringLiteral("false");
case QCborValue::True:
return QStringLiteral("true");
case QCborValue::Null:
return QStringLiteral("null");
case QCborValue::Undefined:
return QStringLiteral("undefined");
case QCborValue::Double:
return makeFpString(v.toDouble());
case QCborValue::Invalid:
return QStringLiteral("<invalid>");
case QCborValue::Tag:
case QCborValue::SimpleType:
default: // tags and other simple types that are recognized
break; // are all handled below
}
if (v.isTag()) {
bool byteArrayFormat = opts & QCborValue::ExtendedFormat && isByteArrayEncodingTag(v.tag());
if (byteArrayFormat)
byteArrayFormatStack.push(int(v.tag()));
QString result = QString::fromLatin1("%1(%2)").arg(quint64(v.tag())).arg(createFromValue(v.taggedValue()));
if (byteArrayFormat)
byteArrayFormatStack.pop();
return result;
}
// must be a simple type
return QString::fromLatin1("simple(%1)").arg(quint8(v.toSimpleType()));
}
QCborContainerPrivate::~QCborContainerPrivate()
{
// delete our elements
@ -2508,37 +2314,6 @@ Q_NEVER_INLINE void QCborValue::toCbor(QCborStreamWriter &writer, EncodingOption
}
}
/*!
Creates the diagnostic notation equivalent of this CBOR object and return
it. The \a opts parameter controls the dialect of the notation. Diagnostic
notation is useful in debugging, to aid the developer in understanding what
value is stored in the QCborValue or in a CBOR stream. For that reason, the
Qt API provides no support for parsing the diagnostic back into the
in-memory format or CBOR stream, though the representation is unique and it
would be possible.
CBOR diagnostic notation is specified by
\l{https://tools.ietf.org/html/rfc7049#section-6}{section 6} of RFC 7049.
It is a text representation of the CBOR stream and it is very similar to
JSON, but it supports the CBOR types not found in JSON. The extended format
enabled by the \l{DiagnosticNotationOption}{ExtendedFormat} flag is
currently in some IETF drafts and its format is subject to change.
This function produces the equivalent representation of the stream that
toCbor() would produce, without any transformation option provided there.
This also implies this function may not produce a representation of the
stream that was used to create the object, if it was created using
fromCbor(), as that function may have applied transformations. For a
high-fidelity notation of a stream, without transformation, see the \c
cbordump example.
\sa toCbor(), toJsonDocument(), QJsonDocument::toJson()
*/
QString QCborValue::toDiagnosticNotation(DiagnosticNotationOptions opts) const
{
return DiagnosticNotation::create(*this, opts);
}
void QCborValueRef::toCbor(QCborStreamWriter &writer, QCborValue::EncodingOptions opt)
{
concrete().toCbor(writer, opt);

View File

@ -56,6 +56,8 @@
#include <private/qglobal_p.h>
#include <private/qutfcodec_p.h>
#include <math.h>
QT_BEGIN_NAMESPACE
namespace QtCbor {

View File

@ -24,6 +24,7 @@ HEADERS += \
SOURCES += \
serialization/qcborstream.cpp \
serialization/qcbordiagnostic.cpp \
serialization/qcborvalue.cpp \
serialization/qdatastream.cpp \
serialization/qjson.cpp \

View File

@ -95,6 +95,8 @@ private slots:
void fromCbor();
void validation_data();
void validation();
void toDiagnosticNotation_data();
void toDiagnosticNotation();
};
// Get the validation data from TinyCBOR (see src/3rdparty/tinycbor/tests/parser/data.cpp)
@ -1517,6 +1519,169 @@ void tst_QCborValue::validation()
}
}
void tst_QCborValue::toDiagnosticNotation_data()
{
QTest::addColumn<QCborValue>("v");
QTest::addColumn<int>("opts");
QTest::addColumn<QString>("expected");
QDateTime dt = QDateTime::currentDateTimeUtc();
QUuid uuid = QUuid::createUuid();
QMetaEnum me = QMetaEnum::fromType<QCborValue::Type>();
auto st = [](QCborSimpleType t) { return QVariant::fromValue<SimpleTypeWrapper>(t); };
auto add = [me](const QCborValue &v, const QString &exp) {
auto addRow = [=](const char *prefix) -> QTestData & {
QCborValue::Type t = v.type();
if (t == QCborValue::Integer)
return QTest::addRow("%sInteger:%lld", prefix, v.toInteger());
if (t == QCborValue::Double)
return QTest::addRow("%sDouble:%g", prefix, v.toDouble());
if (t == QCborValue::ByteArray)
return QTest::addRow("%sByteArray:%d", prefix, v.toByteArray().size());
if (t == QCborValue::String)
return QTest::addRow("%sString:%d", prefix, v.toString().size());
QByteArray typeString = me.valueToKey(t);
Q_ASSERT(!typeString.isEmpty());
return QTest::newRow(prefix + typeString);
};
addRow("") << v << int(QCborValue::DiagnosticNotationOptions{}) << exp;
addRow("LW:") << v << int(QCborValue::LineWrapped) << exp;
addRow("Array:") << QCborValue(QCborArray{v}) << int(QCborValue::DiagnosticNotationOptions{}) << '[' + exp + ']';
addRow("Mapped:") << QCborValue(QCborMap{{2, v}}) << int(QCborValue::DiagnosticNotationOptions{}) << "{2: " + exp + '}';
addRow("Mapping:") << QCborValue(QCborMap{{v, 2}}) << int(QCborValue::DiagnosticNotationOptions{}) << '{' + exp + ": 2}";
};
// empty arrays and maps
QTest::newRow("EmptyArray")
<< QCborValue(QCborArray()) << int(QCborValue::DiagnosticNotationOptions{})
<< "[]";
QTest::newRow("EmptyMap")
<< QCborValue(QCborMap()) << int(QCborValue::DiagnosticNotationOptions{})
<< "{}";
add(QCborValue(), "undefined");
add(QCborValue::Null, "null");
add(false, "false");
add(true, "true");
add(QCborSimpleType(0), "simple(0)");
QTest::newRow("SimpleType-255")
<< QCborValue(QCborSimpleType(255)) << int(QCborValue::DiagnosticNotationOptions{})
<< "simple(255)";
add(0, "0");
add(1, "1");
add(-1, "-1");
add(std::numeric_limits<qint64>::min(), QString::number(std::numeric_limits<qint64>::min()));
add(std::numeric_limits<qint64>::max(), QString::number(std::numeric_limits<qint64>::max()));
add(0., "0.0");
add(1.25, "1.25");
add(-1.25, "-1.25");
add(qInf(), "inf");
add(-qInf(), "-inf");
add(qQNaN(), "nan");
add(QByteArray(), "h''");
add(QByteArray("Hello"), "h'48656c6c6f'");
add(QLatin1String(), QLatin1String("\"\""));
add("Hello", "\"Hello\"");
add("\"Hello\\World\"", "\"\\\"Hello\\\\World\\\"\"");
add(QCborValue(dt), "0(\"" + dt.toString(Qt::ISODateWithMs) + "\")");
add(QCborValue(QUrl("http://example.com")), "32(\"http://example.com\")");
add(QCborValue(QRegularExpression("^.*$")), "35(\"^.*$\")");
add(QCborValue(uuid), "37(h'" + uuid.toString(QUuid::Id128) + "')");
// arrays and maps with more than one element
QTest::newRow("2Array")
<< QCborValue(QCborArray{0, 1}) << int(QCborValue::DiagnosticNotationOptions{})
<< "[0, 1]";
QTest::newRow("2Map")
<< QCborValue(QCborMap{{0, 1}, {"foo", "bar"}}) << int(QCborValue::DiagnosticNotationOptions{})
<< "{0: 1, \"foo\": \"bar\"}";
// line wrapping in arrays and maps
QTest::newRow("LW:EmptyArray")
<< QCborValue(QCborArray()) << int(QCborValue::LineWrapped)
<< "[\n]";
QTest::newRow("LW:EmptyMap")
<< QCborValue(QCborMap()) << int(QCborValue::LineWrapped)
<< "{\n}";
QTest::newRow("LW:Array:Integer:0")
<< QCborValue(QCborArray{0}) << int(QCborValue::LineWrapped)
<< "[\n 0\n]";
QTest::newRow("LW:Array:String:5")
<< QCborValue(QCborArray{"Hello"}) << int(QCborValue::LineWrapped)
<< "[\n \"Hello\"\n]";
QTest::newRow("LW:Map:0-0")
<< QCborValue(QCborMap{{0, 0}}) << int(QCborValue::LineWrapped)
<< "{\n 0: 0\n}";
QTest::newRow("LW:Map:String:5")
<< QCborValue(QCborMap{{0, "Hello"}}) << int(QCborValue::LineWrapped)
<< "{\n 0: \"Hello\"\n}";
QTest::newRow("LW:2Array")
<< QCborValue(QCborArray{0, 1}) << int(QCborValue::LineWrapped)
<< "[\n 0,\n 1\n]";
QTest::newRow("LW:2Map")
<< QCborValue(QCborMap{{0, 0}, {"foo", "bar"}}) << int(QCborValue::LineWrapped)
<< "{\n 0: 0,\n \"foo\": \"bar\"\n}";
// nested arrays and maps
QTest::newRow("Array:EmptyArray")
<< QCborValue(QCborArray() << QCborArray()) << int(QCborValue::DiagnosticNotationOptions{})
<< "[[]]";
QTest::newRow("Array:EmptyMap")
<< QCborValue(QCborArray() << QCborMap()) << int(QCborValue::DiagnosticNotationOptions{})
<< "[{}]";
QTest::newRow("LW:Array:EmptyArray")
<< QCborValue(QCborArray() << QCborArray()) << int(QCborValue::LineWrapped)
<< "[\n [\n ]\n]";
QTest::newRow("LW:Array:EmptyMap")
<< QCborValue(QCborArray() << QCborMap()) << int(QCborValue::LineWrapped)
<< "[\n {\n }\n]";
QTest::newRow("LW:Array:2Array")
<< QCborValue(QCborArray() << QCborArray{0, 1}) << int(QCborValue::LineWrapped)
<< "[\n [\n 0,\n 1\n ]\n]";
QTest::newRow("LW:Map:2Array")
<< QCborValue(QCborMap{{0, QCborArray{0, 1}}}) << int(QCborValue::LineWrapped)
<< "{\n 0: [\n 0,\n 1\n ]\n}";
QTest::newRow("LW:Map:2Map")
<< QCborValue(QCborMap{{-1, QCborMap{{0, 0}, {"foo", "bar"}}}}) << int(QCborValue::LineWrapped)
<< "{\n -1: {\n 0: 0,\n \"foo\": \"bar\"\n }\n}";
// extended formatting for byte arrays
QTest::newRow("Extended:ByteArray:0")
<< QCborValue(QByteArray()) << int(QCborValue::ExtendedFormat)
<< "h''";
QTest::newRow("Extended:ByteArray:5")
<< QCborValue(QByteArray("Hello")) << int(QCborValue::ExtendedFormat)
<< "h'48 65 6c 6c 6f'";
QTest::newRow("Extended:ByteArray:Base64url")
<< QCborValue(QCborKnownTags::ExpectedBase64url, QByteArray("\xff\xef"))
<< int(QCborValue::ExtendedFormat) << "21(b64'_-8')";
QTest::newRow("Extended:ByteArray:Base64")
<< QCborValue(QCborKnownTags::ExpectedBase64, QByteArray("\xff\xef"))
<< int(QCborValue::ExtendedFormat) << "22(b64'/+8=')";
// formatting applies through arrays too
QTest::newRow("Extended:Array:ByteArray:Base64url")
<< QCborValue(QCborKnownTags::ExpectedBase64url, QCborArray{QByteArray("\xff\xef")})
<< int(QCborValue::ExtendedFormat) << "21([b64'_-8'])";
// and only the innermost applies
QTest::newRow("ByteArray:multiple-tags")
<< QCborValue(QCborKnownTags::ExpectedBase64url,
QCborArray{QCborValue(QCborKnownTags::ExpectedBase16, QByteArray("Hello")),
QByteArray("\xff\xef")})
<< int(QCborValue::ExtendedFormat) << "21([23(h'48 65 6c 6c 6f'), b64'_-8'])";
}
void tst_QCborValue::toDiagnosticNotation()
{
QFETCH(QCborValue, v);
QFETCH(QString, expected);
QFETCH(int, opts);
QString result = v.toDiagnosticNotation(QCborValue::DiagnosticNotationOptions(opts));
QCOMPARE(result, expected);
}
QTEST_MAIN(tst_QCborValue)
#include "tst_qcborvalue.moc"

View File

@ -300,12 +300,12 @@ void tst_QCborValue_Json::nonStringKeysInMaps_data()
QTest::newRow("base64") << QCborValue(QCborKnownTags::ExpectedBase64, data) << "/wE=";
QTest::newRow("hex") << QCborValue(QCborKnownTags::ExpectedBase16, data) << "ff01";
QTest::newRow("emptyarray") << QCborValue(QCborValue::Array) << "[ ]";
QTest::newRow("emptymap") << QCborValue(QCborValue::Map) << "{ }";
QTest::newRow("emptyarray") << QCborValue(QCborValue::Array) << "[]";
QTest::newRow("emptymap") << QCborValue(QCborValue::Map) << "{}";
QTest::newRow("array") << QCborValue(QCborArray{1, true, 2.5, "Hello"})
<< "[ 1, true, 2.5, \"Hello\" ]";
<< "[1, true, 2.5, \"Hello\"]";
QTest::newRow("map") << QCborValue(QCborMap{{"Hello", 0}, {0, "Hello"}})
<< "{ \"Hello\": 0, 0: \"Hello\" }";
<< "{\"Hello\": 0, 0: \"Hello\"}";
QDateTime dt = QDateTime::currentDateTimeUtc();
QUrl url("https://example.com");