1/**
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#ifndef LIBPANDABASE_UTILS_JSON_BUILDER_H
17#define LIBPANDABASE_UTILS_JSON_BUILDER_H
18
19#include <cmath>
20#include <cstddef>
21#include <functional>
22#include <ostream>
23#include <sstream>
24#include <string>
25#include <string_view>
26#include <type_traits>
27#include <utility>
28
29namespace panda {
30class JsonArrayBuilder;
31class JsonObjectBuilder;
32void JsonEscape(std::ostream & /* os */, std::string_view /* string */);
33
34template <char startDelimiter, char endDelimiter>
35class JsonBuilderBase {
36public:
37    JsonBuilderBase()
38    {
39        ss_ << startDelimiter;
40    }
41
42    std::string Build() &&
43    {
44        ss_ << endDelimiter;
45        return ss_.str();
46    }
47
48protected:
49    void Entry()
50    {
51        if (firstEntry_) {
52            firstEntry_ = false;
53        } else {
54            ss_ << ',';
55        }
56    }
57
58    template <typename T>
59    void Append(T &&value)
60    {
61        ss_ << (std::forward<T>(value));
62    }
63
64    void Stringify(std::nullptr_t)
65    {
66        ss_ << "null";
67    }
68
69    void Stringify(bool boolean)
70    {
71        ss_ << (boolean ? "true" : "false");
72    }
73
74    template <typename T, std::enable_if_t<std::is_convertible_v<T, double> && !std::is_same_v<T, bool>, int> = 0>
75    void Stringify(T &&number)
76    {
77        auto value = static_cast<double>(std::forward<T>(number));
78        if (std::isfinite(value)) {
79            ss_ << value;
80        } else {
81            ss_ << "null";
82        }
83    }
84
85    void Stringify(std::string_view string)
86    {
87        JsonEscape(ss_, string);
88    }
89
90    void Stringify(const char *string)
91    {
92        JsonEscape(ss_, string);
93    }
94
95    template <typename T, std::enable_if_t<std::is_invocable_v<T, JsonArrayBuilder &>, int> = 0>
96    void Stringify(T &&array);
97
98    template <typename T, std::enable_if_t<std::is_invocable_v<T, JsonObjectBuilder &>, int> = 0>
99    void Stringify(T &&object);
100
101private:
102    std::stringstream ss_;
103    bool firstEntry_ {true};
104};
105
106class JsonArrayBuilder : public JsonBuilderBase<'[', ']'> {
107public:
108    template <typename T>
109    JsonArrayBuilder &Add(T &&value) &
110    {
111        Entry();
112        Stringify(std::forward<T>(value));
113        return *this;
114    }
115
116    template <typename T>
117    JsonArrayBuilder &&Add(T &&value) &&
118    {
119        Add(std::forward<T>(value));
120        return std::move(*this);
121    }
122};
123
124// Trick CodeChecker (G.FMT.03).
125using JsonObjectBuilderBase = JsonBuilderBase<'{', '}'>;
126
127class JsonObjectBuilder : public JsonObjectBuilderBase {
128public:
129    template <typename T>
130    JsonObjectBuilder &AddProperty(const std::string_view &key, T &&value) &
131    {
132        Entry();
133        Stringify(key);
134        Append(":");
135        Stringify(std::forward<T>(value));
136        return *this;
137    }
138
139    template <typename T>
140    JsonObjectBuilder &&AddProperty(std::string_view key, T &&value) &&
141    {
142        AddProperty(key, std::forward<T>(value));
143        return std::move(*this);
144    }
145};
146
147template <char startDelimiter, char endDelimiter>
148template <typename T, std::enable_if_t<std::is_invocable_v<T, JsonArrayBuilder &>, int>>
149void JsonBuilderBase<startDelimiter, endDelimiter>::Stringify(T &&array)
150{
151    JsonArrayBuilder builder;
152    std::invoke(std::forward<T>(array), builder);
153    ss_ << std::move(builder).Build();
154}
155
156template <char startDelimiter, char endDelimiter>
157template <typename T, std::enable_if_t<std::is_invocable_v<T, JsonObjectBuilder &>, int>>
158void JsonBuilderBase<startDelimiter, endDelimiter>::Stringify(T &&object)
159{
160    JsonObjectBuilder builder;
161    std::invoke(std::forward<T>(object), builder);
162    ss_ << std::move(builder).Build();
163}
164}  // namespace panda
165
166#endif  // LIBPANDABASE_UTILS_JSON_BUILDER_H
167