Skip to content

Commit 3ed0ff3

Browse files
robhoganfacebook-github-bot
authored andcommitted
Add helpers for formatting JSON CDP responses (#43340)
Summary: Pull Request resolved: #43340 Adds convenience methods `jsonResult`, `jsonError` and `jsonNotification` for more ergonomic construction of CDP JSON responses. Note that CDP is *loosely* based on [JSON-RPC 2.0](https://www.jsonrpc.org/specification), but differs for example in the omission of `"jsonrpc": "2.0"`. Before: ``` frontendChannel_(folly::toJson(folly::dynamic::object("id", req.id)( "error", folly::dynamic::object("code", -32602)( "message", "executionContextName is mutually exclusive with executionContextId")))); ``` After: ``` frontendChannel_(cdp::jsonError( req.id, cdp::ErrorCode::InvalidParams, "executionContextName is mutually exclusive with executionContextId")); ``` Changelog: [Internal] Reviewed By: motiz88 Differential Revision: D54202854 fbshipit-source-id: 76a407ae39ff9c2ec79bcaddb6cd4d494afb7693
1 parent 9426d24 commit 3ed0ff3

File tree

13 files changed

+241
-178
lines changed

13 files changed

+241
-178
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include "CdpJson.h"
9+
10+
#include <folly/dynamic.h>
11+
#include <folly/json.h>
12+
13+
namespace facebook::react::jsinspector_modern::cdp {
14+
15+
PreparsedRequest preparse(std::string_view message) {
16+
folly::dynamic parsed = folly::parseJson(message);
17+
return PreparsedRequest{
18+
.id = parsed["id"].getInt(),
19+
.method = parsed["method"].getString(),
20+
.params = parsed.count("params") != 0u ? parsed["params"] : nullptr};
21+
}
22+
23+
std::string PreparsedRequest::toJson() const {
24+
folly::dynamic obj = folly::dynamic::object;
25+
obj["id"] = id;
26+
obj["method"] = method;
27+
if (params != nullptr) {
28+
obj["params"] = params;
29+
}
30+
return folly::toJson(obj);
31+
}
32+
33+
std::string jsonError(
34+
std::optional<RequestId> id,
35+
ErrorCode code,
36+
std::optional<std::string> message) {
37+
auto dynamicError = folly::dynamic::object("code", static_cast<int>(code));
38+
if (message) {
39+
dynamicError("message", *message);
40+
}
41+
return folly::toJson(
42+
(id ? folly::dynamic::object("id", *id)
43+
: folly::dynamic::object(
44+
"id", nullptr))("error", std::move(dynamicError)));
45+
}
46+
47+
std::string jsonResult(RequestId id, const folly::dynamic& result) {
48+
return folly::toJson(folly::dynamic::object("id", id)("result", result));
49+
}
50+
51+
std::string jsonNotification(
52+
std::string_view method,
53+
std::optional<folly::dynamic> params) {
54+
auto dynamicNotification = folly::dynamic::object("method", method);
55+
if (params) {
56+
dynamicNotification("params", *params);
57+
}
58+
return folly::toJson(std::move(dynamicNotification));
59+
}
60+
61+
} // namespace facebook::react::jsinspector_modern::cdp
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include <folly/dynamic.h>
11+
#include <folly/json.h>
12+
#include <string>
13+
#include <string_view>
14+
15+
namespace facebook::react::jsinspector_modern::cdp {
16+
17+
using RequestId = long long;
18+
19+
/**
20+
* Error codes to be used in CDP responses.
21+
* https://www.jsonrpc.org/specification#error_object
22+
*/
23+
enum class ErrorCode {
24+
ParseError = -32700,
25+
InvalidRequest = -32600,
26+
MethodNotFound = -32601,
27+
InvalidParams = -32602,
28+
InternalError = -32603
29+
/* -32000 to -32099: Implementation-defined server errors. */
30+
};
31+
32+
/**
33+
* An incoming CDP request that has been parsed into a more usable form.
34+
*/
35+
struct PreparsedRequest {
36+
public:
37+
/**
38+
* The ID of the request.
39+
*/
40+
RequestId id{};
41+
42+
/**
43+
* The name of the method being invoked.
44+
*/
45+
std::string method;
46+
47+
/**
48+
* The parameters passed to the method, if any.
49+
*/
50+
folly::dynamic params;
51+
52+
/**
53+
* Equality operator, useful for unit tests
54+
*/
55+
inline bool operator==(const PreparsedRequest& rhs) const {
56+
return id == rhs.id && method == rhs.method && params == rhs.params;
57+
}
58+
59+
std::string toJson() const;
60+
};
61+
62+
/**
63+
* Parse a JSON-encoded CDP request into its constituent parts.
64+
* \throws ParseError If the input cannot be parsed.
65+
* \throws TypeError If the input does not conform to the expected format.
66+
*/
67+
PreparsedRequest preparse(std::string_view message);
68+
69+
/**
70+
* A type error that may be thrown while preparsing a request, or while
71+
* accessing dynamic params on a request.
72+
*/
73+
using TypeError = folly::TypeError;
74+
75+
/**
76+
* A parse error that may be thrown while preparsing a request.
77+
*/
78+
using ParseError = folly::json::parse_error;
79+
80+
/**
81+
* Helper functions for creating CDP (loosely JSON-RPC) messages of various
82+
* types, returning a JSON string ready for sending over the wire.
83+
*/
84+
85+
/**
86+
* Returns a JSON-formatted string representing an error.
87+
*
88+
* {"id": <id>, "error": { "code": <code>, "message": <message> }}
89+
*
90+
* \param id Request ID. Mandatory, null only if the request omitted it or
91+
* could not be parsed.
92+
* \param code Integer code from cdp::ErrorCode.
93+
* \param message Optional, brief human-readable error message.
94+
*/
95+
std::string jsonError(
96+
std::optional<RequestId> id,
97+
ErrorCode code,
98+
std::optional<std::string> message = std::nullopt);
99+
100+
/**
101+
* Returns a JSON-formatted string representing a successful response.
102+
*
103+
* {"id": <id>, "result": <result>}
104+
*
105+
* \param id The id of the request that this response corresponds to.
106+
* \param result Result payload, defaulting to {}.
107+
*/
108+
std::string jsonResult(
109+
RequestId id,
110+
const folly::dynamic& result = folly::dynamic::object());
111+
112+
/**
113+
* Returns a JSON-formatted string representing a unilateral notifcation.
114+
*
115+
* {"method": <method>, "params": <params>}
116+
*
117+
* \param method Notification (aka "event") method.
118+
* \param params Optional payload pbject.
119+
*/
120+
std::string jsonNotification(
121+
std::string_view method,
122+
std::optional<folly::dynamic> params = std::nullopt);
123+
124+
} // namespace facebook::react::jsinspector_modern::cdp

packages/react-native/ReactCommon/jsinspector-modern/FallbackRuntimeAgentDelegate.cpp

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,17 +54,16 @@ void FallbackRuntimeAgentDelegate::sendFallbackRuntimeWarning() {
5454
}
5555

5656
void FallbackRuntimeAgentDelegate::sendWarningLogEntry(std::string_view text) {
57-
frontendChannel_(
58-
folly::toJson(folly::dynamic::object("method", "Log.entryAdded")(
59-
"params",
57+
frontendChannel_(cdp::jsonNotification(
58+
"Log.entryAdded",
59+
folly::dynamic::object(
60+
"entry",
6061
folly::dynamic::object(
61-
"entry",
62-
folly::dynamic::object(
63-
"timestamp",
64-
duration_cast<milliseconds>(
65-
system_clock::now().time_since_epoch())
66-
.count())("source", "other")(
67-
"level", "warning")("text", text)))));
62+
"timestamp",
63+
duration_cast<milliseconds>(
64+
system_clock::now().time_since_epoch())
65+
.count())("source", "other")(
66+
"level", "warning")("text", text))));
6867
}
6968

