xrpld
Loading...
Searching...
No Matches
SpecBridge.cpp
1#include <xrpld/rpc/detail/SpecBridge.hpp>
2
3#include <xrpld/rpc/detail/JsonObjectView.hpp>
4
5#include <xrpl/json/json_reader.h>
6#include <xrpl/json/json_value.h>
7#include <xrpl/protocol/ErrorCodes.h>
8#include <xrpl/protocol/jss.h>
9
10#include <gtest/gtest.h>
11#include <rpcspec/Errors.hpp>
12#include <rpcspec/Types.hpp>
13#include <rpcspec/handlers/ledger/Spec.hpp>
14
15#include <cstdint>
16#include <string>
17#include <string_view>
18#include <variant>
19
20namespace xrpl {
21
22namespace {
23
24using ObjectView = rpc::JsonObjectView;
25
26json::Value
27makeParams()
28{
29 static constexpr auto kParams = R"JSON({
30 "str": "hello",
31 "num": 42,
32 "big": 3000000000,
33 "neg": -7,
34 "real": 1.5,
35 "flag": true,
36 "nothing": null,
37 "obj": {"inner": "deep"},
38 "arr": ["a", "b"]
39 })JSON";
40
41 json::Value params;
42 EXPECT_TRUE(json::Reader{}.parse(kParams, params));
43
44 return params;
45}
46
47} // namespace
48
49TEST(JsonView, field_types)
50{
51 auto params = makeParams();
52 ObjectView const root{params};
53
54 auto const str = root.child("str");
55 EXPECT_TRUE(str.present());
56 EXPECT_EQ(str.key(), "str");
57 EXPECT_TRUE(str.isString());
58 EXPECT_EQ(str.asString(), "hello");
59 EXPECT_TRUE(str.is<std::string>());
60 EXPECT_FALSE(str.isBool());
61 EXPECT_FALSE(str.isInt64());
62 EXPECT_FALSE(str.isObject());
63 EXPECT_FALSE(str.isArray());
64
65 // uint32-ness is about the value, not the storage: a non-negative Int answers true.
66 auto const num = root.child("num");
67 EXPECT_TRUE(num.isInt64());
68 EXPECT_EQ(num.asInt64(), 42);
69 EXPECT_TRUE(num.isUint32());
70 EXPECT_EQ(num.asUint32(), 42U);
71 EXPECT_TRUE(num.is<std::int64_t>());
72 EXPECT_TRUE(num.is<std::uint32_t>());
73
74 auto const big = root.child("big");
75 EXPECT_TRUE(big.isInt64());
76 EXPECT_EQ(big.asInt64(), 3000000000LL);
77 EXPECT_TRUE(big.isUint32());
78 EXPECT_EQ(big.asUint32(), 3000000000U);
79
80 auto const neg = root.child("neg");
81 EXPECT_TRUE(neg.isInt64());
82 EXPECT_EQ(neg.asInt64(), -7);
83 EXPECT_FALSE(neg.isUint32());
84 EXPECT_FALSE(neg.is<std::uint32_t>());
85
86 auto const real = root.child("real");
87 EXPECT_TRUE(real.isDouble());
88 EXPECT_EQ(real.asDouble(), 1.5);
89 EXPECT_TRUE(real.is<double>());
90 EXPECT_FALSE(real.isInt64());
91
92 auto const flag = root.child("flag");
93 EXPECT_TRUE(flag.isBool());
94 EXPECT_TRUE(flag.asBool());
95 EXPECT_TRUE(flag.is<bool>());
96
97 // A boolean is not a number, though json::Value::isIntegral() says otherwise.
98 EXPECT_FALSE(flag.isInt64());
99 EXPECT_FALSE(flag.isUint32());
100
101 // Present, but null: no predicate claims it.
102 auto const nothing = root.child("nothing");
103 EXPECT_TRUE(nothing.present());
104 EXPECT_FALSE(nothing.isString());
105 EXPECT_FALSE(nothing.isInt64());
106 EXPECT_FALSE(nothing.isBool());
107 EXPECT_FALSE(nothing.isDouble());
108 EXPECT_FALSE(nothing.isObject());
109 EXPECT_FALSE(nothing.isArray());
110
111 auto const obj = root.child("obj");
112 EXPECT_TRUE(obj.isObject());
113 EXPECT_TRUE(obj.is<::rpc::spec::JsonObject>());
114 EXPECT_EQ(obj.objectSize(), 1U);
115 EXPECT_EQ(obj.arraySize(), 0U);
116
117 auto const arr = root.child("arr");
118 EXPECT_TRUE(arr.isArray());
119 EXPECT_TRUE(arr.is<::rpc::spec::JsonArray>());
120 EXPECT_EQ(arr.arraySize(), 2U);
121 EXPECT_EQ(arr.objectSize(), 0U);
122}
123
124TEST(JsonView, field_absent)
125{
126 auto params = makeParams();
127 ObjectView const root{params};
128
129 // Every predicate has to answer for an absent field: that is how a spec asks whether
130 // an optional field was supplied.
131 auto const missing = root.child("nope");
132 EXPECT_FALSE(missing.present());
133 EXPECT_EQ(missing.key(), "nope");
134 EXPECT_FALSE(missing.isString());
135 EXPECT_FALSE(missing.isInt64());
136 EXPECT_FALSE(missing.isUint32());
137 EXPECT_FALSE(missing.isBool());
138 EXPECT_FALSE(missing.isDouble());
139 EXPECT_FALSE(missing.isObject());
140 EXPECT_FALSE(missing.isArray());
141 EXPECT_EQ(missing.arraySize(), 0U);
142 EXPECT_EQ(missing.objectSize(), 0U);
143 EXPECT_FALSE(missing.is<std::string>());
144 EXPECT_FALSE(missing.is<::rpc::spec::JsonObject>());
145
146 // Walking down from an absent field stays absent rather than faulting.
147 EXPECT_FALSE(missing.child("deeper").present());
148 EXPECT_FALSE(missing.element(0).present());
149
150 // Same for a non-object asked for a child, or a non-array for an element.
151 EXPECT_FALSE(root.child("str").child("inner").present());
152 EXPECT_FALSE(root.child("str").element(0).present());
153}
154
155TEST(JsonView, field_navigation)
156{
157 auto params = makeParams();
158 ObjectView const root{params};
159
160 auto const inner = root.child("obj").child("inner");
161 EXPECT_TRUE(inner.present());
162 EXPECT_EQ(inner.asString(), "deep");
163 EXPECT_EQ(inner.key(), "inner");
164
165 // An element borrows its parent's key.
166 auto const first = root.child("arr").element(0);
167 EXPECT_TRUE(first.present());
168 EXPECT_EQ(first.asString(), "a");
169 EXPECT_EQ(first.key(), "arr");
170 EXPECT_EQ(root.child("arr").element(1).asString(), "b");
171 EXPECT_FALSE(root.child("arr").element(2).present());
172}
173
174TEST(JsonView, field_set)
175{
176 auto params = makeParams();
177 ObjectView root{params};
178
179 // What a modifier does: normalise in place, so the converter after it sees that.
180 root.child("str").set(std::string_view{"world"});
181 EXPECT_EQ(params["str"].asString(), "world");
182
183 root.child("num").set(std::uint32_t{7});
184 EXPECT_EQ(params["num"].asUInt(), 7U);
185
186 root.child("neg").set(std::int64_t{-1});
187 EXPECT_EQ(params["neg"].asInt(), -1);
188
189 root.child("flag").set(false);
190 EXPECT_FALSE(params["flag"].asBool());
191
192 root.child("real").set(2.5);
193 EXPECT_EQ(params["real"].asDouble(), 2.5);
194
195 // Write access reaches through a child.
196 root.child("obj").child("inner").set(std::string_view{"changed"});
197 EXPECT_EQ(params["obj"]["inner"].asString(), "changed");
198}
199
200TEST(JsonView, field_set_spans_both_32_bit_types)
201{
202 auto params = makeParams();
203 ObjectView root{params};
204
205 // The spec's toNumber modifier produces values up to UINT32_MAX. json::Int is 32 bits,
206 // so routing everything through it would wrap those negative and lose the value.
207 root.child("num").set(std::int64_t{3000000000});
208 auto const big = root.child("num");
209 EXPECT_TRUE(big.isUint32());
210 EXPECT_EQ(big.asUint32(), 3000000000U);
211 EXPECT_EQ(big.asInt64(), 3000000000LL);
212
213 root.child("neg").set(std::int64_t{-2000000000});
214 auto const negative = root.child("neg");
215 EXPECT_TRUE(negative.isInt64());
216 EXPECT_EQ(negative.asInt64(), -2000000000LL);
217 EXPECT_FALSE(negative.isUint32());
218}
219
220TEST(JsonView, mutable_navigation)
221{
222 auto params = makeParams();
223 ObjectView root{params};
224
225 EXPECT_FALSE(root.child("nope").present());
226 EXPECT_FALSE(root.child("obj").child("nope").present());
227 EXPECT_FALSE(root.child("str").child("inner").present());
228 EXPECT_FALSE(root.child("arr").element(2).present());
229 EXPECT_FALSE(root.child("str").element(0).present());
230 EXPECT_FALSE(params.isMember("nope"));
231 EXPECT_FALSE(params["obj"].isMember("nope"));
232
233 auto second = root.child("arr").element(1);
234 ASSERT_TRUE(second.present());
235 EXPECT_EQ(second.key(), "arr");
236 second.set(std::string_view{"z"});
237 EXPECT_EQ(params["arr"][1u].asString(), "z");
238
240 ObjectView arrayRoot{array};
241 EXPECT_FALSE(arrayRoot.child("anything").present());
242}
243
244TEST(JsonView, object_root)
245{
246 auto params = makeParams();
247
248 ObjectView const root{params};
249 EXPECT_TRUE(root.isObject());
250 EXPECT_FALSE(root.isArray());
251 EXPECT_TRUE(root.child("str").present());
252 EXPECT_FALSE(root.child("nope").present());
253
254 // Must not create the key: the non-const operator[] would have inserted a null.
255 EXPECT_FALSE(params.isMember("nope"));
256
258 ObjectView const arrayRoot{array};
259 EXPECT_TRUE(arrayRoot.isArray());
260 EXPECT_FALSE(arrayRoot.isObject());
261 EXPECT_FALSE(arrayRoot.child("anything").present());
262}
263
264TEST(JsonView, mutable_root_over_const_source_reads)
265{
266 auto const params = makeParams();
267 ObjectView root{params};
268
269 auto const str = root.child("str");
270 ASSERT_TRUE(str.present());
271 EXPECT_EQ(str.asString(), "hello");
272 EXPECT_EQ(root.child("obj").child("inner").asString(), "deep");
273 EXPECT_EQ(root.child("arr").element(1).asString(), "b");
274 EXPECT_FALSE(root.child("nope").present());
275}
276
277TEST(SpecBridge, shared_spec_parses_json_value)
278{
280 params["ledger_index"] = 42U;
281 params["binary"] = true;
282 params["transactions"] = true;
283
284 auto const input = ::rpc::spec::handlers::ledger::kSpec.parse(params, 2);
285 ASSERT_TRUE(input.has_value());
286
287 EXPECT_TRUE(input->binary);
288 EXPECT_TRUE(input->transactions);
289 EXPECT_FALSE(input->expand);
290 ASSERT_TRUE(std::holds_alternative<std::uint32_t>(input->ledger.value));
291 EXPECT_EQ(std::get<std::uint32_t>(input->ledger.value), 42U);
292}
293
294TEST(SpecBridge, shared_spec_reports_a_bad_json_value)
295{
297 params["ledger_index"] = "not-a-ledger";
298
299 auto const input = ::rpc::spec::handlers::ledger::kSpec.parse(params, 2);
300 ASSERT_FALSE(input.has_value());
301 EXPECT_EQ(std::get<::rpc::XrpldError>(input.error().code), RpcInvalidParams);
302}
303
304TEST(SpecBridge, shared_spec_warns_on_a_deprecated_field)
305{
307 params["type"] = "hashes";
308
309 auto const warnings = ::rpc::spec::handlers::ledger::kSpec.check(params, 2);
310 ASSERT_EQ(warnings.size(), 1U);
311 EXPECT_EQ(warnings[0].code, ::rpc::WarningCode::WarnRpcDeprecated);
312}
313
314TEST(SpecBridge, inject_spec_error_uses_default_message)
315{
317 rpc::injectSpecError(out, ::rpc::Status{RpcLgrNotFound});
318 EXPECT_EQ(out, rpc::makeError(RpcLgrNotFound));
319}
320
321TEST(SpecBridge, inject_spec_error_uses_custom_message)
322{
324 rpc::injectSpecError(out, ::rpc::Status{RpcInvalidParams, "Invalid field 'ledger'."});
325 EXPECT_EQ(out, rpc::makeError(RpcInvalidParams, "Invalid field 'ledger'."));
326}
327
328TEST(SpecBridge, inject_spec_warnings)
329{
330 std::string const kDeprecatedBase{
331 ::rpc::getWarningInfo(::rpc::WarningCode::WarnRpcDeprecated).message};
332
333 {
335 rpc::injectSpecWarnings(out, {});
336 EXPECT_FALSE(out.isMember(jss::warnings));
337 }
338 {
340 rpc::injectSpecWarnings(
341 out,
342 {
343 {
344 .code = ::rpc::WarningCode::WarnRpcDeprecated,
345 .field = "type",
346 .message = "Field 'type' is deprecated.",
347 },
348 });
349
350 ASSERT_TRUE(out[jss::warnings].isArray());
351 ASSERT_EQ(out[jss::warnings].size(), 1U);
352 EXPECT_EQ(out[jss::warnings][0u][jss::id].asInt(), WarnRpcFieldsDeprecated);
353 EXPECT_EQ(
354 out[jss::warnings][0u][jss::message], kDeprecatedBase + " Field 'type' is deprecated.");
355 }
356 {
357 // One entry per code, both details appended.
359 rpc::injectSpecWarnings(
360 out,
361 {
362 {
363 .code = ::rpc::WarningCode::WarnRpcDeprecated,
364 .field = "type",
365 .message = "Field 'type' is deprecated.",
366 },
367 {
368 .code = ::rpc::WarningCode::WarnRpcDeprecated,
369 .field = "ledger",
370 .message = "Field 'ledger' is deprecated.",
371 },
372 });
373
374 ASSERT_EQ(out[jss::warnings].size(), 1U);
375 EXPECT_EQ(
376 out[jss::warnings][0u][jss::message],
377 kDeprecatedBase + " Field 'type' is deprecated. Field 'ledger' is deprecated.");
378 }
379 {
380 // Ordered by code, so the response does not depend on field visit order.
382 rpc::injectSpecWarnings(
383 out,
384 {
385 {
386 .code = ::rpc::WarningCode::WarnRpcDeprecated,
387 .field = "type",
388 .message = "Field 'type' is deprecated.",
389 },
390 {.code = ::rpc::WarningCode::WarnRpcOutdated, .field = "", .message = ""},
391 });
392
393 ASSERT_EQ(out[jss::warnings].size(), 2U);
394 EXPECT_EQ(
395 out[jss::warnings][0u][jss::id].asInt(),
396 static_cast<int>(::rpc::WarningCode::WarnRpcOutdated));
397 EXPECT_EQ(
398 out[jss::warnings][0u][jss::message],
399 std::string{::rpc::getWarningInfo(::rpc::WarningCode::WarnRpcOutdated).message});
400 EXPECT_EQ(out[jss::warnings][1u][jss::id].asInt(), WarnRpcFieldsDeprecated);
401 }
402 {
403 // A handler's own warnings are kept.
407 entry[jss::id] = WarnRpcUnsupportedMajority;
408 out[jss::warnings] = existing;
409
410 rpc::injectSpecWarnings(
411 out,
412 {
413 {
414 .code = ::rpc::WarningCode::WarnRpcDeprecated,
415 .field = "type",
416 .message = "Field 'type' is deprecated.",
417 },
418 });
419
420 ASSERT_EQ(out[jss::warnings].size(), 2U);
421 EXPECT_EQ(out[jss::warnings][0u][jss::id].asInt(), WarnRpcUnsupportedMajority);
422 EXPECT_EQ(out[jss::warnings][1u][jss::id].asInt(), WarnRpcFieldsDeprecated);
423 }
424}
425
426} // namespace xrpl
Represents a JSON value.
Definition json_value.h:117
Value & append(Value const &value)
Append value to array at the end.
bool isMember(char const *key) const
Return true if the object has a member named key.
T holds_alternative(T... args)
@ Array
array value (ordered list)
Definition json_value.h:28
@ Object
object value (collection of name/value pairs).
Definition json_value.h:29
json::Value makeError(ErrorCodeI code)
Returns a new json object that reflects the error code.
Use hash_* containers for keys that do not need a cryptographically secure hashing algorithm.
Definition algorithm.h:5
@ WarnRpcFieldsDeprecated
Definition ErrorCodes.h:162
@ WarnRpcUnsupportedMajority
Definition ErrorCodes.h:158
@ RpcLgrNotFound
Definition ErrorCodes.h:55
@ RpcInvalidParams
Definition ErrorCodes.h:67
TEST(FileUtilitiesTest, get_file_contents)
Number root(Number f, unsigned d)
Status represents the results of an operation that might fail.
Definition Status.h:27