1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #ifndef UTIL_JSON_JSON_HELPERS_H_
6 #define UTIL_JSON_JSON_HELPERS_H_
7 
8 #include <chrono>
9 #include <cmath>
10 #include <functional>
11 #include <string>
12 #include <utility>
13 #include <vector>
14 
15 #include "absl/strings/string_view.h"
16 #include "json/value.h"
17 #include "platform/base/error.h"
18 #include "util/chrono_helpers.h"
19 #include "util/simple_fraction.h"
20 
21 // This file contains helper methods for parsing JSON, in an attempt to
22 // reduce boilerplate code when working with JsonCpp.
23 namespace openscreen {
24 namespace json {
25 
26 // TODO(jophba): remove these methods after refactoring offer messaging.
CreateParseError(const std::string & type)27 inline Error CreateParseError(const std::string& type) {
28   return Error(Error::Code::kJsonParseError, "Failed to parse " + type);
29 }
30 
CreateParameterError(const std::string & type)31 inline Error CreateParameterError(const std::string& type) {
32   return Error(Error::Code::kParameterInvalid, "Invalid parameter: " + type);
33 }
34 
ParseBool(const Json::Value & parent,const std::string & field)35 inline ErrorOr<bool> ParseBool(const Json::Value& parent,
36                                const std::string& field) {
37   const Json::Value& value = parent[field];
38   if (!value.isBool()) {
39     return CreateParseError("bool field " + field);
40   }
41   return value.asBool();
42 }
43 
ParseInt(const Json::Value & parent,const std::string & field)44 inline ErrorOr<int> ParseInt(const Json::Value& parent,
45                              const std::string& field) {
46   const Json::Value& value = parent[field];
47   if (!value.isInt()) {
48     return CreateParseError("integer field: " + field);
49   }
50   return value.asInt();
51 }
52 
ParseUint(const Json::Value & parent,const std::string & field)53 inline ErrorOr<uint32_t> ParseUint(const Json::Value& parent,
54                                    const std::string& field) {
55   const Json::Value& value = parent[field];
56   if (!value.isUInt()) {
57     return CreateParseError("unsigned integer field: " + field);
58   }
59   return value.asUInt();
60 }
61 
ParseString(const Json::Value & parent,const std::string & field)62 inline ErrorOr<std::string> ParseString(const Json::Value& parent,
63                                         const std::string& field) {
64   const Json::Value& value = parent[field];
65   if (!value.isString()) {
66     return CreateParseError("string field: " + field);
67   }
68   return value.asString();
69 }
70 
71 // TODO(jophba): offer messaging should use these methods instead.
ParseBool(const Json::Value & value,bool * out)72 inline bool ParseBool(const Json::Value& value, bool* out) {
73   if (!value.isBool()) {
74     return false;
75   }
76   *out = value.asBool();
77   return true;
78 }
79 
80 // A general note about parsing primitives. "Validation" in this context
81 // generally means ensuring that the values are non-negative, excepting doubles
82 // which may be negative in some cases.
83 inline bool ParseAndValidateDouble(const Json::Value& value,
84                                    double* out,
85                                    bool allow_negative = false) {
86   if (!value.isDouble()) {
87     return false;
88   }
89   const double d = value.asDouble();
90   if (std::isnan(d)) {
91     return false;
92   }
93   if (!allow_negative && d < 0) {
94     return false;
95   }
96   *out = d;
97   return true;
98 }
99 
ParseAndValidateInt(const Json::Value & value,int * out)100 inline bool ParseAndValidateInt(const Json::Value& value, int* out) {
101   if (!value.isInt()) {
102     return false;
103   }
104   int i = value.asInt();
105   if (i < 0) {
106     return false;
107   }
108   *out = i;
109   return true;
110 }
111 
ParseAndValidateUint(const Json::Value & value,uint32_t * out)112 inline bool ParseAndValidateUint(const Json::Value& value, uint32_t* out) {
113   if (!value.isUInt()) {
114     return false;
115   }
116   *out = value.asUInt();
117   return true;
118 }
119 
ParseAndValidateString(const Json::Value & value,std::string * out)120 inline bool ParseAndValidateString(const Json::Value& value, std::string* out) {
121   if (!value.isString()) {
122     return false;
123   }
124   *out = value.asString();
125   return true;
126 }
127 
128 // We want to be more robust when we parse fractions then just
129 // allowing strings, this will parse numeral values such as
130 // value: 50 as well as value: "50" and value: "100/2".
ParseAndValidateSimpleFraction(const Json::Value & value,SimpleFraction * out)131 inline bool ParseAndValidateSimpleFraction(const Json::Value& value,
132                                            SimpleFraction* out) {
133   if (value.isInt()) {
134     int parsed = value.asInt();
135     if (parsed < 0) {
136       return false;
137     }
138     *out = SimpleFraction{parsed, 1};
139     return true;
140   }
141 
142   if (value.isString()) {
143     auto fraction_or_error = SimpleFraction::FromString(value.asString());
144     if (!fraction_or_error) {
145       return false;
146     }
147 
148     if (!fraction_or_error.value().is_positive() ||
149         !fraction_or_error.value().is_defined()) {
150       return false;
151     }
152     *out = std::move(fraction_or_error.value());
153     return true;
154   }
155   return false;
156 }
157 
ParseAndValidateMilliseconds(const Json::Value & value,milliseconds * out)158 inline bool ParseAndValidateMilliseconds(const Json::Value& value,
159                                          milliseconds* out) {
160   int out_ms;
161   if (!ParseAndValidateInt(value, &out_ms) || out_ms < 0) {
162     return false;
163   }
164   *out = milliseconds(out_ms);
165   return true;
166 }
167 
168 template <typename T>
169 using Parser = std::function<bool(const Json::Value&, T*)>;
170 
171 // NOTE: array parsing methods reset the output vector to an empty vector in
172 // any error case. This is especially useful for optional arrays.
173 template <typename T>
ParseAndValidateArray(const Json::Value & value,Parser<T> parser,std::vector<T> * out)174 bool ParseAndValidateArray(const Json::Value& value,
175                            Parser<T> parser,
176                            std::vector<T>* out) {
177   out->clear();
178   if (!value.isArray() || value.empty()) {
179     return false;
180   }
181 
182   out->reserve(value.size());
183   for (Json::ArrayIndex i = 0; i < value.size(); ++i) {
184     T v;
185     if (!parser(value[i], &v)) {
186       out->clear();
187       return false;
188     }
189     out->push_back(v);
190   }
191 
192   return true;
193 }
194 
ParseAndValidateIntArray(const Json::Value & value,std::vector<int> * out)195 inline bool ParseAndValidateIntArray(const Json::Value& value,
196                                      std::vector<int>* out) {
197   return ParseAndValidateArray<int>(value, ParseAndValidateInt, out);
198 }
199 
ParseAndValidateUintArray(const Json::Value & value,std::vector<uint32_t> * out)200 inline bool ParseAndValidateUintArray(const Json::Value& value,
201                                       std::vector<uint32_t>* out) {
202   return ParseAndValidateArray<uint32_t>(value, ParseAndValidateUint, out);
203 }
204 
ParseAndValidateStringArray(const Json::Value & value,std::vector<std::string> * out)205 inline bool ParseAndValidateStringArray(const Json::Value& value,
206                                         std::vector<std::string>* out) {
207   return ParseAndValidateArray<std::string>(value, ParseAndValidateString, out);
208 }
209 
210 }  // namespace json
211 }  // namespace openscreen
212 
213 #endif  // UTIL_JSON_JSON_HELPERS_H_
214