omni/structuredlog/JsonTreeSerializer.h

File members: omni/structuredlog/JsonTreeSerializer.h

// Copyright (c) 2020-2023, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once
#include "JsonSerializer.h"
#include "BinarySerializer.h"
#include "JsonTree.h"

namespace omni
{
namespace structuredlog
{

static inline void ignoreJsonTreeSerializerValidationError(const char* s)
{
    CARB_UNUSED(s);
}

template <bool validate = false,
          typename T,
          typename JsonSerializerType = JsonSerializer<false, false, ignoreJsonTreeSerializerValidationError>,
          typename JsonNodeType = JsonNode,
          typename BlobReaderType = BlobReader<false, ignoreJsonTreeSerializerValidationError>>
static inline bool serializeScalar(JsonSerializerType* serial, const JsonNodeType* root, T constVal, BlobReaderType* reader)
{
    if ((root->flags & JsonNode::fFlagConst) != 0)
    {
        return serial->writeValue(constVal);
    }
    else
    {
        T b = {};
        bool result = reader->read(&b);
        if (validate && !result)
            return false;

        return serial->writeValue(b);
    }
}

template <bool validate = false,
          typename T,
          typename JsonSerializerType = JsonSerializer<false, false, ignoreJsonTreeSerializerValidationError>,
          typename JsonNodeType = JsonNode,
          typename BlobReaderType = BlobReader<false, ignoreJsonTreeSerializerValidationError>>
static inline bool serializeArray(JsonSerializerType* serial,
                                  const JsonNodeType* root,
                                  const T* constVal,
                                  BlobReaderType* reader)
{
    bool result = true;

    result = serial->openArray();
    if (validate && !result)
        return false;

    if ((root->flags & JsonNode::fFlagConst) != 0)
    {
        for (uint16_t i = 0; i < root->len; i++)
        {
            result = serial->writeValue(constVal[i]);
            if (validate && !result)
                return false;
        }
    }
    else
    {
        const T* b = nullptr;
        uint16_t len = 0;

        if ((root->flags & JsonNode::fFlagFixedLength) != 0)
        {
            len = root->len;
            result = reader->read(&b, len);
            if (validate && !result)
                return false;
        }
        else
        {
            result = reader->read(&b, &len);
            if (validate && !result)
                return false;
        }

        for (uint16_t i = 0; i < len; i++)
        {
            result = serial->writeValue(b[i]);
            if (validate && !result)
                return false;
        }
    }

    return serial->closeArray();
}

template <bool validate = false,
          typename T,
          typename JsonSerializerType = JsonSerializer<false, false, ignoreJsonTreeSerializerValidationError>,
          typename JsonNodeType = JsonNode,
          typename BlobReaderType = BlobReader<false, ignoreJsonTreeSerializerValidationError>>
static inline bool serializeEnum(JsonSerializerType* serial, const JsonNodeType* root, T* enumChoices, BlobReaderType* reader)
{
    JsonNode::EnumBase b = {};
    bool result = reader->read(&b);
    if (validate && !result)
        return false;

    if (b > root->len)
    {
        char tmp[256];
        carb::extras::formatString(tmp, sizeof(tmp),
                                   "enum value is out of range"
                                   " {value = %" PRIu16 ", max = %" PRIu16 "}",
                                   b, root->len);
        serial->m_onValidationError(tmp);
        return false;
    }

    return serial->writeValue(enumChoices[b]);
}

template <bool validate = false,
          typename JsonSerializerType = JsonSerializer<false, false, ignoreJsonTreeSerializerValidationError>,
          typename JsonNodeType = JsonNode,
          typename BlobReaderType = BlobReader<false, ignoreJsonTreeSerializerValidationError>>
static inline bool serializeJsonTree(JsonSerializerType* serial, const JsonNodeType* root, BlobReaderType* reader)
{
    bool result = true;

    if (root->name != nullptr)
    {
        result = serial->writeKey(root->name, root->nameLen - 1);
        if (validate && !result)
            return false;
    }

    switch (root->type)
    {
        case NodeType::eNull:
            return serial->writeValue();

        case NodeType::eBool:
            return serializeScalar<validate>(serial, root, root->data.boolVal, reader);

        case NodeType::eInt32:
            return serializeScalar<validate>(serial, root, int32_t(root->data.intVal), reader);

        case NodeType::eUint32:
            return serializeScalar<validate>(serial, root, uint32_t(root->data.uintVal), reader);

        case NodeType::eInt64:
            return serializeScalar<validate>(serial, root, root->data.intVal, reader);

        case NodeType::eUint64:
            return serializeScalar<validate>(serial, root, root->data.uintVal, reader);

        case NodeType::eFloat32:
            return serializeScalar<validate>(serial, root, float(root->data.floatVal), reader);

        case NodeType::eFloat64:
            return serializeScalar<validate>(serial, root, root->data.floatVal, reader);

        case NodeType::eBinary:
            if ((root->flags & JsonNode::fFlagConst) != 0)
            {
                return serial->writeValueWithBase64Encoding(root->data.binaryVal, root->len);
            }
            else
            {
                const uint8_t* b = nullptr;
                uint16_t len = 0;

                if ((root->flags & JsonNode::fFlagFixedLength) != 0)
                {
                    len = root->len;
                    result = reader->read(&b, len);
                }
                else
                {
                    result = reader->read(&b, &len);
                }

                if (validate && !result)
                    return false;

                // null terminator is included in the length
                return serial->writeValueWithBase64Encoding(b, len);
            }

        case NodeType::eBoolArray:
            if ((root->flags & JsonNode::fFlagEnum) != 0)
                return serializeEnum<validate>(serial, root, root->data.boolArrayVal, reader);
            else
                return serializeArray<validate>(serial, root, root->data.boolArrayVal, reader);

        case NodeType::eInt32Array:
            if ((root->flags & JsonNode::fFlagEnum) != 0)
                return serializeEnum<validate>(serial, root, root->data.int32ArrayVal, reader);
            else
                return serializeArray<validate>(serial, root, root->data.int32ArrayVal, reader);

        case NodeType::eUint32Array:
            if ((root->flags & JsonNode::fFlagEnum) != 0)
                return serializeEnum<validate>(serial, root, root->data.uint32ArrayVal, reader);
            else
                return serializeArray<validate>(serial, root, root->data.uint32ArrayVal, reader);

        case NodeType::eInt64Array:
            if ((root->flags & JsonNode::fFlagEnum) != 0)
                return serializeEnum<validate>(serial, root, root->data.int64ArrayVal, reader);
            else
                return serializeArray<validate>(serial, root, root->data.int64ArrayVal, reader);

        case NodeType::eUint64Array:
            if ((root->flags & JsonNode::fFlagEnum) != 0)
                return serializeEnum<validate>(serial, root, root->data.uint64ArrayVal, reader);
            else
                return serializeArray<validate>(serial, root, root->data.uint64ArrayVal, reader);

        case NodeType::eFloat32Array:
            if ((root->flags & JsonNode::fFlagEnum) != 0)
                return serializeEnum<validate>(serial, root, root->data.float32ArrayVal, reader);
            else
                return serializeArray<validate>(serial, root, root->data.float32ArrayVal, reader);

        case NodeType::eFloat64Array:
            if ((root->flags & JsonNode::fFlagEnum) != 0)
                return serializeEnum(serial, root, root->data.float64ArrayVal, reader);
            else
                return serializeArray(serial, root, root->data.float64ArrayVal, reader);

        case NodeType::eString:
            if ((root->flags & JsonNode::fFlagConst) != 0)
            {
                return serial->writeValue(root->data.strVal, (root->len == 0) ? 0 : root->len - 1);
            }
            else
            {
                const char* b = nullptr;
                uint16_t len = 0;

                if ((root->flags & JsonNode::fFlagFixedLength) != 0)
                {
                    len = root->len;
                    result = reader->read(&b, len);
                }
                else
                {
                    result = reader->read(&b, &len);
                }

                if (validate && !result)
                    return false;

                // null terminator is included in the length
                return serial->writeValue(b, (len == 0) ? 0 : len - 1);
            }

        case NodeType::eStringArray:
            if ((root->flags & JsonNode::fFlagEnum) != 0)
                return serializeEnum<validate>(serial, root, root->data.strArrayVal, reader);

            result = serial->openArray();
            if (validate && !result)
                return false;

            if ((root->flags & JsonNode::fFlagConst) != 0)
            {
                for (uint16_t i = 0; i < root->len; i++)
                {
                    result = serial->writeValue(root->data.strArrayVal[i]);
                    if (validate && !result)
                        return false;
                }
            }
            else
            {
                const char** b = nullptr;
                uint16_t len = 0;

                // fixed length isn't supported here
                result = reader->read(b, &len, 0);
                if (validate && !result)
                    return false;

                // FIXME: dangerous
                b = static_cast<const char**>(alloca(len * sizeof(*b)));
                result = reader->read(b, &len, len);
                if (validate && !result)
                    return false;

                for (uint16_t i = 0; i < len; i++)
                {
                    result = serial->writeValue(b[i]);

                    if (validate && !result)
                        return false;
                }
            }

            return serial->closeArray();

        case NodeType::eObject:
            result = serial->openObject();
            if (validate && !result)
                return false;

            for (uint16_t i = 0; i < root->len; i++)
            {
                result = serializeJsonTree<validate>(serial, &root->data.objVal[i], reader);
                if (validate && !result)
                    return false;
            }

            return serial->closeObject();

        case NodeType::eObjectArray:
            result = serial->openArray();
            if (validate && !result)
                return false;

            if ((root->flags & JsonNode::fFlagFixedLength) != 0)
            {
                for (uint16_t i = 0; i < root->len; i++)
                {
                    result = serializeJsonTree<validate>(serial, &root->data.objVal[i], reader);
                    if (validate && !result)
                        return false;
                }
            }
            else
            {
                uint16_t len = 0;

                // read the array length
                result = reader->read(&len);
                if (validate && !result)
                    return false;

                // a variable length object array uses the same object schema for
                // each object in the array, so we just pass the 0th element here
                for (uint16_t i = 0; i < len; i++)
                {
                    result = serializeJsonTree<validate>(serial, &root->data.objVal[0], reader);
                    if (validate && !result)
                        return false;
                }
            }

            return serial->closeArray();
    }

    return false;
}

template <bool validate = false,
          typename JsonSerializerType = JsonSerializer<false, false, ignoreJsonTreeSerializerValidationError>,
          typename JsonNodeType = JsonNode,
          typename BlobReaderType = BlobReader<false, ignoreJsonTreeSerializerValidationError>>
static inline bool serializeJsonTree(JsonSerializerType* serial, const JsonNodeType* root, const void* blob, size_t blobSize)
{
    BlobReaderType reader(blob, blobSize);
    return serializeJsonTree<validate>(serial, root, &reader) && serial->finish();
}

/* we don't extract static symbols so this breaks exhale somehow */
#ifndef DOXYGEN_SHOULD_SKIP_THIS

template <typename JsonSerializerType = JsonSerializer<true, true>, typename JsonNodeType = JsonNode>
static inline void serializeJsonSchema_(JsonSerializerType* serial, const JsonNodeType* root)
{
    auto nodeTypeString = [](NodeType n) -> const char* {
        switch (n)
        {
            case NodeType::eNull:
                return "null";
            case NodeType::eBool:
                return "boolean";
            case NodeType::eInt32:
                return "integer";
            case NodeType::eUint32:
                return "uint32";
            case NodeType::eInt64:
                return "int64";
            case NodeType::eUint64:
                return "uint64";
            case NodeType::eFloat32:
                return "float";
            case NodeType::eFloat64:
                return "double";
            case NodeType::eBinary:
                return "binary";
            case NodeType::eBoolArray:
                return "bool[]";
            case NodeType::eInt32Array:
                return "integer[]";
            case NodeType::eUint32Array:
                return "uint32[]";
            case NodeType::eInt64Array:
                return "int64[]";
            case NodeType::eUint64Array:
                return "uint64[]";
            case NodeType::eFloat32Array:
                return "float[]";
            case NodeType::eFloat64Array:
                return "double[]";
            case NodeType::eString:
                return "string";
            case NodeType::eStringArray:
                return "string[]";
            case NodeType::eObject:
                return "object";
            case NodeType::eObjectArray:
                return "object[]";
        }
        return "unknown";
    };

    if (root->name != nullptr)
    {
        serial->writeKey(root->name, root->nameLen - 1);
    }

    serial->openObject();
    serial->writeKey("type");
    serial->writeValue(nodeTypeString(root->type));

    serial->writeKey("flags");
    serial->writeValue(root->flags);

    if ((root->flags & JsonNode::fFlagConst) != 0)
    {
        serial->writeKey("const");
        switch (root->type)
        {
            case NodeType::eNull:
                serial->writeValue();
                break;
            case NodeType::eBool:
                serial->writeValue(root->data.boolVal);
                break;
            case NodeType::eInt32:
                serial->writeValue(root->data.intVal);
                break;
            case NodeType::eUint32:
                serial->writeValue(root->data.uintVal);
                break;
            case NodeType::eInt64:
                serial->writeValue(root->data.intVal);
                break;
            case NodeType::eUint64:
                serial->writeValue(root->data.uintVal);
                break;
            case NodeType::eFloat32:
                serial->writeValue(root->data.floatVal);
                break;
            case NodeType::eFloat64:
                serial->writeValue(root->data.floatVal);
                break;
            case NodeType::eBinary:
                serial->writeValueWithBase64Encoding(root->data.binaryVal, root->len);
                break;
            case NodeType::eBoolArray:
                serial->openArray();
                for (uint16_t i = 0; i < root->len; i++)
                {
                    serial->writeValue(root->data.boolArrayVal[i]);
                }
                serial->closeArray();
                break;
            case NodeType::eInt32Array:
                serial->openArray();
                for (uint16_t i = 0; i < root->len; i++)
                {
                    serial->writeValue(root->data.int32ArrayVal[i]);
                }
                serial->closeArray();
                break;
            case NodeType::eUint32Array:
                serial->openArray();
                for (uint16_t i = 0; i < root->len; i++)
                {
                    serial->writeValue(root->data.uint32ArrayVal[i]);
                }
                serial->closeArray();
                break;
            case NodeType::eInt64Array:
                serial->openArray();
                for (uint16_t i = 0; i < root->len; i++)
                {
                    serial->writeValue(root->data.int64ArrayVal[i]);
                }
                serial->closeArray();
                break;
            case NodeType::eUint64Array:
                serial->openArray();
                for (uint16_t i = 0; i < root->len; i++)
                {
                    serial->writeValue(root->data.uint64ArrayVal[i]);
                }
                serial->closeArray();
                break;
            case NodeType::eFloat32Array:
                serial->openArray();
                for (uint16_t i = 0; i < root->len; i++)
                {
                    serial->writeValue(root->data.float32ArrayVal[i]);
                }
                serial->closeArray();
                break;
            case NodeType::eFloat64Array:
                serial->openArray();
                for (uint16_t i = 0; i < root->len; i++)
                {
                    serial->writeValue(root->data.float64ArrayVal[i]);
                }
                serial->closeArray();
                break;
            case NodeType::eString:
                serial->writeValue(root->data.strVal, (root->len == 0) ? 0 : root->len - 1);
                break;
            case NodeType::eStringArray:
                serial->openArray();
                for (uint16_t i = 0; i < root->len; i++)
                {
                    serial->writeValue(root->data.strArrayVal[i]);
                }
                serial->closeArray();
                break;
            case NodeType::eObject:
                serial->writeValue();
                break;
            case NodeType::eObjectArray:
                serial->openArray();
                serial->closeArray();
                break;
        }
    }

    if ((root->flags & JsonNode::fFlagEnum) != 0)
    {
        serial->writeKey("enum");
        serial->writeValue(true);
    }

    if (root->type == NodeType::eObject || root->type == NodeType::eObjectArray)
    {
        serial->writeKey("properties");
        serial->openObject();
        for (size_t i = 0; i < root->len; i++)
        {
            serializeJsonSchema_(serial, &root->data.objVal[i]);
        }
        serial->closeObject();
    }

    serial->closeObject();
}

template <typename JsonSerializerType = JsonSerializer<true, true>, typename JsonNodeType = JsonNode>
static inline void serializeJsonSchema(JsonSerializerType* serial, const JsonNodeType* root)
{
    serializeJsonSchema_(serial, root);
    serial->finish();
}
#endif

} // namespace structuredlog
} // namespace omni