#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