7069
} // namespace facebook::react::jsinspector_modern

packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp

Lines changed: 17 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
* LICENSE file in the root directory of this source tree.
66
*/
77

8+
#include "CdpJson.h"
9+
810
#include <folly/dynamic.h>
911
#include <folly/json.h>
1012
#include <jsinspector-modern/HostAgent.h>
@@ -98,33 +100,27 @@ void HostAgent::handleRequest(const cdp::PreparsedRequest& req) {
98100
}
99101

100102
if (shouldSendOKResponse) {
101-
folly::dynamic res = folly::dynamic::object("id", req.id)(
102-
"result", folly::dynamic::object());
103-
std::string json = folly::toJson(res);
104-
frontendChannel_(json);
103+
frontendChannel_(cdp::jsonResult(req.id));
105104
return;
106105
}
107106

108-
folly::dynamic res = folly::dynamic::object("id", req.id)(
109-
"error",
110-
folly::dynamic::object("code", -32601)(
111-
"message", req.method + " not implemented yet"));
112-
std::string json = folly::toJson(res);
113-
frontendChannel_(json);
107+
frontendChannel_(cdp::jsonError(
108+
req.id,
109+
cdp::ErrorCode::MethodNotFound,
110+
req.method + " not implemented yet"));
114111
}
115112

116113
void HostAgent::sendInfoLogEntry(std::string_view text) {
117-
frontendChannel_(
118-
folly::toJson(folly::dynamic::object("method", "Log.entryAdded")(
119-
"params",
114+
frontendChannel_(cdp::jsonNotification(
115+
"Log.entryAdded",
116+
folly::dynamic::object(
117+
"entry",
120118
folly::dynamic::object(
121-
"entry",
122-
folly::dynamic::object(
123-
"timestamp",
124-
duration_cast<milliseconds>(
125-
system_clock::now().time_since_epoch())
126-
.count())("source", "other")(
127-
"level", "info")("text", text)))));
119+
"timestamp",
120+
duration_cast<milliseconds>(
121+
system_clock::now().time_since_epoch())
122+
.count())("source", "other")(
123+
"level", "info")("text", text))));
128124
}
129125

