This commit is contained in:
Jan Smrcina 2019-04-30 13:40:18 -07:00
Родитель a3a0caab96
Коммит 89e304be9f
17 изменённых файлов: 4844 добавлений и 330 удалений

332
.gitignore поставляемый
Просмотреть файл

@ -1,330 +1,2 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
**/Properties/launchSettings.json
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# JetBrains Rider
.idea/
*.sln.iml
# CodeRush
.cr/
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
build
.vscode

3
.gitmodules поставляемый Normal file
Просмотреть файл

@ -0,0 +1,3 @@
[submodule "external/Catch2"]
path = external/Catch2
url = https://github.com/catchorg/Catch2

19
CMakeLists.txt Normal file
Просмотреть файл

@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 3.12)
project(jsonbuilder)
# Include sub-projects.
add_subdirectory(src)
# Only include testing stuff if we are the top level
if (${CMAKE_PROJECT_NAME} STREQUAL ${PROJECT_NAME})
enable_testing()
if (NOT TARGET Catch2::Catch2)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/external/Catch2/contrib")
add_subdirectory(external/Catch2)
endif ()
add_subdirectory(test)
endif ()

1
DEPENDENCIES Normal file
Просмотреть файл

@ -0,0 +1 @@
uuid-dev

2
TODO Normal file
Просмотреть файл

@ -0,0 +1,2 @@
- Fix comments
- Consider a CMake export config file

Просмотреть файл

@ -0,0 +1,25 @@
#
# Input variables:
# UUID_PREFIX
#
# Output variables
# UUID_FOUND
# UUID_LIBRARIES
# UUID_INCLUDE_DIRS
#
if (UUID_INCLUDE_DIRS AND UUID_LIBRARIES)
set(UUID_FIND_QUIETLY TRUE)
else ()
find_path(UUID_INCLUDE_DIRS NAMES uuid/uuid.h HINTS ${UUID_PREFIX})
find_library(UUID_LIBRARIES NAMES uuid HINTS ${UUID_PREFIX})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(UUID DEFAULT_MSG UUID_LIBRARIES UUID_INCLUDE_DIRS)
mark_as_advanced(UUID_LIBRARIES UUID_INCLUDE_DIRS)
endif()
add_library(uuid INTERFACE)
target_include_directories(uuid INTERFACE ${UUID_INCLUDE_DIRS})
target_link_libraries(uuid INTERFACE ${UUID_LIBRARIES})

1
external/Catch2 поставляемый Submodule

@ -0,0 +1 @@
Subproject commit 979bbf03bb00bc55ca09783791b5091a2247df68

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,275 @@
/*
JsonRenderer converts JsonBuilder trees into utf8 JSON text.
Summary:
- JsonRenderer
- JsonRenderBool
- JsonRenderFalse
- JsonRenderTime
- JsonRenderFloat
- JsonRenderInt
- JsonRenderNull
- JsonRenderTrue
- JsonRenderUInt
- JsonRenderUuid
- JsonRenderUuidWithBraces
*/
#pragma once
#include <jsonbuilder/JsonBuilder.h>
#include <uuid/uuid.h>
namespace jsonbuilder
{
/*
Converts JsonBuilder data into utf-8 JSON text.
Recognizes the built-in JsonType types. To support other (custom) types,
derive from JsonRenderer and override RenderCustom.
*/
class JsonRenderer
{
protected:
typedef JsonInternal::PodVector<char> RenderBuffer;
typedef JsonBuilder::const_iterator iterator;
private:
RenderBuffer m_renderBuffer;
std::string_view m_newLine;
unsigned m_indentSpaces;
unsigned m_indent;
bool m_pretty;
public:
typedef JsonInternal::PodVector<char>::size_type size_type;
/*
Initializes a new instance of the JsonRenderer class.
Optionally sets the initial value of the formatting properties.
*/
explicit JsonRenderer(
bool pretty = false,
std::string_view const& newLine = "\n",
unsigned indentSpaces = 2) throw();
/*
Preallocates memory in the rendering buffer (increases capacity).
*/
void Reserve(size_type cb); // may throw bad_alloc, length_error
/*
Gets the current size of the rendering buffer, in bytes.
*/
size_type Size() const throw();
/*
Gets the current capacity of the rendering buffer (how large it can grow
without allocating additional memory), in bytes.
*/
size_type Capacity() const throw();
/*
Gets a value indicating whether the output will be formatted nicely.
If true, insignificant whitespace (spaces and newlines) will be added to
improve readability by humans and to put each value on its own line.
If false, all insignificant whitespace will be omitted.
Default value is false.
*/
bool Pretty() const throw();
/*
Set a value indicating whether the output will be formatted nicely.
If true, insignificant whitespace (spaces and newlines) will be added to
improve readability by humans and to put each value on its own line.
If false, all insignificant whitespace will be omitted.
Default value is false.
*/
void Pretty(bool) throw();
/*
Gets the string that is used for newline when Pretty() is true.
Default value is "\r\n".
*/
std::string_view const& NewLine() const throw();
/*
Sets the string that is used for newline when Pretty() is true.
Note that the JsonRenderer will store a copy of the string_view, but it
does not make a copy of the actual string. The string passed in here must
be valid for as long as the JsonRenderer exists.
Default value is "\r\n".
*/
void NewLine(std::string_view const&) throw();
/*
Gets the number of spaces per indent level. Default value is 2.
*/
unsigned IndentSpaces() const throw();
/*
Sets the number of spaces per indent level. Default value is 2.
*/
void IndentSpaces(unsigned) throw();
/*
Renders the contents of the specified JsonBuilder as utf-8 JSON, starting
at the root value.
Returns a string_view with the resulting JSON string. The returned string
is nul-terminated, but the nul is not included as part of the string_view
itself, so return.data()[return.size()] == '\0'.
The returned string_view is valid until the next call to Render or until
this JsonBuilder is destroyed.
*/
std::string_view Render(JsonBuilder const& builder); // may throw
// bad_alloc,
// length_error
/*
Renders the contents of a JsonBuilder as utf-8 JSON, starting at the
specified value.
Returns a string_view with the resulting JSON string. The returned string
is nul-terminated, but the nul is not included as part of the string_view
itself, so return.data()[return.size()] == '\0'.
The returned string_view is valid until the next call to Render or until
this JsonBuilder is destroyed.
*/
std::string_view
Render(JsonBuilder::const_iterator const& it); // may throw bad_alloc,
// length_error
protected:
/*
Override this method to provide rendering behavior for custom value types.
The utf-8 representation of the value referenced by itValue should be
appended to the end of buffer by calling buffer.push_back with the bytes
of the utf-8 representation.
*/
virtual void RenderCustom(
RenderBuffer& buffer,
iterator const& itValue); // may throw bad_alloc, length_error
private:
/*
Renders any value and its children by dispatching to the appropriate
subroutine.
CANNOT RENDER THE ROOT! (Don't call this if it.IsRoot() is true.)
*/
void RenderValue(iterator const& it);
/*
Renders an object/array value and its children.
itParent must be an array or object.
Set showNames = true for object. Set showNames = false for array.
Can be called with itParent == end() to render the root.
Example output: {"ObjectName":{"ArrayName":[7]}}
*/
void RenderStructure(iterator const& itParent, bool showNames);
/*
Renders value as floating-point. Requires that cb be 4 or 8. Data will be
interpreted as a little-endian float or double.
Example output: 123.45
*/
void RenderFloat(double const& value);
/*
Renders value as signed integer. Requires that cb be 1, 2, 4 or 8. Data will
be interpreted as a little-endian signed integer.
Example output: -12345
*/
void RenderInt(long long signed const& value);
/*
Renders value as unsigned integer. Requires that cb be 1, 2, 4 or 8. Data
will be interpreted as a little-endian unsigned integer.
Example output: 12345
*/
void RenderUInt(long long unsigned const& value);
/*
Renders value as time. Requires that cb be 8. Data will be interpreted as
number of 100ns intervals since 1601-01-01T00:00:00Z.
Example output: "2015-04-02T02:09:14.7927652Z".
*/
void RenderTime(std::chrono::system_clock::time_point const& value);
/*
Renders value as UUID. Requires that cb be 16.
Example output: "CD8D0A5E-6409-4B8E-9366-B815CEF0E35D".
*/
void RenderUuid(uuid_t const& value);
/*
Renders value as a string. Converts pch to utf-8, escapes any control
characters, and adds quotes around the result. Example output: "String\n"
*/
void RenderString(std::string_view const& value);
/*
If pretty-printing is disabled, has no effect.
If pretty-printing is enabled, writes m_newLine followed by
(m_indent * m_indentSpaces) space characters.
*/
void RenderNewline();
};
/*
Renders the given value as an unsigned decimal integer, e.g. "123".
Returns the number of characters written, not counting the nul-termination.
*/
unsigned JsonRenderUInt(long long unsigned n, char* pBuffer) throw();
/*
Renders the given value as a signed decimal integer, e.g. "-123".
Returns the number of characters written, not counting the nul-termination.
*/
unsigned JsonRenderInt(long long signed n, char* pBuffer) throw();
/*
Renders the given value as a signed floating-point, e.g. "-123.1", or "null"
if the value is not finite.
Returns the number of characters written, not counting the nul-termination.
*/
unsigned JsonRenderFloat(double n, char* pBuffer) throw();
/*
Renders the string "true" or "false".
Returns the number of characters written, not counting the nul-termination.
(Always returns 4 or 5.)
*/
unsigned JsonRenderBool(bool b, char* pBuffer) throw();
/*
Renders the string "null".
Returns the number of characters written, not counting the nul-termination.
(Always returns 4.)
*/
unsigned JsonRenderNull(char* pBuffer) throw();
/*
Renders the given FILETIME value (uint64) as an ISO 8601 string, e.g.
"2015-04-02T02:09:14.7927652Z".
Returns the number of characters written, not counting the nul-termination.
(Always returns 28.)
*/
unsigned JsonRenderTime(
std::chrono::system_clock::time_point const& ft,
char* pBuffer) throw();
/*
Renders the given GUID value as a string in uppercase without braces, e.g.
"CD8D0A5E-6409-4B8E-9366-B815CEF0E35D".
Returns the number of characters written, not counting the nul-termination.
(Always returns 36.)
*/
unsigned JsonRenderUuid(uuid_t const& g, char* pBuffer) throw();
/*
Renders the given GUID value as a string in uppercase with braces, e.g.
"{CD8D0A5E-6409-4B8E-9366-B815CEF0E35D}".
Returns the number of characters written, not counting the nul-termination.
(Always returns 38.)
*/
unsigned JsonRenderUuidWithBraces(uuid_t const& g, char* pBuffer) throw();
}

