Add Generic DOM visitor interface (#4852)

* Add Generic DOM visitor interface

Just the visitor interface from the [Generic DOM RFC](https://github.com/o3de/sig-content/blob/main/rfcs/rfc-10-generic-dom.md) with a few hardening changes so that we can align on it early:
- Clarified Lifetimes with an enum, extended it to cover the by-ref opaque values as well
- Added an explicit error type so that serializers can provide logging friendly rejections
- Did a first pass on documentation
- Added Visitor capabilities introspection and support for raw strings
This commit is contained in:
Nicholas Van Sickle
2021-10-27 08:46:31 -07:00
committed by GitHub
parent 97920feaf1
commit c5c043ecc5
3 changed files with 478 additions and 0 deletions
@@ -0,0 +1,239 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/DOM/DomVisitor.h>
namespace AZ::DOM
{
const char* VisitorError::CodeToString(VisitorErrorCode code)
{
switch (code)
{
case VisitorErrorCode::UnsupportedOperation:
return "operation not supported";
case VisitorErrorCode::InvalidData:
return "invalid data specified";
case VisitorErrorCode::InternalError:
return "internal error";
default:
return "unknown error";
}
}
VisitorError::VisitorError(VisitorErrorCode code)
: m_code(code)
{
}
VisitorError::VisitorError(VisitorErrorCode code, AZStd::string additionalInfo)
: m_code(code)
, m_additionalInfo(AZStd::move(additionalInfo))
{
}
VisitorErrorCode VisitorError::GetCode() const
{
return m_code;
}
const AZStd::string& VisitorError::GetAdditionalInfo() const
{
return m_additionalInfo;
}
AZStd::string VisitorError::FormatVisitorErrorMessage() const
{
if (m_additionalInfo.empty())
{
return AZStd::string::format("VisitorError: %s.", CodeToString(m_code));
}
return AZStd::string::format("VisitorError: %s. %s.", CodeToString(m_code), m_additionalInfo.c_str());
}
Visitor::Result Visitor::VisitorFailure(VisitorErrorCode code)
{
return AZ::Failure(VisitorError(code));
}
Visitor::Result Visitor::VisitorFailure(VisitorErrorCode code, AZStd::string additionalInfo)
{
return AZ::Failure(VisitorError(code, AZStd::move(additionalInfo)));
}
Visitor::Result Visitor::VisitorFailure(VisitorError error)
{
return AZ::Failure(error);
}
Visitor::Result Visitor::VisitorSuccess()
{
return AZ::Success();
}
Visitor::Result Visitor::Null()
{
return VisitorSuccess();
}
Visitor::Result Visitor::Bool([[maybe_unused]] bool value)
{
return VisitorSuccess();
}
Visitor::Result Visitor::Int64([[maybe_unused]] AZ::s64 value)
{
return VisitorSuccess();
}
Visitor::Result Visitor::Uint64([[maybe_unused]] AZ::u64 value)
{
return VisitorSuccess();
}
Visitor::Result Visitor::Double([[maybe_unused]] double value)
{
return VisitorSuccess();
}
Visitor::Result Visitor::String([[maybe_unused]] AZStd::string_view value, [[maybe_unused]] Lifetime lifetime)
{
return VisitorSuccess();
}
Visitor::Result Visitor::OpaqueValue([[maybe_unused]] const OpaqueType& value, [[maybe_unused]] Lifetime lifetime)
{
if (!SupportsOpaqueValues())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Opaque values are not supported by this visitor");
}
return VisitorSuccess();
}
Visitor::Result Visitor::RawValue([[maybe_unused]] AZStd::string_view value, [[maybe_unused]] Lifetime lifetime)
{
if (!SupportsRawValues())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Raw values are not supported by this visitor");
}
return VisitorSuccess();
}
Visitor::Result Visitor::StartObject()
{
if (!SupportsObjects())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Objects are not supported by this visitor");
}
return VisitorSuccess();
}
Visitor::Result Visitor::EndObject([[maybe_unused]] AZ::u64 attributeCount)
{
if (!SupportsObjects())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Objects are not supported by this visitor");
}
return VisitorSuccess();
}
Visitor::Result Visitor::Key([[maybe_unused]] AZ::Name key)
{
if (!SupportsObjects() && !SupportsNodes())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Keys are not supported by this visitor");
}
return VisitorSuccess();
}
Visitor::Result Visitor::RawKey(AZStd::string_view key, [[maybe_unused]] Lifetime lifetime)
{
if (!SupportsRawKeys())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Raw keys are not supported by this visitor");
}
return Key(AZ::Name(key));
}
Visitor::Result Visitor::StartArray()
{
if (!SupportsArrays())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Arrays are not supported by this visitor");
}
return VisitorSuccess();
}
Visitor::Result Visitor::EndArray([[maybe_unused]] AZ::u64 elementCount)
{
if (!SupportsArrays())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Arrays are not supported by this visitor");
}
return VisitorSuccess();
}
Visitor::Result Visitor::StartNode([[maybe_unused]] AZ::Name name)
{
if (!SupportsNodes())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Nodes are not supported by this visitor");
}
return VisitorSuccess();
}
Visitor::Result Visitor::RawStartNode(AZStd::string_view name, [[maybe_unused]] Lifetime lifetime)
{
return StartNode(AZ::Name(name));
}
Visitor::Result Visitor::EndNode([[maybe_unused]] AZ::u64 attributeCount, [[maybe_unused]] AZ::u64 elementCount)
{
if (!SupportsNodes())
{
return VisitorFailure(VisitorErrorCode::UnsupportedOperation, "Nodes are not supported by this visitor");
}
return VisitorSuccess();
}
VisitorFlags Visitor::GetVisitorFlags() const
{
// By default support raw keys (promoting them to AZ::Name) and support Array / Object / Node
// We leave Opaque type support and Raw Values to more specialized, implementation-specific cases
return VisitorFlags::SupportsRawKeys | VisitorFlags::SupportsArrays | VisitorFlags::SupportsObjects | VisitorFlags::SupportsNodes;
}
bool Visitor::SupportsRawValues() const
{
return (GetVisitorFlags() & VisitorFlags::SupportsRawValues) != VisitorFlags::Null;
}
bool Visitor::SupportsRawKeys() const
{
return (GetVisitorFlags() & VisitorFlags::SupportsRawKeys) != VisitorFlags::Null;
}
bool Visitor::SupportsObjects() const
{
return (GetVisitorFlags() & VisitorFlags::SupportsObjects) != VisitorFlags::Null;
}
bool Visitor::SupportsArrays() const
{
return (GetVisitorFlags() & VisitorFlags::SupportsArrays) != VisitorFlags::Null;
}
bool Visitor::SupportsNodes() const
{
return (GetVisitorFlags() & VisitorFlags::SupportsNodes) != VisitorFlags::Null;
}
bool Visitor::SupportsOpaqueValues() const
{
return (GetVisitorFlags() & VisitorFlags::SupportsOpaqueValues) != VisitorFlags::Null;
}
} // namespace AZ::DOM
@@ -0,0 +1,237 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Name/Name.h>
#include <AzCore/Outcome/Outcome.h>
#include <AzCore/std/any.h>
#include <AzCore/std/string/string.h>
namespace AZ::DOM
{
//
// Lifetime enum
//
//! Specifies the period in which a reference value will still be alive and safe to read.
enum class Lifetime
{
//! Specifies that the value is safe to read and will remain so indefinitely.
//! This implies that the value will not be mutated for the duration of this storage.
Persistent,
//! Specifies that the value may change or be deallocated, and must be copied to be safely stored.
Temporary,
};
//
// VisitorErrorCode enum
//
//! Error code specifying the reason a Visitor operation failed.
enum class VisitorErrorCode
{
//! Set when a Visitor doesn't have an implementation for a given attribute type.
//! A pure-JSON serializer might reject a Node attribute, for example, and serialization visitors
//! can forbid non-serializable Opaque types.
UnsupportedOperation,
//! Set when a Visitor has received malformed or invalid data.
//! Potential sources include mismatching Begin/End call pairs or invalid attribute or element counts
//! being sent to End methods.
InvalidData,
//! The Visitor failed for some other reason not caused by invalid input.
//! If returning a custom error with this code, it's preferrable to also provide supplemental info
//! in the form of an explanatory string.
InternalError
};
//
// VisitorError class
//
//! Details of the reason for failure within a VisitorInterface operation.
class VisitorError final
{
public:
explicit VisitorError(VisitorErrorCode code);
VisitorError(VisitorErrorCode code, AZStd::string additionalInfo);
//! Gets the error code associated with this error.
VisitorErrorCode GetCode() const;
//! Gets a supplemental error info string from the error.
//! Returns an empty string if no additional information was provided to the error.
const AZStd::string& GetAdditionalInfo() const;
//! Provides a formatted, human-readable error description that can be used for logging purposes.
AZStd::string FormatVisitorErrorMessage() const;
//! Helper method, translates a VisitorErrorCode to a human readable string.
static const char* CodeToString(VisitorErrorCode code);
private:
VisitorErrorCode m_code;
AZStd::string m_additionalInfo;
};
//! A type alias for opaque DOM types that aren't meant to be serializable.
//! /see VisitorInterface::OpaqueValue
using OpaqueType = AZStd::any;
//
// VisitorFlags enum
//
//! Flags representning capabilities of a \ref Visitor.
enum class VisitorFlags : AZ::u16
{
//! No flags are set. This can be used in conjunction with bitwise operators to check a flag.
Null = 0,
//! If set, this Visitor interface supports raw strings in place of specific value types.
//! Visitors with this flag accept RawValue calls in lieu of more specific value calls such as Int64 or String.
SupportsRawValues = (1 << 1),
//! If set, this Visitor interface supports raw strings in place of Name types for keys and Node names.
//! Visitors with this flag accept RawKey and RawStartNode in lieu of Key and StartNode calls.
SupportsRawKeys = (1 << 2),
//! If set, this Visitor interface supports Object types described via BeginObject and EndObject.
SupportsObjects = (1 << 3),
//! If set, this Visitor interface supports Array types described via BeginArray and EndArray.
SupportsArrays = (1 << 4),
//! If set, this Visitor interface supports Node types described BeginNode and EndNode.
SupportsNodes = (1 << 4),
//! If set, this Visitor interface supports opaque values described via OpaqueValue.
SupportsOpaqueValues = (1 << 5),
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(VisitorFlags);
//
// Visitor class
//
//! An interface for performing operations on elements of a generic DOM (Document Object Model).
//! A Document Object Model is defined here as a tree structure comprised of one of the following values:
//! - Primitives: plain data types, including
//! - \ref Int64: 64 bit signed integer
//! - \ref Uint64: 64 bit unsigned integer
//! - \ref Bool: boolean value
//! - \ref Double: 64 bit double precision float
//! - \ref Null: sentinel "empty" type with no value representation
//! - \ref String: UTF8 encoded string
//! - \ref Object: an ordered container of key/value pairs where keys are AZ::Names and values may be any DOM type
//! (including Object)
//! - \ref Array: an ordered container of values, in which values are any DOM value type (including Array)
//! - \ref Node: a container
//! - \ref OpaqueValue: An arbitrary value stored in an AZStd::any. This is a non-serializable representation of an
//! entry useful for in-memory options. This is intended to be used as an intermediate value over the course of DOM
//! transformation and as a proxy to pass through types of which the DOM has no knowledge to other systems.
//!
//! Opaque values are rejected by the default VisitorInterface implementation.
//!
//! Care should be ensured that DOMs representing opaque types are only visited by consumers that understand them.
class Visitor
{
public:
virtual ~Visitor() = default;
//! The result of a Visitor operation.
//! A failure indicates a non-recoverable issue and signals that no further visit calls may be made in the
//! current state.
using Result = AZ::Outcome<void, VisitorError>;
//! Returns a set of flags representing the operations this Visitor supports.
//! The base implementation supports raw keys (\see VisitorFlags::SupportsRawKeys) and
//! arrays (\see VisitorFlags::SupportsArrays), objects (\see VisitorFlags::SupportsObjects), and
//! nodes (\see VisitorFlags::SupportsNodes).
//! Raw (\see VisitorFlags::SupportsRawValues) and opaque values (\see VisitorFlags::SupportsOpaqueValues)
//! are disallowed by default, as their handling is intended to be implementation-specific.
virtual VisitorFlags GetVisitorFlags() const;
//! /see VisitorFlags::SupportsRawValues
bool SupportsRawValues() const;
//! /see VisitorFlags::SupportsRawKeys
bool SupportsRawKeys() const;
//! /see VisitorFlags::SupportsObjects
bool SupportsObjects() const;
//! /see VisitorFlags::SupportsArrays
bool SupportsArrays() const;
//! /see VisitorFlags::SupportsNodes
bool SupportsNodes() const;
//! /see VisitorFlags::SupportsOpaqueValues
bool SupportsOpaqueValues() const;
//! Operates on an empty null value.
virtual Result Null();
//! Operates on a bool value.
virtual Result Bool(bool value);
//! Operates on a signed, 64 bit integer value.
virtual Result Int64(AZ::s64 value);
//! Operates on an unsigned, 64 bit integer value.
virtual Result Uint64(AZ::u64 value);
//! Operates on a double precision, 64 bit floating point value.
virtual Result Double(double value);
//! Operates on a string value. As strings are a reference type.
//! Storage semantics are provided to indicate where the value may be stored persistently or requires a copy.
virtual Result String(AZStd::string_view value, Lifetime lifetime);
//! Operates on an opaque value. As opaque values are a reference type, storage semantics are provided to
//! indicate where the value may be stored persistently or requires a copy.
//! The base implementation of OpaqueValue rejects the operation, as opaque values are meant for special
//! cases with specific implementations, not generic usage.
//! Storage semantics are provided to indicate where the value may be stored persistently or requires a copy.
virtual Result OpaqueValue(const OpaqueType& value, Lifetime lifetime);
//! Operates on a raw value encoded as a UTF-8 string that hasn't had its type deduced.
//! Visitors that support raw values (\see VisitorFlags::SupportsRawValues) may parse the raw value and
//! forward it to the corresponding value call or calls of their choice.
//! The base implementation of RawValue rejects the operation, as raw values are meant to be handled on
//! a per-implementation basis.
virtual Result RawValue(AZStd::string_view value, Lifetime lifetime);
//! Operates on an Object.
//! Callers may make any number of Key calls, followed by calls representing a value (including a nested
//! StartObject call) and then must call EndObject.
virtual Result StartObject();
//! Finishes operating on an Object.
//! Callers must provide the number of attributes that were provided to the object, i.e. the number of key
//! and value calls made within the direct context of this object (but not any nested objects / nodes).
virtual Result EndObject(AZ::u64 attributeCount);
//! Specifies a key for a key/value pair.
//! Key must be called subsequent to a call to \ref StartObject or \ref StartNode and immediately followed by
//! calls representing the key's associated value.
virtual Result Key(AZ::Name key);
//! Specifies a key for a key/value pair using a raw string instead of \ref AZ::Name.
//! \see Key
virtual Result RawKey(AZStd::string_view key, Lifetime lifetime);
//! Operates on an Array.
//! Callers may make any number of subsequent value calls to represent the elements of the array, and then must
//! call EndArray.
virtual Result StartArray();
//! Finishes operating on an Array.
//! Callers must provide the number of elements that were provided to the array, i.e. the number of value calls
//! made within the direct context of this array (but not any nested arrays / nodes).
virtual Result EndArray(AZ::u64 elementCount);
//! Operates on a Node.
//! Callers may make any number of Key calls followed by value calls or value calls not prefixed with a Key
//! call, and then must call EndNode. See \ref StartObject and \ref StartArray as Node types combine the
//! functionality of both structures into a named Node structure.
virtual Result StartNode(AZ::Name name);
//! Operates on a Node using a raw string instead of \ref AZ::Name.
//! \see StartNode
virtual Result RawStartNode(AZStd::string_view name, Lifetime lifetime);
//! Finishes operating on a Node.
//! Callers must provide both the number of attributes the were provided and the number of elements that were
//! provided to the node, attributes being values prefaced by a call to Key.
virtual Result EndNode(AZ::u64 attributeCount, AZ::u64 elementCount);
protected:
Visitor() = default;
//! Helper method, constructs a failure \ref Result with the specified code.
static Result VisitorFailure(VisitorErrorCode code);
//! Helper method, constructs a failure \ref Result with the specified code and supplemental info.
static Result VisitorFailure(VisitorErrorCode code, AZStd::string additionalInfo);
//! Helper method, constructs a failure \ref Result with the specified error.
static Result VisitorFailure(VisitorError error);
//! Helper method, constructs a success \ref Result.
static Result VisitorSuccess();
};
} // namespace AZ::DOM
@@ -123,6 +123,8 @@ set(FILES
Debug/TraceMessagesDrillerBus.h
Debug/TraceReflection.cpp
Debug/TraceReflection.h
DOM/DomVisitor.cpp
DOM/DomVisitor.h
Driller/DefaultStringPool.h
Driller/Driller.cpp
Driller/Driller.h