130126
void HostAgent::setCurrentInstanceAgent(
@@ -140,9 +136,7 @@ void HostAgent::setCurrentInstanceAgent(
140136

141137
// Because we can only have a single instance, we can report all contexts
142138
// as cleared.
143-
folly::dynamic contextsCleared =
144-
folly::dynamic::object("method", "Runtime.executionContextsCleared");
145-
frontendChannel_(folly::toJson(contextsCleared));
139+
frontendChannel_(cdp::jsonNotification("Runtime.executionContextsCleared"));
146140
}
147141
if (instanceAgent_) {
148142
// TODO: Send Runtime.executionContextCreated here - at the moment we expect

packages/react-native/ReactCommon/jsinspector-modern/HostAgent.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212

1313
#include <jsinspector-modern/InspectorInterfaces.h>
1414
#include <jsinspector-modern/InstanceAgent.h>
15-
#include <jsinspector-modern/Parsing.h>
1615

1716
#include <functional>
1817
#include <string_view>

packages/react-native/ReactCommon/jsinspector-modern/HostTarget.cpp

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
*/
77

88
#include "HostTarget.h"
9+
#include "CdpJson.h"
910
#include "HostAgent.h"
1011
#include "InspectorInterfaces.h"
1112
#include "InspectorUtilities.h"
1213
#include "InstanceTarget.h"
13-
#include "Parsing.h"
1414
#include "SessionState.h"
1515

1616
#include <folly/dynamic.h>
@@ -53,14 +53,12 @@ class HostTargetSession {
5353
try {
5454
request = cdp::preparse(message);
5555
} catch (const cdp::ParseError& e) {
56-
frontendChannel_(folly::toJson(folly::dynamic::object("id", nullptr)(
57-
"error",
58-
folly::dynamic::object("code", -32700)("message", e.what()))));
56+
frontendChannel_(
57+
cdp::jsonError(std::nullopt, cdp::ErrorCode::ParseError, e.what()));
5958
return;
6059
} catch (const cdp::TypeError& e) {
61-
frontendChannel_(folly::toJson(folly::dynamic::object("id", nullptr)(
62-
"error",
63-
folly::dynamic::object("code", -32600)("message", e.what()))));
60+
frontendChannel_(cdp::jsonError(
61+
std::nullopt, cdp::ErrorCode::InvalidRequest, e.what()));
6462
return;
6563
}
6664

@@ -69,9 +67,8 @@ class HostTargetSession {
6967
try {
7068
hostAgent_.handleRequest(request);
7169
} catch (const cdp::TypeError& e) {
72-
frontendChannel_(folly::toJson(folly::dynamic::object("id", request.id)(
73-
"error",
74-
folly::dynamic::object("code", -32600)("message", e.what()))));
70+
frontendChannel_(
71+
cdp::jsonError(request.id, cdp::ErrorCode::InvalidRequest, e.what()));
7572
return;
7673
}
7774
}

packages/react-native/ReactCommon/jsinspector-modern/InstanceAgent.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
#include <jsinspector-modern/InstanceAgent.h>
9+
#include "CdpJson.h"
910
#include "RuntimeTarget.h"
1011

1112
namespace facebook::react::jsinspector_modern {
@@ -49,9 +50,8 @@ void InstanceAgent::setCurrentRuntime(RuntimeTarget* runtimeTarget) {
4950
if (previousContext.uniqueId.has_value()) {
5051
params["executionContextUniqueId"] = *previousContext.uniqueId;
5152
}
52-
folly::dynamic contextDestroyed = folly::dynamic::object(
53-
"method", "Runtime.executionContextDestroyed")("params", params);
54-
frontendChannel_(folly::toJson(contextDestroyed));
53+
frontendChannel_(
54+
cdp::jsonNotification("Runtime.executionContextDestroyed", params));
5555
}
5656
maybeSendExecutionContextCreatedNotification();
5757
}
@@ -66,9 +66,8 @@ void InstanceAgent::maybeSendExecutionContextCreatedNotification() {
6666
if (newContext.uniqueId.has_value()) {
6767
params["uniqueId"] = *newContext.uniqueId;
6868
}
69-
folly::dynamic contextCreated = folly::dynamic::object(
70-
"method", "Runtime.executionContextCreated")("params", params);
71-
frontendChannel_(folly::toJson(contextCreated));
69+
frontendChannel_(
70+
cdp::jsonNotification("Runtime.executionContextCreated", params));
7271
}
7372
}
7473

packages/react-native/ReactCommon/jsinspector-modern/InstanceAgent.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77

88
#pragma once
99

10+
#include "CdpJson.h"
1011
#include "RuntimeTarget.h"
1112
#include "SessionState.h"
1213

1314
#include <jsinspector-modern/InspectorInterfaces.h>
14-
#include <jsinspector-modern/Parsing.h>
1515
#include <jsinspector-modern/RuntimeAgent.h>
1616

1717
#include <functional>

0 commit comments

Comments
 (0)