1 /* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2    file Copyright.txt or https://cmake.org/licensing for details.  */
3 #pragma once
4 
5 #include <algorithm>
6 #include <cstddef>
7 #include <functional>
8 #include <map>
9 #include <string>
10 #include <vector>
11 
12 #include <cm/optional>
13 #include <cm/string_view>
14 
15 #include <cm3p/json/value.h>
16 
17 template <typename T, typename E>
18 using cmJSONHelper = std::function<E(T& out, const Json::Value* value)>;
19 
20 template <typename T, typename E>
21 class cmJSONObjectHelper
22 {
23 public:
24   cmJSONObjectHelper(E&& success, E&& fail, bool allowExtra = true);
25 
26   template <typename U, typename M, typename F>
27   cmJSONObjectHelper& Bind(const cm::string_view& name, M U::*member, F func,
28                            bool required = true);
29   template <typename M, typename F>
30   cmJSONObjectHelper& Bind(const cm::string_view& name, std::nullptr_t, F func,
31                            bool required = true);
32   template <typename F>
33   cmJSONObjectHelper& Bind(const cm::string_view& name, F func,
34                            bool required = true);
35 
36   E operator()(T& out, const Json::Value* value) const;
37 
38 private:
39   // Not a true cmJSONHelper, it just happens to match the signature
40   using MemberFunction = std::function<E(T& out, const Json::Value* value)>;
41   struct Member
42   {
43     cm::string_view Name;
44     MemberFunction Function;
45     bool Required;
46   };
47   std::vector<Member> Members;
48   bool AnyRequired = false;
49   E Success;
50   E Fail;
51   bool AllowExtra;
52 
53   cmJSONObjectHelper& BindPrivate(const cm::string_view& name,
54                                   MemberFunction&& func, bool required);
55 };
56 
57 template <typename T, typename E>
cmJSONObjectHelper(E && success,E && fail,bool allowExtra)58 cmJSONObjectHelper<T, E>::cmJSONObjectHelper(E&& success, E&& fail,
59                                              bool allowExtra)
60   : Success(std::move(success))
61   , Fail(std::move(fail))
62   , AllowExtra(allowExtra)
63 {
64 }
65 
66 template <typename T, typename E>
67 template <typename U, typename M, typename F>
Bind(const cm::string_view & name,M U::* member,F func,bool required)68 cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::Bind(
69   const cm::string_view& name, M U::*member, F func, bool required)
70 {
71   return this->BindPrivate(
72     name,
73     [func, member](T& out, const Json::Value* value) -> E {
74       return func(out.*member, value);
75     },
76     required);
77 }
78 
79 template <typename T, typename E>
80 template <typename M, typename F>
Bind(const cm::string_view & name,std::nullptr_t,F func,bool required)81 cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::Bind(
82   const cm::string_view& name, std::nullptr_t, F func, bool required)
83 {
84   return this->BindPrivate(name,
85                            [func](T& /*out*/, const Json::Value* value) -> E {
86                              M dummy;
87                              return func(dummy, value);
88                            },
89                            required);
90 }
91 
92 template <typename T, typename E>
93 template <typename F>
Bind(const cm::string_view & name,F func,bool required)94 cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::Bind(
95   const cm::string_view& name, F func, bool required)
96 {
97   return this->BindPrivate(name, MemberFunction(func), required);
98 }
99 
100 template <typename T, typename E>
BindPrivate(const cm::string_view & name,MemberFunction && func,bool required)101 cmJSONObjectHelper<T, E>& cmJSONObjectHelper<T, E>::BindPrivate(
102   const cm::string_view& name, MemberFunction&& func, bool required)
103 {
104   Member m;
105   m.Name = name;
106   m.Function = std::move(func);
107   m.Required = required;
108   this->Members.push_back(std::move(m));
109   if (required) {
110     this->AnyRequired = true;
111   }
112   return *this;
113 }
114 
115 template <typename T, typename E>
operator()116 E cmJSONObjectHelper<T, E>::operator()(T& out, const Json::Value* value) const
117 {
118   if (!value && this->AnyRequired) {
119     return this->Fail;
120   }
121   if (value && !value->isObject()) {
122     return this->Fail;
123   }
124   Json::Value::Members extraFields;
125   if (value) {
126     extraFields = value->getMemberNames();
127   }
128 
129   for (auto const& m : this->Members) {
130     std::string name(m.Name.data(), m.Name.size());
131     if (value && value->isMember(name)) {
132       E result = m.Function(out, &(*value)[name]);
133       if (result != this->Success) {
134         return result;
135       }
136       extraFields.erase(
137         std::find(extraFields.begin(), extraFields.end(), name));
138     } else if (!m.Required) {
139       E result = m.Function(out, nullptr);
140       if (result != this->Success) {
141         return result;
142       }
143     } else {
144       return this->Fail;
145     }
146   }
147 
148   return this->AllowExtra || extraFields.empty() ? this->Success : this->Fail;
149 }
150 
151 template <typename E>
152 cmJSONHelper<std::string, E> cmJSONStringHelper(E success, E fail,
153                                                 const std::string& defval = "")
154 {
155   return
156     [success, fail, defval](std::string& out, const Json::Value* value) -> E {
157       if (!value) {
158         out = defval;
159         return success;
160       }
161       if (!value->isString()) {
162         return fail;
163       }
164       out = value->asString();
165       return success;
166     };
167 }
168 
169 template <typename E>
170 cmJSONHelper<int, E> cmJSONIntHelper(E success, E fail, int defval = 0)
171 {
172   return [success, fail, defval](int& out, const Json::Value* value) -> E {
173     if (!value) {
174       out = defval;
175       return success;
176     }
177     if (!value->isInt()) {
178       return fail;
179     }
180     out = value->asInt();
181     return success;
182   };
183 }
184 
185 template <typename E>
186 cmJSONHelper<unsigned int, E> cmJSONUIntHelper(E success, E fail,
187                                                unsigned int defval = 0)
188 {
189   return
190     [success, fail, defval](unsigned int& out, const Json::Value* value) -> E {
191       if (!value) {
192         out = defval;
193         return success;
194       }
195       if (!value->isUInt()) {
196         return fail;
197       }
198       out = value->asUInt();
199       return success;
200     };
201 }
202 
203 template <typename E>
204 cmJSONHelper<bool, E> cmJSONBoolHelper(E success, E fail, bool defval = false)
205 {
206   return [success, fail, defval](bool& out, const Json::Value* value) -> E {
207     if (!value) {
208       out = defval;
209       return success;
210     }
211     if (!value->isBool()) {
212       return fail;
213     }
214     out = value->asBool();
215     return success;
216   };
217 }
218 
219 template <typename T, typename E, typename F, typename Filter>
cmJSONVectorFilterHelper(E success,E fail,F func,Filter filter)220 cmJSONHelper<std::vector<T>, E> cmJSONVectorFilterHelper(E success, E fail,
221                                                          F func, Filter filter)
222 {
223   return [success, fail, func, filter](std::vector<T>& out,
224                                        const Json::Value* value) -> E {
225     if (!value) {
226       out.clear();
227       return success;
228     }
229     if (!value->isArray()) {
230       return fail;
231     }
232     out.clear();
233     for (auto const& item : *value) {
234       T t;
235       E result = func(t, &item);
236       if (result != success) {
237         return result;
238       }
239       if (!filter(t)) {
240         continue;
241       }
242       out.push_back(std::move(t));
243     }
244     return success;
245   };
246 }
247 
248 template <typename T, typename E, typename F>
cmJSONVectorHelper(E success,E fail,F func)249 cmJSONHelper<std::vector<T>, E> cmJSONVectorHelper(E success, E fail, F func)
250 {
251   return cmJSONVectorFilterHelper<T, E, F>(success, fail, func,
252                                            [](const T&) { return true; });
253 }
254 
255 template <typename T, typename E, typename F, typename Filter>
cmJSONMapFilterHelper(E success,E fail,F func,Filter filter)256 cmJSONHelper<std::map<std::string, T>, E> cmJSONMapFilterHelper(E success,
257                                                                 E fail, F func,
258                                                                 Filter filter)
259 {
260   return [success, fail, func, filter](std::map<std::string, T>& out,
261                                        const Json::Value* value) -> E {
262     if (!value) {
263       out.clear();
264       return success;
265     }
266     if (!value->isObject()) {
267       return fail;
268     }
269     out.clear();
270     for (auto const& key : value->getMemberNames()) {
271       if (!filter(key)) {
272         continue;
273       }
274       T t;
275       E result = func(t, &(*value)[key]);
276       if (result != success) {
277         return result;
278       }
279       out[key] = std::move(t);
280     }
281     return success;
282   };
283 }
284 
285 template <typename T, typename E, typename F>
cmJSONMapHelper(E success,E fail,F func)286 cmJSONHelper<std::map<std::string, T>, E> cmJSONMapHelper(E success, E fail,
287                                                           F func)
288 {
289   return cmJSONMapFilterHelper<T, E, F>(
290     success, fail, func, [](const std::string&) { return true; });
291 }
292 
293 template <typename T, typename E, typename F>
cmJSONOptionalHelper(E success,F func)294 cmJSONHelper<cm::optional<T>, E> cmJSONOptionalHelper(E success, F func)
295 {
296   return [success, func](cm::optional<T>& out, const Json::Value* value) -> E {
297     if (!value) {
298       out.reset();
299       return success;
300     }
301     out.emplace();
302     return func(*out, value);
303   };
304 }
305 
306 template <typename T, typename E, typename F>
cmJSONRequiredHelper(E fail,F func)307 cmJSONHelper<T, E> cmJSONRequiredHelper(E fail, F func)
308 {
309   return [fail, func](T& out, const Json::Value* value) -> E {
310     if (!value) {
311       return fail;
312     }
313     return func(out, value);
314   };
315 }
316