aboutsummaryrefslogblamecommitdiffstats
path: root/yt/cpp/mapreduce/interface/fluent.h
blob: a890b5a86b9be4df018b188fb59c299de476b515 (plain) (tree)





















































































































































































































































































































































































                                                                                                             
                                                                                                    












































































































































































































































































































                                                                                                           
#pragma once

///
/// @file yt/cpp/mapreduce/interface/fluent.h
///
/// Adapters for working with @ref NYson::IYsonConsumer in a structured way, with compile-time syntax checks.
///
/// The following documentation is copied verbatim from `yt/core/ytree/fluent.h`.
///
/// WHAT IS THIS
///
/// Fluent adapters encapsulate invocation of IYsonConsumer methods in a
/// convenient structured manner. Key advantage of fluent-like code is that
/// attempt of building syntactically incorrect YSON structure will result
/// in a compile-time error.
///
/// Each fluent object is associated with a context that defines possible YSON
/// tokens that may appear next. For example, TFluentMap is a fluent object
/// that corresponds to a location within YSON map right before a key-value
/// pair or the end of the map.
///
/// More precisely, each object that may be obtained by a sequence of fluent
/// method calls has the full history of its enclosing YSON composite types in
/// its single template argument hereinafter referred to as TParent. This allows
/// us not to forget the original context after opening and closing the embedded
/// composite structure.
///
/// It is possible to invoke a separate YSON building procedure by calling
/// one of convenience Do* methods. There are two possibilities here: it is
/// possible to delegate invocation context either as a fluent object (like
/// TFluentMap, TFluentList, TFluentAttributes or TFluentAny) or as a raw
/// IYsonConsumer*. The latter is discouraged since it is impossible to check
/// if a given side-built YSON structure fits current fluent context.
/// For example it is possible to call Do() method inside YSON map passing
/// consumer to a procedure that will treat context like it is in a list.
/// Passing typed fluent builder saves you from such a misbehaviour.
///
/// TFluentXxx corresponds to an internal class of TXxx
/// without any history hidden in template argument. It allows you to
/// write procedures of form:
///
///   void BuildSomeAttributesInYson(TFluentMap fluent) { ... }
///
/// without thinking about the exact way how this procedure is nested in other
/// procedures.
///
/// An important notation: we will refer to a function whose first argument
/// is TFluentXxx as TFuncXxx.
///
///
/// BRIEF LIST OF AVAILABLE METHODS
///
/// Only the most popular methods are covered here. Refer to the code for the
/// rest of them.
///
/// TAny:
/// * Value(T value) -> TParent, serialize `value` using underlying consumer.
///   T should be such that free function Serialize(NYson::IYsonConsumer*, const T&) is
///   defined;
/// * BeginMap() -> TFluentMap, open map;
/// * BeginList() -> TFluentList, open list;
/// * BeginAttributes() -> TFluentAttributes, open attributes;
///
/// * Do(TFuncAny func) -> TAny, delegate invocation to a separate procedure.
/// * DoIf(bool condition, TFuncAny func) -> TAny, same as Do() but invoke
///   `func` only if `condition` is true;
/// * DoFor(TCollection collection, TFuncAny func) -> TAny, same as Do()
///   but iterate over `collection` and pass each of its elements as a second
///   argument to `func`. Instead of passing a collection you may it is possible
///   to pass two iterators as an argument;
///
/// * DoMap(TFuncMap func) -> TAny, open a map, delegate invocation to a separate
///   procedure and close map;
/// * DoMapFor(TCollection collection, TFuncMap func) -> TAny, open a map, iterate
///   over `collection` and pass each of its elements as a second argument to `func`
///   and close map;
/// * DoList(TFuncList func) -> TAny, same as DoMap();
/// * DoListFor(TCollection collection, TFuncList func) -> TAny; same as DoMapFor().
///
///
/// TFluentMap:
/// * Item(TStringBuf key) -> TAny, open an element keyed with `key`;
/// * EndMap() -> TParent, close map;
/// * Do(TFuncMap func) -> TFluentMap, same as Do() for TAny;
/// * DoIf(bool condition, TFuncMap func) -> TFluentMap, same as DoIf() for TAny;
/// * DoFor(TCollection collection, TFuncMap func) -> TFluentMap, same as DoFor() for TAny.
///
///
/// TFluentList:
/// * Item() -> TAny, open an new list element;
/// * EndList() -> TParent, close list;
/// * Do(TFuncList func) -> TFluentList, same as Do() for TAny;
/// * DoIf(bool condition, TFuncList func) -> TFluentList, same as DoIf() for TAny;
/// * DoFor(TCollection collection, TListMap func) -> TFluentList, same as DoFor() for TAny.
///
///
/// TFluentAttributes:
/// * Item(TStringBuf key) -> TAny, open an element keyed with `key`.
/// * EndAttributes() -> TParentWithoutAttributes, close attributes. Note that
///   this method leads to a context that is forces not to have attributes,
///   preventing us from putting attributes twice before an object.
/// * Do(TFuncAttributes func) -> TFluentAttributes, same as Do() for TAny;
/// * DoIf(bool condition, TFuncAttributes func) -> TFluentAttributes, same as DoIf()
///   for TAny;
/// * DoFor(TCollection collection, TListAttributes func) -> TFluentAttributes, same as DoFor()
///   for TAny.
///