21
src/CMakeLists.txt Normal file
Просмотреть файл

@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.12)
list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules)
find_package(uuid REQUIRED)
set(JB_PrivateHeaders)
set(JB_SourceFiles JsonBuilder.cpp JsonRenderer.cpp PodVector.cpp)
set(PublicHeaderSrcPrefix ${PROJECT_SOURCE_DIR}/include/jsonbuilder)
set(JB_PublicHeaders ${PublicHeaderSrcPrefix}/JsonBuilder.h ${PublicHeaderSrcPrefix}/JsonRenderer.h)
# Add source to this project's executable.
add_library(jsonbuilder ${JB_SourceFiles} ${JB_PrivateHeaders} ${JB_PublicHeaders})
target_include_directories(jsonbuilder PUBLIC ${PROJECT_SOURCE_DIR}/include)
target_link_libraries(jsonbuilder PUBLIC uuid)
set_property(TARGET jsonbuilder PROPERTY CXX_STANDARD 17)
set_property(TARGET jsonbuilder PROPERTY POSITION_INDEPENDENT_CODE ON)
install(TARGETS jsonbuilder DESTINATION lib)
install(FILES ${JB_PublicHeaders} DESTINATION include/jsonbuilder)

1409
src/JsonBuilder.cpp Normal file

Разница между файлами не показана из-за своего большого размера Загрузить разницу

485
src/JsonRenderer.cpp Normal file
Просмотреть файл

