123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678 |
- #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
|