#include "common.h"
#include "serialize.h"

#include <library/cpp/yson/node/serialize.h>
#include <library/cpp/yson/node/node_builder.h>

#include <library/cpp/yson/consumer.h>
#include <library/cpp/yson/writer.h>

#include <util/generic/noncopyable.h>
#include <util/generic/ptr.h>
#include <util/stream/str.h>

namespace NYT {

////////////////////////////////////////////////////////////////////////////////

template <class T>
struct TFluentYsonUnwrapper
{
    using TUnwrapped = T;

    static TUnwrapped Unwrap(T t)
    {
        return std::move(t);
    }
};

////////////////////////////////////////////////////////////////////////////////

struct TFluentYsonVoid
{ };

template <>
struct TFluentYsonUnwrapper<TFluentYsonVoid>
{
    using TUnwrapped = void;

    static TUnwrapped Unwrap(TFluentYsonVoid)
    { }
};

////////////////////////////////////////////////////////////////////////////////

/// This class is actually a namespace for specific fluent adapter classes.
class TFluentYsonBuilder
    : private TNonCopyable
{
private:
    template <class T>
    static void WriteValue(NYT::NYson::IYsonConsumer* consumer, const T& value)
    {
        Serialize(value, consumer);
    }

public:
    class TFluentAny;
    template <class TParent> class TAny;
    template <class TParent> class TToAttributes;
    template <class TParent> class TAttributes;
    template <class TParent> class TListType;
    template <class TParent> class TMapType;

    /// Base class for all fluent adapters.
    template <class TParent>
    class TFluentBase
    {
    public:
        /// Implicit conversion to yson consumer
        operator NYT::NYson::IYsonConsumer* () const
        {
            return Consumer;
        }

    protected:
        /// @cond Doxygen_Suppress
        NYT::NYson::IYsonConsumer* Consumer;
        TParent Parent;

        TFluentBase(NYT::NYson::IYsonConsumer* consumer, TParent parent)
            : Consumer(consumer)
            , Parent(std::move(parent))
        { }

        using TUnwrappedParent = typename TFluentYsonUnwrapper<TParent>::TUnwrapped;

        TUnwrappedParent GetUnwrappedParent()
        {
            return TFluentYsonUnwrapper<TParent>::Unwrap(std::move(Parent));
        }
        /// @endcond Doxygen_Suppress
    };

    /// Base class for fluent adapters for fragment of list, map or attributes.
    template <template <class TParent> class TThis, class TParent>
    class TFluentFragmentBase
        : public TFluentBase<TParent>
    {
    public:
        using TDeepThis = TThis<TParent>;
        using TShallowThis = TThis<TFluentYsonVoid>;
        using TUnwrappedParent = typename TFluentYsonUnwrapper<TParent>::TUnwrapped;

        explicit TFluentFragmentBase(NYT::NYson::IYsonConsumer* consumer, TParent parent = TParent())
            : TFluentBase<TParent>(consumer, std::move(parent))
        { }

        /// Delegate invocation to a separate procedure.
        template <class TFunc>
        TDeepThis& Do(const TFunc& func)
        {
            func(TShallowThis(this->Consumer));
            return *static_cast<TDeepThis*>(this);
        }

        /// Conditionally delegate invocation to a separate procedure.
        template <class TFunc>
        TDeepThis& DoIf(bool condition, const TFunc& func)
        {
            if (condition) {
                func(TShallowThis(this->Consumer));
            }
            return *static_cast<TDeepThis*>(this);
        }

        /// Calls `func(*this, element)` for each `element` in range `[begin, end)`.
        template <class TFunc, class TIterator>
        TDeepThis& DoFor(const TIterator& begin, const TIterator& end, const TFunc& func)
        {
            for (auto current = begin; current != end; ++current) {
                func(TShallowThis(this->Consumer), current);
            }
            return *static_cast<TDeepThis*>(this);
        }

        /// Calls `func(*this, element)` for each `element` in `collection`.
        template <class TFunc, class TCollection>
        TDeepThis& DoFor(const TCollection& collection, const TFunc& func)
        {
            for (const auto& item : collection) {
                func(TShallowThis(this->Consumer), item);
            }
            return *static_cast<TDeepThis*>(this);
        }

    };

    /// Fluent adapter of a value without attributes.
    template <class TParent>
    class TAnyWithoutAttributes
        : public TFluentBase<TParent>
    {
    public:
        using TUnwrappedParent = typename TFluentYsonUnwrapper<TParent>::TUnwrapped;

        TAnyWithoutAttributes(NYT::NYson::IYsonConsumer* consumer, TParent parent)
            : TFluentBase<TParent>(consumer, std::move(parent))
        { }

        /// Pass `value` to underlying consumer.
        template <class T>
        TUnwrappedParent Value(const T& value)
        {
            WriteValue(this->Consumer, value);
            return this->GetUnwrappedParent();
        }

        /// Call `OnEntity()` of underlying consumer.
        TUnwrappedParent Entity()
        {
            this->Consumer->OnEntity();
            return this->GetUnwrappedParent();
        }

        /// Serialize `collection` to underlying consumer as a list.
        template <class TCollection>
        TUnwrappedParent List(const TCollection& collection)
        {
            this->Consumer->OnBeginList();
            for (const auto& item : collection) {
                this->Consumer->OnListItem();
                WriteValue(this->Consumer, item);
            }
            this->Consumer->OnEndList();
            return this->GetUnwrappedParent();
        }

        /// Serialize maximum `maxSize` elements of `collection` to underlying consumer as a list.
        template <class TCollection>
        TUnwrappedParent ListLimited(const TCollection& collection, size_t maxSize)
        {
            this->Consumer->OnBeginAttributes();
            this->Consumer->OnKeyedItem("count");
            this->Consumer->OnInt64Scalar(collection.size());
            this->Consumer->OnEndAttributes();
            this->Consumer->OnBeginList();
            size_t printedSize = 0;
            for (const auto& item : collection) {
                if (printedSize >= maxSize)
                    break;
                this->Consumer->OnListItem();
                WriteValue(this->Consumer, item);
                ++printedSize;
            }
            this->Consumer->OnEndList();
            return this->GetUnwrappedParent();
        }

        /// Open a list.
        TListType<TParent> BeginList()
        {
            this->Consumer->OnBeginList();
            return TListType<TParent>(this->Consumer, this->Parent);
        }

        /// Open a list, delegate invocation to `func`, then close the list.
        template <class TFunc>
        TUnwrappedParent DoList(const TFunc& func)
        {
            this->Consumer->OnBeginList();
            func(TListType<TFluentYsonVoid>(this->Consumer));
            this->Consumer->OnEndList();
            return this->GetUnwrappedParent();
        }

        /// Open a list, call `func(*this, element)` for each `element` of range, then close the list.
        template <class TFunc, class TIterator>
        TUnwrappedParent DoListFor(const TIterator& begin, const TIterator& end, const TFunc& func)
        {
            this->Consumer->OnBeginList();
            for (auto current = begin; current != end; ++current) {
                func(TListType<TFluentYsonVoid>(this->Consumer), current);
            }
            this->Consumer->OnEndList();
            return this->GetUnwrappedParent();
        }

        /// Open a list, call `func(*this, element)` for each `element` of `collection`, then close the list.
        template <class TFunc, class TCollection>
        TUnwrappedParent DoListFor(const TCollection& collection, const TFunc& func)
        {
            this->Consumer->OnBeginList();
            for (const auto& item : collection) {
                func(TListType<TFluentYsonVoid>(this->Consumer), item);
            }
            this->Consumer->OnEndList();
            return this->GetUnwrappedParent();
        }

        /// Open a map.
        TMapType<TParent> BeginMap()
        {
            this->Consumer->OnBeginMap();
            return TMapType<TParent>(this->Consumer, this->Parent);
        }

        /// Open a map, delegate invocation to `func`, then close the map.
        template <class TFunc>
        TUnwrappedParent DoMap(const TFunc& func)
        {
            this->Consumer->OnBeginMap();
            func(TMapType<TFluentYsonVoid>(this->Consumer));
            this->Consumer->OnEndMap();
            return this->GetUnwrappedParent();
        }

        /// Open a map, call `func(*this, element)` for each `element` of range, then close the map.
        template <class TFunc, class TIterator>
        TUnwrappedParent DoMapFor(const TIterator& begin, const TIterator& end, const TFunc& func)
        {
            this->Consumer->OnBeginMap();
            for (auto current = begin; current != end; ++current) {
                func(TMapType<TFluentYsonVoid>(this->Consumer), current);
            }
            this->Consumer->OnEndMap();
            return this->GetUnwrappedParent();
        }

        /// Open a map, call `func(*this, element)` for each `element` of `collection`, then close the map.
        template <class TFunc, class TCollection>
        TUnwrappedParent DoMapFor(const TCollection& collection, const TFunc& func)
        {
            this->Consumer->OnBeginMap();
            for (const auto& item : collection) {
                func(TMapType<TFluentYsonVoid>(this->Consumer), item);
            }
            this->Consumer->OnEndMap();
            return this->GetUnwrappedParent();
        }
    };

    /// Fluent adapter of any value.
    template <class TParent>
    class TAny
        : public TAnyWithoutAttributes<TParent>
    {
    public:
        using TBase = TAnyWithoutAttributes<TParent>;

        explicit TAny(NYT::NYson::IYsonConsumer* consumer, TParent parent)
            : TBase(consumer, std::move(parent))
        { }

        /// Open attributes.
        TAttributes<TBase> BeginAttributes()
        {
            this->Consumer->OnBeginAttributes();
            return TAttributes<TBase>(
                this->Consumer,
                TBase(this->Consumer, this->Parent));
        }
    };

    /// Fluent adapter of attributes fragment (the inside part of attributes).
    template <class TParent = TFluentYsonVoid>
    class TAttributes
        : public TFluentFragmentBase<TAttributes, TParent>
    {
    public:
        using TThis = TAttributes<TParent>;
        using TUnwrappedParent = typename TFluentYsonUnwrapper<TParent>::TUnwrapped;

        explicit TAttributes(NYT::NYson::IYsonConsumer* consumer, TParent parent = TParent())
            : TFluentFragmentBase<TFluentYsonBuilder::TAttributes, TParent>(consumer, std::move(parent))
        { }

        /// Pass attribute key to underlying consumer.
        TAny<TThis> Item(const TStringBuf& key)
        {
            this->Consumer->OnKeyedItem(key);
            return TAny<TThis>(this->Consumer, *this);
        }

        /// Pass attribute key to underlying consumer.
        template <size_t Size>
        TAny<TThis> Item(const char (&key)[Size])
        {
            return Item(TStringBuf(key, Size - 1));
        }

        //TODO: from TNode

        /// Close the attributes.
        TUnwrappedParent EndAttributes()
        {
            this->Consumer->OnEndAttributes();
            return this->GetUnwrappedParent();
        }
    };

    /// Fluent adapter of list fragment (the inside part of a list).
    template <class TParent = TFluentYsonVoid>
    class TListType
        : public TFluentFragmentBase<TListType, TParent>
    {
    public:
        using TThis = TListType<TParent>;
        using TUnwrappedParent = typename TFluentYsonUnwrapper<TParent>::TUnwrapped;

        explicit TListType(NYT::NYson::IYsonConsumer* consumer, TParent parent = TParent())
            : TFluentFragmentBase<TFluentYsonBuilder::TListType, TParent>(consumer, std::move(parent))
        { }

        /// Call `OnListItem()` of underlying consumer.
        TAny<TThis> Item()
        {
            this->Consumer->OnListItem();
            return TAny<TThis>(this->Consumer, *this);
        }

        // TODO: from TNode

        /// Close the list.
        TUnwrappedParent EndList()
        {
            this->Consumer->OnEndList();
            return this->GetUnwrappedParent();
        }
    };

    /// Fluent adapter of map fragment (the inside part of a map).
    template <class TParent = TFluentYsonVoid>
    class TMapType
        : public TFluentFragmentBase<TMapType, TParent>
    {
    public:
        using TThis = TMapType<TParent>;
        using TUnwrappedParent = typename TFluentYsonUnwrapper<TParent>::TUnwrapped;

        explicit TMapType(NYT::NYson::IYsonConsumer* consumer, TParent parent = TParent())
            : TFluentFragmentBase<TFluentYsonBuilder::TMapType, TParent>(consumer, std::move(parent))
        { }

        /// Pass map key to underlying consumer.
        template <size_t Size>
        TAny<TThis> Item(const char (&key)[Size])
        {
            return Item(TStringBuf(key, Size - 1));
        }

        /// Pass map key to underlying consumer.
        TAny<TThis> Item(const TStringBuf& key)
        {
            this->Consumer->OnKeyedItem(key);
            return TAny<TThis>(this->Consumer, *this);
        }

        // TODO: from TNode

        /// Close the map.
        TUnwrappedParent EndMap()
        {
            this->Consumer->OnEndMap();
            return this->GetUnwrappedParent();
        }
    };

};

////////////////////////////////////////////////////////////////////////////////

/// Builder representing any value.
using TFluentAny = TFluentYsonBuilder::TAny<TFluentYsonVoid>;

/// Builder representing the inside of a list (list fragment).
using TFluentList = TFluentYsonBuilder::TListType<TFluentYsonVoid>;

/// Builder representing the inside of a map (map fragment).
using TFluentMap = TFluentYsonBuilder::TMapType<TFluentYsonVoid>;

/// Builder representing the inside of attributes.
using TFluentAttributes = TFluentYsonBuilder::TAttributes<TFluentYsonVoid>;

////////////////////////////////////////////////////////////////////////////////

/// Create a fluent adapter to invoke methods of `consumer`.
static inline TFluentAny BuildYsonFluently(NYT::NYson::IYsonConsumer* consumer)
{
    return TFluentAny(consumer, TFluentYsonVoid());
}

/// Create a fluent adapter to invoke methods of `consumer` describing the contents of a list.
static inline TFluentList BuildYsonListFluently(NYT::NYson::IYsonConsumer* consumer)
{
    return TFluentList(consumer);
}

/// Create a fluent adapter to invoke methods of `consumer` describing the contents of a map.
static inline TFluentMap BuildYsonMapFluently(NYT::NYson::IYsonConsumer* consumer)
{
    return TFluentMap(consumer);
}

////////////////////////////////////////////////////////////////////////////////

class TFluentYsonWriterState
    : public TThrRefBase
{
public:
    using TValue = TString;

    explicit TFluentYsonWriterState(::NYson::EYsonFormat format)
        : Writer(&Output, format)
    { }

    TString GetValue()
    {
        return Output.Str();
    }

    NYT::NYson::IYsonConsumer* GetConsumer()
    {
        return &Writer;
    }

private:
    TStringStream Output;
    ::NYson::TYsonWriter Writer;
};

////////////////////////////////////////////////////////////////////////////////

class TFluentYsonBuilderState
    : public TThrRefBase
{
public:
    using TValue = TNode;

    explicit TFluentYsonBuilderState()
        : Builder(&Node)
    { }

    TNode GetValue()
    {
        return std::move(Node);
    }

    NYT::NYson::IYsonConsumer* GetConsumer()
    {
        return &Builder;
    }

private:
    TNode Node;
    TNodeBuilder Builder;
};

////////////////////////////////////////////////////////////////////////////////

template <class TState>
class TFluentYsonHolder
{
public:
    explicit TFluentYsonHolder(::TIntrusivePtr<TState> state)
        : State(state)
    { }