@ -0,0 +1,485 @@
#include "jsonbuilder/JsonRenderer.h"
#include <cassert>
#include <string>
#include <uuid/uuid.h>
#define WriteChar(ch) m_renderBuffer.push_back(ch)
#define WriteChars(pch, cch) m_renderBuffer.append(pch, cch)
namespace jsonbuilder
{
/*
Efficiently multiply two 32-bit unsigned integers to get a 64-bit result.
(The current VC compiler does not optimize this -- if we don't use an
intrinsic, it makes a call to _aullmul.)
*/
#if defined(_M_ARM) || defined(_M_ARM64)
# define UMul64(a, b) _arm_umull(a, b)
#elif defined(_M_IX86) || defined(_M_X64)
# define UMul64(a, b) __emulu(a, b)
#else
static long long unsigned UMul64(unsigned a, unsigned b)
{
return static_cast<long long unsigned>(a) * b;
}
#endif
/*
Given a number from 0..15, returns the corresponding uppercase hex digit,
i.e. '0'..'F'. Note: it is an error to pass a value larger than 15.
*/
static inline constexpr char u4_to_hex_upper(char unsigned _Val4)
{
return ("0123456789ABCDEF")[_Val4];
}
/*
Multiply two 64-bit unsigned integers to get a 128-bit result. Return the high
64 bits of the answer.
*/
__attribute__((unused)) static long long unsigned
UMul128Hi(long long unsigned a, long long unsigned b)
{
#if defined(_M_X64) || defined(_M_ARM64)
long long unsigned const high = __umulh(a, b);
#else
long long unsigned const mid =
UMul64(static_cast<unsigned>(a), static_cast<unsigned>(b >> 32)) +
(UMul64(static_cast<unsigned>(a), static_cast<unsigned>(b)) >> 32);
long long unsigned const high =
UMul64(static_cast<unsigned>(a >> 32), static_cast<unsigned>(b >> 32)) +
(mid >> 32) +
((UMul64(static_cast<unsigned>(a >> 32), static_cast<unsigned>(b)) +
static_cast<unsigned>(mid)) >>
32);
#endif
return high;
}
/*
Formats a uint32 with leading 0s. Always writes cch characters.
*/
static void FormatUint(unsigned n, char* pch, unsigned cch)
{
do
{
--cch;
pch[cch] = '0' + (n % 10);
n = n / 10;
} while (cch != 0);
}
template<unsigned CB, class N>
static unsigned JsonRenderXInt(N const& n, char* pBuffer)
{
// TODO: Why can't we use std::to_wchars when we're using c++17?
std::string result = std::to_string(n);
std::size_t const cch =
std::min(result.size(), static_cast<std::size_t>(CB - 1));
strncpy(pBuffer, result.c_str(), cch);
pBuffer[cch] = 0;
pBuffer[cch] = 0;
return static_cast<unsigned>(cch);
}
unsigned JsonRenderUInt(long long unsigned n, char* pBuffer) throw()
{
return JsonRenderXInt<21>(n, pBuffer);
}
unsigned JsonRenderInt(long long signed n, char* pBuffer) throw()
{
return JsonRenderXInt<21>(n, pBuffer);
}
unsigned JsonRenderFloat(double n, char* pBuffer) throw()
{
unsigned cch;
if (isfinite(n))
{
cch = static_cast<unsigned>(snprintf(pBuffer, 31, "%.17g", n));
if (cch > 31)
{
cch = 31;
}
pBuffer[cch] = 0;
}
else
{
cch = JsonRenderNull(pBuffer);
}
return cch;
}
unsigned JsonRenderBool(bool b, char* pBuffer) throw()
{
unsigned cch;
if (b)
{
strcpy(pBuffer, "true");
cch = 4;
}
else
{
strcpy(pBuffer, "false");
cch = 5;
}
return cch;
}
unsigned JsonRenderNull(char* pBuffer) throw()
{
strcpy(pBuffer, "null");
return 4;
}
unsigned JsonRenderTime(
std::chrono::system_clock::time_point const& timePoint,
char* pBuffer) throw()
{
time_t printableTime = std::chrono::system_clock::to_time_t(timePoint);
tm timeStruct = {};
gmtime_r(&printableTime, &timeStruct);
auto subsecondDuration =
timePoint.time_since_epoch() % std::chrono::seconds{ 1 };
auto subsecondNanos =
std::chrono::duration_cast<std::chrono::nanoseconds>(subsecondDuration);
FormatUint(static_cast<unsigned>(timeStruct.tm_year + 1900), pBuffer + 0, 4);
pBuffer[4] = '-';
FormatUint(static_cast<unsigned>(timeStruct.tm_mon + 1), pBuffer + 5, 2);
pBuffer[7] = '-';
FormatUint(static_cast<unsigned>(timeStruct.tm_mday), pBuffer + 8, 2);
pBuffer[10] = 'T';
FormatUint(static_cast<unsigned>(timeStruct.tm_hour), pBuffer + 11, 2);
pBuffer[13] = ':';
FormatUint(static_cast<unsigned>(timeStruct.tm_min), pBuffer + 14, 2);
pBuffer[16] = ':';
FormatUint(static_cast<unsigned>(timeStruct.tm_sec), pBuffer + 17, 2);
pBuffer[19] = '.';
FormatUint(
static_cast<unsigned>(subsecondNanos.count() / 100), pBuffer + 20, 7);
pBuffer[27] = 'Z';
pBuffer[28] = 0;
return 28;
}
unsigned JsonRenderUuid(uuid_t const& g, char* pBuffer) throw()
{
uuid_unparse_upper(g, pBuffer);
return 36;
}
unsigned JsonRenderUuidWithBraces(uuid_t const& g, char* pBuffer) throw()
{
pBuffer[0] = '{';
JsonRenderUuid(g, pBuffer + 1);
pBuffer[37] = '}';
pBuffer[38] = 0;
return 38;
}
JsonRenderer::JsonRenderer(
bool pretty,
std::string_view const& newLine,
unsigned indentSpaces) throw()
: m_newLine(newLine), m_indentSpaces(indentSpaces), m_pretty(pretty)
{
return;
}
void JsonRenderer::Reserve(size_type cb)
{
m_renderBuffer.reserve(cb);
}
JsonRenderer::size_type JsonRenderer::Size() const throw()
{
return m_renderBuffer.size();
}
JsonRenderer::size_type JsonRenderer::Capacity() const throw()
{
return m_renderBuffer.capacity();
}
bool JsonRenderer::Pretty() const throw()
{
return m_pretty;
}
void JsonRenderer::Pretty(bool value) throw()
{
m_pretty = value;
}
std::string_view const& JsonRenderer::NewLine() const throw()
{
return m_newLine;
}
void JsonRenderer::NewLine(std::string_view const& value) throw()
{
m_newLine = value;
}
unsigned JsonRenderer::IndentSpaces() const throw()
{
return m_indentSpaces;
}
void JsonRenderer::IndentSpaces(unsigned value) throw()
{
m_indentSpaces = value;
}
std::string_view JsonRenderer::Render(JsonBuilder const& builder)
{
auto itRoot = builder.end();
m_renderBuffer.clear();
m_indent = 0;
RenderStructure(itRoot, true);
WriteChar('\0');
return std::string_view(m_renderBuffer.data(), m_renderBuffer.size() - 1);
}
std::string_view JsonRenderer::Render(JsonBuilder::const_iterator const& it)
{
m_renderBuffer.clear();
m_indent = 0;
if (it.IsRoot())
{
RenderStructure(it, true);
}
else
{
RenderValue(it);
}
WriteChar('\0');
return std::string_view(m_renderBuffer.data(), m_renderBuffer.size() - 1);
}
void JsonRenderer::RenderCustom(RenderBuffer&, iterator const& it)
{
auto const cchMax = 32u;
auto pch = m_renderBuffer.GetAppendPointer(cchMax);
unsigned cch =
static_cast<unsigned>(snprintf(pch, cchMax, "\"Custom#%u\"", it->Type()));
pch += cch > cchMax ? cchMax : cch;
m_renderBuffer.SetEndPointer(pch);
}
void JsonRenderer::RenderValue(iterator const& it)
{
assert(!it.IsRoot());
switch (it->Type())
{
case JsonObject:
RenderStructure(it, true);
break;
case JsonArray:
RenderStructure(it, false);
break;
case JsonNull:
WriteChars("null", 4);
break;
case JsonBool:
if (it->GetUnchecked<bool>())
{
WriteChars("true", 4);
}
else
{
WriteChars("false", 5);
}
break;
case JsonUtf8:
RenderString(it->GetUnchecked<std::string_view>());
break;
case JsonFloat:
RenderFloat(it->GetUnchecked<double>());
break;
case JsonInt:
RenderInt(it->GetUnchecked<long long signed>());
break;
case JsonUInt:
RenderUInt(it->GetUnchecked<long long unsigned>());
break;
case JsonTime:
RenderTime(it->GetUnchecked<std::chrono::system_clock::time_point>());
break;
case JsonUuid:
RenderUuid(it->GetUnchecked<UuidStruct>().Data);
break;
default:
RenderCustom(m_renderBuffer, it);
break;
}
}
void JsonRenderer::RenderStructure(iterator const& itParent, bool showNames)
{
WriteChar(showNames ? '{' : '[');
auto it = itParent.begin();
auto itEnd = itParent.end();
if (it != itEnd)
{
m_indent += m_indentSpaces;
for (;;)
{
if (m_pretty)
{
RenderNewline();
}
if (showNames)
{
RenderString(it->Name());
WriteChar(':');
if (m_pretty)
{
WriteChar(' ');
}
}
RenderValue(it);
++it;
if (it == itEnd)
{
break;
}
WriteChar(',');
}
m_indent -= m_indentSpaces;
if (m_pretty)
{
RenderNewline();
}
}
WriteChar(showNames ? '}' : ']');
}
void JsonRenderer::RenderFloat(double const& value)
{
auto pch = m_renderBuffer.GetAppendPointer(32);
pch += JsonRenderFloat(value, pch);
m_renderBuffer.SetEndPointer(pch);
}
void JsonRenderer::RenderInt(long long signed const& value)
{
auto const cchMax = 20u;
auto pch = m_renderBuffer.GetAppendPointer(cchMax);
// TODO: Why can't we use std::to_wchars when we're using c++17?
unsigned cch = static_cast<unsigned>(snprintf(pch, cchMax, "%lld", value));
pch += cch > cchMax ? cchMax : cch;
m_renderBuffer.SetEndPointer(pch);
}
void JsonRenderer::RenderUInt(long long unsigned const& value)
{
auto const cchMax = 20u;
auto pch = m_renderBuffer.GetAppendPointer(cchMax);
// TODO: Why can't we use std::to_wchars when we're using c++17?
unsigned cch = static_cast<unsigned>(snprintf(pch, cchMax, "%llu", value));
pch += cch > cchMax ? cchMax : cch;
m_renderBuffer.SetEndPointer(pch);
}
void JsonRenderer::RenderTime(std::chrono::system_clock::time_point const& value)
{
auto pch = m_renderBuffer.GetAppendPointer(32);
*pch++ = '"';
pch += JsonRenderTime(value, pch);
*pch++ = '"';
m_renderBuffer.SetEndPointer(pch);
}
void JsonRenderer::RenderUuid(uuid_t const& value)
{
auto pch = m_renderBuffer.GetAppendPointer(38);
*pch++ = '"';
pch += JsonRenderUuid(value, pch);
*pch++ = '"';
m_renderBuffer.SetEndPointer(pch);
}
void JsonRenderer::RenderString(std::string_view const& value)
{
WriteChar('"');
for (auto ch : value)
{
if (ch >= 0 && ch < 0x20)
{
// Control character - must be escaped.
switch (ch)
{
case 8:
WriteChar('\\');
WriteChar('b');
break;
case 9:
WriteChar('\\');
WriteChar('t');
break;
case 10:
WriteChar('\\');
WriteChar('n');
break;
case 12:
WriteChar('\\');
WriteChar('f');
break;
case 13:
WriteChar('\\');
WriteChar('r');
break;
default:
auto p = m_renderBuffer.GetAppendPointer(6);
*p++ = '\\';
*p++ = 'u';
*p++ = '0';
*p++ = '0';
*p++ = u4_to_hex_upper((ch >> 4) & 0xf);
*p++ = u4_to_hex_upper(ch & 0xf);
m_renderBuffer.SetEndPointer(p);
break;
}
}
else if (ch == '"' || ch == '\\')
{
// ASCII character - pass through (escape quote and backslash)
WriteChar('\\');
WriteChar(ch);
}
else
{
WriteChar(ch);
}
}
WriteChar('"');
}
void JsonRenderer::RenderNewline()
{
WriteChars(m_newLine.data(), static_cast<unsigned>(m_newLine.size()));
m_renderBuffer.append(m_indent, ' ');
}
}

124
src/PodVector.cpp Normal file
Просмотреть файл

@ -0,0 +1,124 @@
#include <cassert>
#include "jsonbuilder/JsonBuilder.h"
namespace jsonbuilder
{
//
// TODO:
// Copied from OS code
// with a bug fix, since (1 << 32) is undefined in the c standard and evaluates
// to 1 on some hardware. Try to find a better source or update PodVector.cpp to
// not rely on this.
//
static inline unsigned char
BitScanReverse(unsigned long* Index, unsigned long Mask)
{
if (Mask == 0 || Index == 0)
return 0;
unsigned int ii = 0;
for (ii = ((sizeof(Mask) * 8) - 1); ii >= 0; --ii)
{
unsigned long tempMask = 1 << ii;
if ((Mask & tempMask) != 0)
{
*Index = ii;
break;
}
}
return (ii >= 0 ? (unsigned char) 1 : (unsigned char) 0);
}
namespace JsonInternal
{
void PodVectorBase::CheckOffset(size_type index, size_type currentSize) throw()
{
(void) index; // Unreferenced parameter
(void) currentSize; // Unreferenced parameter
assert(index < currentSize);
}
void PodVectorBase::CheckRange(void const* p1, void const* p2, void const* p3) throw()
{
(void) p1; // Unreferenced parameter
(void) p2; // Unreferenced parameter
(void) p3; // Unreferenced parameter
assert(p1 <= p2 && p2 <= p3);
}
PodVectorBase::size_type PodVectorBase::CheckedAdd(size_type a, size_type b)
{
size_type c = a + b;
if (c < a)
{
throw std::length_error("JsonVector - exceeded maximum capacity");
}
return c;
}
void PodVectorBase::InitData(void* pDest, void const* pSource, size_t cb) throw()
{
memcpy(pDest, pSource, cb);
}
PodVectorBase::size_type
PodVectorBase::GetNewCapacity(size_type minCapacity, size_type maxCapacity)
{
size_type cap;
if (minCapacity <= 15)
{
cap = 15;
}
else
{
long unsigned index;
BitScanReverse(&index, minCapacity);
cap = static_cast<uint32_t>((2 << index) - 1);
}
if (maxCapacity < cap)
{
if (maxCapacity < minCapacity)
{
throw std::length_error("JsonVector - exceeded maximum capacity");
}
cap = maxCapacity;
}
assert(15 <= cap);
assert(minCapacity <= cap);
assert(cap <= maxCapacity);
return cap;
}
void* PodVectorBase::Reallocate(void* pb, size_t cb, bool zeroInitializeMemory)
{
void* const pbNew = pb ? realloc(pb, cb) : malloc(cb);
if (pbNew == nullptr)
{
throw std::bad_alloc();
}
if (zeroInitializeMemory)
{
memset(pbNew, 0, cb);
}
return pbNew;
}
void PodVectorBase::Deallocate(void* pb) throw()
{
if (pb)
{
free(pb);
}
}
}
// namespace JsonInternal
}