    ::TIntrusivePtr<TState> GetState() const
    {
        return State;
    }

private:
    ::TIntrusivePtr<TState> State;
};

////////////////////////////////////////////////////////////////////////////////

template <class TState>
struct TFluentYsonUnwrapper< TFluentYsonHolder<TState> >
{
    using TUnwrapped = typename TState::TValue;

    static TUnwrapped Unwrap(const TFluentYsonHolder<TState>& holder)
    {
        return std::move(holder.GetState()->GetValue());
    }
};

////////////////////////////////////////////////////////////////////////////////

template <class TState>
TFluentYsonBuilder::TAny<TFluentYsonHolder<TState>>
BuildYsonFluentlyWithState(::TIntrusivePtr<TState> state)
{
    return TFluentYsonBuilder::TAny<TFluentYsonHolder<TState>>(
        state->GetConsumer(),
        TFluentYsonHolder<TState>(state));
}

/// Create a fluent adapter returning a `TString` with corresponding YSON when construction is finished.
inline TFluentYsonBuilder::TAny<TFluentYsonHolder<TFluentYsonWriterState>>
BuildYsonStringFluently(::NYson::EYsonFormat format = ::NYson::EYsonFormat::Text)
{
    ::TIntrusivePtr<TFluentYsonWriterState> state(new TFluentYsonWriterState(format));
    return BuildYsonFluentlyWithState(state);
}

/// Create a fluent adapter returning a @ref NYT::TNode when construction is finished.
inline TFluentYsonBuilder::TAny<TFluentYsonHolder<TFluentYsonBuilderState>>
BuildYsonNodeFluently()
{
    ::TIntrusivePtr<TFluentYsonBuilderState> state(new TFluentYsonBuilderState);
    return BuildYsonFluentlyWithState(state);
}

////////////////////////////////////////////////////////////////////////////////

} // namespace NYT