13
test/CMakeLists.txt Normal file
Просмотреть файл

@ -0,0 +1,13 @@
cmake_minimum_required(VERSION 3.12)
# Add source to this project's executable.
add_executable(jsonbuilderTest CatchMain.cpp TestBuilder.cpp TestRenderer.cpp)
target_link_libraries(jsonbuilderTest PRIVATE jsonbuilder Catch2::Catch2)
set_property(TARGET jsonbuilderTest PROPERTY CXX_STANDARD 17)
include(CTest)
include(Catch)
catch_discover_tests(jsonbuilderTest)
install(TARGETS jsonbuilderTest DESTINATION bin)

2
test/CatchMain.cpp Normal file
Просмотреть файл

@ -0,0 +1,2 @@
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>

630
test/TestBuilder.cpp Normal file
Просмотреть файл

@ -0,0 +1,630 @@
#include <catch2/catch.hpp>
#include <jsonbuilder/JsonBuilder.h>
using namespace jsonbuilder;
TEST_CASE("JsonBuilder buffer reserve", "[builder]")
{
constexpr auto c_maxSize = JsonBuilder::buffer_max_size();
JsonBuilder b;
SECTION("Buffer begins empty")
{
REQUIRE(b.buffer_size() == 0);
REQUIRE(b.buffer_capacity() == 0);
}
SECTION("Buffer reserving 0 remains empty")
{
b.buffer_reserve(0);
REQUIRE(b.buffer_size() == 0);
REQUIRE(b.buffer_capacity() == 0);
}
SECTION("Buffer reserving 1 keeps size 0 and makes a capacity of at least "
"the size of one element")
{
b.buffer_reserve(1);
REQUIRE(b.buffer_size() == 0);
REQUIRE(b.buffer_capacity() >= 4);
}
SECTION("Buffer reserving 2 keeps size 0 and makes a capacity of at least "
"the size of one element")
{
b.buffer_reserve(2);
REQUIRE(b.buffer_size() == 0);
REQUIRE(b.buffer_capacity() >= 4);
}
SECTION("Buffer reserving 5 keeps size 0 and makes a capacity of at least "
"the size of two elements")
{
b.buffer_reserve(5);
REQUIRE(b.buffer_size() == 0);
REQUIRE(b.buffer_capacity() >= 8);
}
SECTION("Buffer reserving max succeeds or throws std::bad_alloc")
{
auto reserveMax = [&]() {
try
{
b.buffer_reserve(c_maxSize);
}
catch (const std::bad_alloc&)
{}
};
REQUIRE_NOTHROW(reserveMax());
}
SECTION("Buffer reserving one more than max throws std::length_error")
{
REQUIRE_THROWS_AS(b.buffer_reserve(c_maxSize + 1), std::length_error);
}
SECTION("Buffer reserving maximum of size_type throws std::length_error")
{
auto maxSizeType = ~JsonBuilder::size_type(0);
REQUIRE_THROWS_AS(b.buffer_reserve(maxSizeType), std::length_error);
}
}
template<class InputType, class OutputType>
static void TestInputOutputScalar()
{
using InputLimits = std::numeric_limits<InputType>;
using OutputLimits = std::numeric_limits<OutputType>;
JsonBuilder b;
b.push_back(b.end(), "", InputLimits::lowest());
b.push_back(b.end(), "", InputLimits::min());
b.push_back(b.end(), "", InputLimits::max());
b.push_back(b.end(), "", OutputLimits::lowest());
b.push_back(b.end(), "", OutputLimits::min());
b.push_back(b.end(), "", OutputLimits::max());
REQUIRE_NOTHROW(b.ValidateData());
InputType i;
auto it = b.begin();
REQUIRE(it->GetUnchecked<InputType>() == InputLimits::lowest());
REQUIRE(it->ConvertTo(i));
REQUIRE(i == InputLimits::lowest());
++it;
REQUIRE(it->GetUnchecked<InputType>() == InputLimits::min());
REQUIRE(it->ConvertTo(i));
REQUIRE(i == InputLimits::min());
++it;
REQUIRE(it->GetUnchecked<InputType>() == InputLimits::max());
REQUIRE(it->ConvertTo(i));
REQUIRE(i == InputLimits::max());
++it;
REQUIRE(
it->GetUnchecked<InputType>() ==
static_cast<InputType>(OutputLimits::lowest()));
if (it->ConvertTo(i))
{
REQUIRE(i == static_cast<InputType>(OutputLimits::lowest()));
}
else
{
REQUIRE(i == 0);
REQUIRE(it->GetUnchecked<InputType>() != OutputLimits::lowest());
}
++it;
REQUIRE(
it->GetUnchecked<InputType>() ==
static_cast<InputType>(OutputLimits::min()));
if (it->ConvertTo(i))
{
REQUIRE(i == static_cast<InputType>(OutputLimits::min()));
}
else
{
REQUIRE(i == 0);
REQUIRE(it->GetUnchecked<InputType>() != OutputLimits::min());
}
++it;
REQUIRE(
it->GetUnchecked<InputType>() ==
static_cast<InputType>(OutputLimits::max()));
if (it->ConvertTo(i))
{
REQUIRE(i == static_cast<InputType>(OutputLimits::max()));
}
else
{
REQUIRE(i == 0);
REQUIRE(it->GetUnchecked<InputType>() != OutputLimits::max());
}
}
TEST_CASE("JsonBuilder numeric limits", "[builder]")
{
SECTION("signed char") { TestInputOutputScalar<signed char, int64_t>(); }
SECTION("signed short") { TestInputOutputScalar<signed short, int64_t>(); }
SECTION("signed int") { TestInputOutputScalar<signed int, int64_t>(); }
SECTION("signed long") { TestInputOutputScalar<signed long, int64_t>(); }
SECTION("signed long long")
{
TestInputOutputScalar<signed long long, int64_t>();
}
SECTION("unsigned char")
{
TestInputOutputScalar<unsigned char, uint64_t>();
}
SECTION("unsigned short")
{
TestInputOutputScalar<unsigned short, uint64_t>();
}
SECTION("unsigned int") { TestInputOutputScalar<unsigned int, uint64_t>(); }
SECTION("unsigned long")
{
TestInputOutputScalar<unsigned long, uint64_t>();
}
SECTION("unsigned long long")
{
TestInputOutputScalar<unsigned long long, uint64_t>();
}
SECTION("float") { TestInputOutputScalar<float, double>(); }
SECTION("double") { TestInputOutputScalar<double, double>(); }
}
TEST_CASE("JsonBuilder string push_back")
{
JsonBuilder b;
SECTION("push_back std::string_view")
{
auto itr = b.push_back(b.end(), "", std::string_view{ "ABCDE" });
REQUIRE(itr->GetUnchecked<std::string_view>() == "ABCDE");
}
SECTION("push_back std::string")
{
auto itr = b.push_back(b.end(), "", std::string{ "ABCDE" });
REQUIRE(itr->GetUnchecked<std::string_view>() == "ABCDE");
}
SECTION("push_back char")
{
auto itr = b.push_back(b.end(), "", ' ');
REQUIRE(itr->GetUnchecked<std::string_view>() == " ");
}
SECTION("push_back char*")
{
auto itr = b.push_back(b.end(), "", const_cast<char*>("ABC"));
REQUIRE(itr->GetUnchecked<std::string_view>() == "ABC");
}
SECTION("push_back const char*")
{
auto itr = b.push_back(b.end(), "", static_cast<const char*>("DEF"));
REQUIRE(itr->GetUnchecked<std::string_view>() == "DEF");
}
SECTION("push_back const char[]")
{
auto itr = b.push_back(b.end(), "", "HIJ");
REQUIRE(itr->GetUnchecked<std::string_view>() == "HIJ");
}
}
TEST_CASE("JsonBuilder chrono push_back", "[builder]")
{
auto now = std::chrono::system_clock::now();
JsonBuilder b;
auto itr = b.push_back(b.end(), "CurrentTime", now);
auto retrieved = itr->GetUnchecked<std::chrono::system_clock::time_point>();
REQUIRE(retrieved == now);
}
TEST_CASE("JsonBuilder uuid push_back", "[builder]")
{
UuidStruct uuid;
uuid_generate(uuid.Data);
JsonBuilder b;
auto itr = b.push_back(b.end(), "Uuid", uuid);
auto retrieved = itr->GetUnchecked<UuidStruct>();
REQUIRE(uuid_compare(retrieved.Data, uuid.Data) == 0);
}
TEST_CASE("JsonBuilder find", "[builder]")
{
JsonBuilder b;
// Empty object
REQUIRE(b.find("a1") == b.end());
REQUIRE(b.find("a1", "a2") == b.end());
// Single object (a1)
auto itA1 = b.push_back(b.end(), "a1", JsonObject);
REQUIRE(b.find("a1") == itA1);
REQUIRE(b.find(b.end(), "a1") == itA1);
REQUIRE(b.find("b1") == b.end());
REQUIRE(b.find(b.end(), "b1") == b.end());
REQUIRE(b.find("a1", "a2") == b.end());
// Second object b2, sibling of a1
auto itB1 = b.push_back(b.end(), "b1", JsonObject);
REQUIRE(b.find("a1") == itA1);
REQUIRE(b.find("a1", "a2") == b.end());
REQUIRE(b.find("b1") == itB1);
REQUIRE(b.find("c1") == b.end());
// First child of a1, a2
auto itA1A2 = b.push_back(itA1, "a2", JsonObject);
REQUIRE(b.find("a1") == itA1);
REQUIRE(b.find("a1", "a2") == itA1A2);
REQUIRE(b.find(b.end(), "a1", "a2") == itA1A2);
REQUIRE(b.find("a1", "a2", "a3") == b.end());
REQUIRE(b.find("b1") == itB1);
REQUIRE(b.find("c1") == b.end());
// First child of a2, a3
auto itA1A2A3 = b.push_back(itA1A2, "a3", 0);
REQUIRE(b.find("a1", "a2", "a3") == itA1A2A3);
REQUIRE(b.find(itA1, "a2") == itA1A2);
REQUIRE(b.find(itB1, "a2") == b.end());
REQUIRE_NOTHROW(b.ValidateData());
}
TEST_CASE("JsonBuilder constructors", "[builder]")
{
JsonBuilder b;
b.push_back(b.end(), "aname", "ava");
b.push_back(b.end(), "bname", "bva");
REQUIRE_NOTHROW(b.ValidateData());
SECTION("Copy constructor")
{
JsonBuilder copy{ b };
REQUIRE_NOTHROW(copy.ValidateData());
auto it = copy.begin();
REQUIRE(it->Name() == "aname");
REQUIRE(it->GetUnchecked<std::string_view>() == "ava");
++it;
REQUIRE(it->Name() == "bname");
REQUIRE(it->GetUnchecked<std::string_view>() == "bva");
}
SECTION("Move constructor")
{
JsonBuilder move{ std::move(b) };
REQUIRE_NOTHROW(move.ValidateData());
auto it = move.begin();
REQUIRE(it->Name() == "aname");
REQUIRE(it->GetUnchecked<std::string_view>() == "ava");
++it;
REQUIRE(it->Name() == "bname");
REQUIRE(it->GetUnchecked<std::string_view>() == "bva");
}
}
TEST_CASE("JsonBuilder erase", "[builder]")
{
JsonBuilder b;
b.push_back(b.end(), "aname", "ava");
b.push_back(b.end(), "bname", "bva");
REQUIRE_NOTHROW(b.ValidateData());
SECTION("erase a single child element")
{
auto itr = b.erase(b.begin());
REQUIRE_NOTHROW(b.ValidateData());
REQUIRE(itr == b.begin());
REQUIRE(b.count(b.end()) == 1);
}
SECTION("erase all children")
{
auto itr = b.erase(b.begin(), b.end());
REQUIRE_NOTHROW(b.ValidateData());
REQUIRE(itr == b.end());
REQUIRE(b.begin() == b.end());
REQUIRE(b.count(itr) == 0);
}
}
TEST_CASE("JsonBuilder conversions", "[builder]")
{
int64_t ival;
uint64_t uval;
double fval;
std::string_view sval;
bool bval;
std::chrono::system_clock::time_point tval;
UuidStruct uuidval;
JsonBuilder b;
SECTION("JsonNull")
{
auto itr = b.push_back(b.end(), "FirstItem", JsonNull);
REQUIRE(itr->IsNull());
REQUIRE(!itr->ConvertTo(bval));
REQUIRE(!itr->ConvertTo(fval));
REQUIRE(!itr->ConvertTo(ival));
REQUIRE(!itr->ConvertTo(uval));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("false")
{
auto itr = b.push_back(b.end(), "", false);
REQUIRE(itr->GetUnchecked<bool>() == false);
REQUIRE((itr->ConvertTo(bval) && !bval));
REQUIRE(!itr->ConvertTo(fval));
REQUIRE(!itr->ConvertTo(ival));
REQUIRE(!itr->ConvertTo(uval));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("true")
{
auto itr = b.push_back(b.end(), "", true);
REQUIRE(itr->GetUnchecked<bool>() == true);
REQUIRE((itr->ConvertTo(bval) && bval));
REQUIRE(!itr->ConvertTo(fval));
REQUIRE(!itr->ConvertTo(ival));
REQUIRE(!itr->ConvertTo(uval));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("int64_t")
{
auto itr = b.push_back(b.end(), "", 123);
REQUIRE(itr->GetUnchecked<int64_t>() == 123);
REQUIRE(!itr->ConvertTo(bval));
REQUIRE((itr->ConvertTo(fval) && fval == 123));
REQUIRE((itr->ConvertTo(ival) && ival == 123));
REQUIRE((itr->ConvertTo(uval) && uval == 123));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("uint64_t")
{
auto itr = b.push_back(b.end(), "", 123u);
REQUIRE(itr->GetUnchecked<uint64_t>() == 123u);
REQUIRE(!itr->ConvertTo(bval));
REQUIRE((itr->ConvertTo(fval) && fval == 123));
REQUIRE((itr->ConvertTo(ival) && ival == 123));
REQUIRE((itr->ConvertTo(uval) && uval == 123));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("double")
{
auto itr = b.push_back(b.end(), "", 123.0);
REQUIRE(itr->GetUnchecked<double>() == 123.0);
REQUIRE(!itr->ConvertTo(bval));
REQUIRE((itr->ConvertTo(fval) && fval == 123));
REQUIRE((itr->ConvertTo(ival) && ival == 123));
REQUIRE((itr->ConvertTo(uval) && uval == 123));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("string")
{
auto itr = b.push_back(b.end(), "", "ABC");
REQUIRE(itr->GetUnchecked<std::string_view>() == "ABC");
REQUIRE(!itr->ConvertTo(bval));
REQUIRE(!itr->ConvertTo(fval));
REQUIRE(!itr->ConvertTo(ival));
REQUIRE(!itr->ConvertTo(uval));
REQUIRE((itr->ConvertTo(sval) && sval == "ABC"));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("less than int64_t min as double")
{
auto itr = b.push_back(b.end(), "", -9223372036854777856.0);
REQUIRE(!itr->ConvertTo(bval));
REQUIRE((itr->ConvertTo(fval) && fval == -9223372036854777856.0));
REQUIRE(!itr->ConvertTo(ival));
REQUIRE(!itr->ConvertTo(uval));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("int64_t min")
{
// This value of -9223372036854775808 cannot be described as a literal
// due to negative literals being positive literals with a unary minus
// applied.
constexpr int64_t c_int64min = (-9223372036854775807ll - 1);
auto itr = b.push_back(b.end(), "", c_int64min);
REQUIRE(!itr->ConvertTo(bval));
REQUIRE((itr->ConvertTo(fval) && fval == -9223372036854775808.0));
REQUIRE((itr->ConvertTo(ival) && ival == c_int64min));
REQUIRE(!itr->ConvertTo(uval));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("-1")
{
auto itr = b.push_back(b.end(), "", -1);
REQUIRE(!itr->ConvertTo(bval));
REQUIRE((itr->ConvertTo(fval) && fval == -1.0));
REQUIRE((itr->ConvertTo(ival) && ival == -1));
REQUIRE(!itr->ConvertTo(uval));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("0")
{
auto itr = b.push_back(b.end(), "", 0);
REQUIRE(!itr->ConvertTo(bval));
REQUIRE((itr->ConvertTo(fval) && fval == 0.0));
REQUIRE((itr->ConvertTo(ival) && ival == 0));
REQUIRE((itr->ConvertTo(uval) && uval == 0));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("int64_t max")
{
auto itr = b.push_back(b.end(), "", 9223372036854775807);
REQUIRE(!itr->ConvertTo(bval));
REQUIRE((itr->ConvertTo(fval) && fval == 9223372036854775807.0));
REQUIRE((itr->ConvertTo(ival) && ival == 9223372036854775807));
REQUIRE((itr->ConvertTo(uval) && uval == 9223372036854775807));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("greater than int64_t max and less than uint64_t max")
{
auto itr = b.push_back(b.end(), "", 9223372036854775808ull);
REQUIRE(!itr->ConvertTo(bval));
REQUIRE((itr->ConvertTo(fval) && fval == 9223372036854775808.0));
REQUIRE(!itr->ConvertTo(ival));
REQUIRE((itr->ConvertTo(uval) && uval == 9223372036854775808ull));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("greater than int64_t max and less than uint64_t max as double")
{
auto itr = b.push_back(b.end(), "", 9223372036854777856.0);
REQUIRE(!itr->ConvertTo(bval));
REQUIRE((itr->ConvertTo(fval) && fval == 9223372036854777856.0));
REQUIRE(!itr->ConvertTo(ival));
REQUIRE((itr->ConvertTo(uval) && uval == 9223372036854777856ull));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("uint64_t max")
{
auto itr = b.push_back(b.end(), "", 18446744073709551615ull);
REQUIRE(!itr->ConvertTo(bval));
REQUIRE((itr->ConvertTo(fval) && fval == 18446744073709551615.0));
REQUIRE(!itr->ConvertTo(ival));
REQUIRE((itr->ConvertTo(uval) && uval == 18446744073709551615ull));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("greater than uint64_t max")
{
auto itr = b.push_back(b.end(), "", 18446744073709551616.0);
REQUIRE(!itr->ConvertTo(bval));
REQUIRE((itr->ConvertTo(fval) && fval == 18446744073709551615.0));
REQUIRE(!itr->ConvertTo(ival));
REQUIRE(!itr->ConvertTo(uval));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("time")
{
auto now = std::chrono::system_clock::now();
auto itr = b.push_back(b.end(), "", now);
REQUIRE(!itr->ConvertTo(bval));
REQUIRE(!itr->ConvertTo(fval));
REQUIRE(!itr->ConvertTo(ival));
REQUIRE(!itr->ConvertTo(uval));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE((itr->ConvertTo(tval) && tval == now));
REQUIRE(!itr->ConvertTo(uuidval));
}
SECTION("uuid")
{
UuidStruct uuid;
uuid_generate(uuid.Data);
auto itr = b.push_back(b.end(), "", uuid);
REQUIRE(!itr->ConvertTo(bval));
REQUIRE(!itr->ConvertTo(fval));
REQUIRE(!itr->ConvertTo(ival));
REQUIRE(!itr->ConvertTo(uval));
REQUIRE(!itr->ConvertTo(sval));
REQUIRE(!itr->ConvertTo(tval));
REQUIRE(
(itr->ConvertTo(uuidval) &&
0 == uuid_compare(uuidval.Data, uuid.Data)));
}
}
// int main()
// {
// JsonBuilder builder;
// builder.push_back(builder.end(), "field", 5l);
// builder.push_back(builder.end(), "String", "Grandes écoles");
// builder.push_back(
// builder.end(), "CurrentTime", std::chrono::system_clock::now());
// JsonRenderer renderer;
// renderer.Pretty(true);
// std::string_view output = renderer.Render(builder);
// std::cout << output << std::endl;
// return 0;
// }

239
test/TestRenderer.cpp Normal file
Просмотреть файл

@ -0,0 +1,239 @@
#include <cstdio>
#include <iterator>
#include <catch2/catch.hpp>
#include <jsonbuilder/JsonRenderer.h>
using namespace jsonbuilder;
template<class N>
static void TestUInt(N n)
{
unsigned const cchBuf = 24;
char buf1[cchBuf];
char buf2[cchBuf];
unsigned cch;
memset(buf1, 1, sizeof(buf1));
cch = JsonRenderUInt(n, buf1);
REQUIRE(cch < cchBuf);
for (unsigned i = cch + 1; i != cchBuf; i++)
{
REQUIRE(buf1[i] == 1);
}
std::snprintf(
buf2, std::size(buf2), "%llu", static_cast<long long unsigned>(n));
for (unsigned i = 0; i <= cch; i++)
{
REQUIRE(buf1[i] == buf2[i]);
}
}
template<class N>
static void TestInt(N n)
{
unsigned const cchBuf = 24;
char buf1[cchBuf];
char buf2[cchBuf];
unsigned cch;
memset(buf1, 1, sizeof(buf1));
cch = JsonRenderInt(n, buf1);
REQUIRE(cch < cchBuf);
for (unsigned i = cch + 1; i != cchBuf; i++)
{
REQUIRE(buf1[i] == 1);
}
std::snprintf(buf2, std::size(buf2), "%lld", static_cast<long long signed>(n));
for (unsigned i = 0; i <= cch; i++)
{
REQUIRE(buf1[i] == buf2[i]);
}
}
template<class N>
static void TestFloat(N n)
{
unsigned const cchBuf = 32;
char buf1[cchBuf];
char buf2[cchBuf];
unsigned cch;
memset(buf1, 1, sizeof(buf1));
cch = JsonRenderFloat(n, buf1);
REQUIRE(cch < cchBuf);
for (unsigned i = cch + 1; i != cchBuf; i++)
{
REQUIRE(buf1[i] == 1);
}
std::snprintf(buf2, std::size(buf2), "%.17g", static_cast<double>(n));
for (unsigned i = 0; i <= cch; i++)
{
REQUIRE(buf1[i] == buf2[i]);
}
}
static void TestBool(bool n)
{
unsigned const cchBuf = 6;
char buf1[cchBuf];
char const* buf2 = n ? "true" : "false";
memset(buf1, 1, sizeof(buf1));
unsigned cch = JsonRenderBool(n, buf1);
REQUIRE(cch < cchBuf);
for (unsigned i = cch + 1; i != cchBuf; i++)
{
REQUIRE(buf1[i] == 1);
}
for (unsigned i = 0; i <= cch; i++)
{
REQUIRE(buf1[i] == buf2[i]);
}
}
// TODO: Unused
// static void TestTime() {}
template<class N>
static void TestUInts()
{
SECTION("0") { TestUInt<N>(0); }
SECTION("min") { TestUInt<N>(std::numeric_limits<N>::min()); }
SECTION("max") { TestUInt<N>(std::numeric_limits<N>::max()); }
}
template<class N>
static void TestInts()
{
SECTION("0") { TestInt<N>(0); }
SECTION("min") { TestInt<N>(std::numeric_limits<N>::min()); }
SECTION("max") { TestInt<N>(std::numeric_limits<N>::max()); }
}
template<class N>
static void TestFloats()
{
SECTION("0") { TestFloat<N>(0); }
SECTION("min") { TestFloat<N>(std::numeric_limits<N>::min()); }
SECTION("max") { TestFloat<N>(std::numeric_limits<N>::max()); }
}
TEST_CASE("JsonRenderer values match printf", "[renderer]")
{
SECTION("signed char") { TestInts<signed char>(); }
SECTION("signed short") { TestInts<signed short>(); }
SECTION("signed int") { TestInts<signed int>(); }
SECTION("signed long") { TestInts<signed long>(); }
SECTION("signed long long") { TestInts<signed long long>(); }
SECTION("unsigned char") { TestUInts<unsigned char>(); }
SECTION("unsigned short") { TestUInts<unsigned short>(); }
SECTION("unsigned int") { TestUInts<unsigned int>(); }
SECTION("unsigned long") { TestUInts<unsigned long>(); }
SECTION("unsigned long long") { TestUInts<unsigned long long>(); }
SECTION("float") { TestFloats<float>(); }
SECTION("double") { TestFloats<double>(); }
SECTION("bool-false") { TestBool(false); }
SECTION("bool-true") { TestBool(true); }
}
TEST_CASE("JsonRenderer JsonNull")
{
unsigned const cchBuf = 24;
char buf1[cchBuf];
char const* buf2 = "null";
memset(buf1, 1, sizeof(buf1));
unsigned cch = JsonRenderNull(buf1);
REQUIRE(cch < cchBuf);
for (unsigned i = cch + 1; i != cchBuf; i++)
{
REQUIRE(buf1[i] == 1);
}
for (unsigned i = 0; i <= cch; i++)
{
REQUIRE(buf1[i] == buf2[i]);
}
}
using namespace std::string_view_literals;
TEST_CASE("JsonRenderer JsonTime", "[renderer]")
{
auto epoch = std::chrono::system_clock::from_time_t(0);
char chars[39];
memset(chars, 1, sizeof(chars));
unsigned cch = JsonRenderTime(epoch, chars);
REQUIRE(cch == strlen(chars));
REQUIRE(chars == "1970-01-01T00:00:00.0000000Z"sv);
}
TEST_CASE("JsonRenderer JsonUuid", "[renderer]")
{
uuid_t uuid;
for (int i = 0; i < 16; i++)
{
uuid[i] = i;
}
char chars[39];
memset(chars, 1, sizeof(chars));
SECTION("Without braces")
{
unsigned cch = JsonRenderUuid(uuid, chars);
REQUIRE(cch == strlen(chars));
REQUIRE(chars == "00010203-0405-0607-0809-0A0B0C0D0E0F"sv);
}
SECTION("With braces")
{
unsigned cch = JsonRenderUuidWithBraces(uuid, chars);
REQUIRE(cch == strlen(chars));
REQUIRE(chars == "{00010203-0405-0607-0809-0A0B0C0D0E0F}"sv);
}
}
TEST_CASE("JsonRenderer full object", "[renderer]")
{
JsonBuilder b;
auto objItr = b.push_back(b.end(), "obj", JsonObject);
b.push_back(objItr, "str", "strval");
b.push_back(objItr, "str2", "str2val");
auto arrItr = b.push_back(b.end(), "arr", JsonArray);
b.push_back(arrItr, "useless", 1);
b.push_back(arrItr, "useless2", 2);
SECTION("Default renderer")
{
JsonRenderer renderer;
auto renderString = renderer.Render(b);
REQUIRE(
renderString ==
R"({"obj":{"str":"strval","str2":"str2val"},"arr":[1,2]})");
}
SECTION("Pretty renderer")
{
JsonRenderer renderer;
renderer.Pretty(true);
auto renderString = renderer.Render(b);
REQUIRE(
renderString ==
R"({
"obj": {
"str": "strval",
"str2": "str2val"
},
"arr": [
1,
2
]
})");
}
}