Skip to content

Commit

Permalink
Add helpers for formatting JSON CDP responses (#43340)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
robhogan authored and facebook-github-bot committed Mar 6, 2024
1 parent 9426d24 commit 3ed0ff3
Show file tree
Hide file tree
Showing 13 changed files with 241 additions and 178 deletions.
61 changes: 61 additions & 0 deletions packages/react-native/ReactCommon/jsinspector-modern/CdpJson.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#include "CdpJson.h"

#include <folly/dynamic.h>
#include <folly/json.h>

namespace facebook::react::jsinspector_modern::cdp {

PreparsedRequest preparse(std::string_view message) {
folly::dynamic parsed = folly::parseJson(message);
return PreparsedRequest{
.id = parsed["id"].getInt(),
.method = parsed["method"].getString(),
.params = parsed.count("params") != 0u ? parsed["params"] : nullptr};
}

std::string PreparsedRequest::toJson() const {
folly::dynamic obj = folly::dynamic::object;
obj["id"] = id;
obj["method"] = method;
if (params != nullptr) {
obj["params"] = params;
}
return folly::toJson(obj);
}

std::string jsonError(
std::optional<RequestId> id,
ErrorCode code,
std::optional<std::string> message) {
auto dynamicError = folly::dynamic::object("code", static_cast<int>(code));
if (message) {
dynamicError("message", *message);
}
return folly::toJson(
(id ? folly::dynamic::object("id", *id)
: folly::dynamic::object(
"id", nullptr))("error", std::move(dynamicError)));
}

std::string jsonResult(RequestId id, const folly::dynamic& result) {
return folly::toJson(folly::dynamic::object("id", id)("result", result));
}

std::string jsonNotification(
std::string_view method,
std::optional<folly::dynamic> params) {
auto dynamicNotification = folly::dynamic::object("method", method);
if (params) {
dynamicNotification("params", *params);
}
return folly::toJson(std::move(dynamicNotification));
}

} // namespace facebook::react::jsinspector_modern::cdp
124 changes: 124 additions & 0 deletions packages/react-native/ReactCommon/jsinspector-modern/CdpJson.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

#pragma once

#include <folly/dynamic.h>
#include <folly/json.h>
#include <string>
#include <string_view>

namespace facebook::react::jsinspector_modern::cdp {

using RequestId = long long;

/**
* Error codes to be used in CDP responses.
* https://www.jsonrpc.org/specification#error_object
*/
enum class ErrorCode {
ParseError = -32700,
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603
/* -32000 to -32099: Implementation-defined server errors. */
};

/**
* An incoming CDP request that has been parsed into a more usable form.
*/
struct PreparsedRequest {
public:
/**
* The ID of the request.
*/
RequestId id{};

/**
* The name of the method being invoked.
*/
std::string method;

/**
* The parameters passed to the method, if any.
*/
folly::dynamic params;

/**
* Equality operator, useful for unit tests
*/
inline bool operator==(const PreparsedRequest& rhs) const {
return id == rhs.id && method == rhs.method && params == rhs.params;
}

std::string toJson() const;
};

/**
* Parse a JSON-encoded CDP request into its constituent parts.
* \throws ParseError If the input cannot be parsed.
* \throws TypeError If the input does not conform to the expected format.
*/
PreparsedRequest preparse(std::string_view message);

/**
* A type error that may be thrown while preparsing a request, or while
* accessing dynamic params on a request.
*/
using TypeError = folly::TypeError;

/**
* A parse error that may be thrown while preparsing a request.
*/
using ParseError = folly::json::parse_error;

/**
* Helper functions for creating CDP (loosely JSON-RPC) messages of various
* types, returning a JSON string ready for sending over the wire.
*/

/**
* Returns a JSON-formatted string representing an error.
*
* {"id": <id>, "error": { "code": <code>, "message": <message> }}
*
* \param id Request ID. Mandatory, null only if the request omitted it or
* could not be parsed.
* \param code Integer code from cdp::ErrorCode.
* \param message Optional, brief human-readable error message.
*/
std::string jsonError(
std::optional<RequestId> id,
ErrorCode code,
std::optional<std::string> message = std::nullopt);

/**
* Returns a JSON-formatted string representing a successful response.
*
* {"id": <id>, "result": <result>}
*
* \param id The id of the request that this response corresponds to.
* \param result Result payload, defaulting to {}.
*/
std::string jsonResult(
RequestId id,
const folly::dynamic& result = folly::dynamic::object());

/**
* Returns a JSON-formatted string representing a unilateral notifcation.
*
* {"method": <method>, "params": <params>}
*
* \param method Notification (aka "event") method.
* \param params Optional payload pbject.
*/
std::string jsonNotification(
std::string_view method,
std::optional<folly::dynamic> params = std::nullopt);

} // namespace facebook::react::jsinspector_modern::cdp
Original file line number Diff line number Diff line change
Expand Up @@ -54,17 +54,16 @@ void FallbackRuntimeAgentDelegate::sendFallbackRuntimeWarning() {
}

void FallbackRuntimeAgentDelegate::sendWarningLogEntry(std::string_view text) {
frontendChannel_(
folly::toJson(folly::dynamic::object("method", "Log.entryAdded")(
"params",
frontendChannel_(cdp::jsonNotification(
"Log.entryAdded",
folly::dynamic::object(
"entry",
folly::dynamic::object(
"entry",
folly::dynamic::object(
"timestamp",
duration_cast<milliseconds>(
system_clock::now().time_since_epoch())
.count())("source", "other")(
"level", "warning")("text", text)))));
"timestamp",
duration_cast<milliseconds>(
system_clock::now().time_since_epoch())
.count())("source", "other")(
"level", "warning")("text", text))));
}

} // namespace facebook::react::jsinspector_modern
40 changes: 17 additions & 23 deletions packages/react-native/ReactCommon/jsinspector-modern/HostAgent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* LICENSE file in the root directory of this source tree.
*/

#include "CdpJson.h"

#include <folly/dynamic.h>
#include <folly/json.h>
#include <jsinspector-modern/HostAgent.h>
Expand Down Expand Up @@ -98,33 +100,27 @@ void HostAgent::handleRequest(const cdp::PreparsedRequest& req) {
}

if (shouldSendOKResponse) {
folly::dynamic res = folly::dynamic::object("id", req.id)(
"result", folly::dynamic::object());
std::string json = folly::toJson(res);
frontendChannel_(json);
frontendChannel_(cdp::jsonResult(req.id));
return;
}

folly::dynamic res = folly::dynamic::object("id", req.id)(
"error",
folly::dynamic::object("code", -32601)(
"message", req.method + " not implemented yet"));
std::string json = folly::toJson(res);
frontendChannel_(json);
frontendChannel_(cdp::jsonError(
req.id,
cdp::ErrorCode::MethodNotFound,
req.method + " not implemented yet"));
}

void HostAgent::sendInfoLogEntry(std::string_view text) {
frontendChannel_(
folly::toJson(folly::dynamic::object("method", "Log.entryAdded")(
"params",
frontendChannel_(cdp::jsonNotification(
"Log.entryAdded",
folly::dynamic::object(
"entry",
folly::dynamic::object(
"entry",
folly::dynamic::object(
"timestamp",
duration_cast<milliseconds>(
system_clock::now().time_since_epoch())
.count())("source", "other")(
"level", "info")("text", text)))));
"timestamp",
duration_cast<milliseconds>(
system_clock::now().time_since_epoch())
.count())("source", "other")(
"level", "info")("text", text))));
}

void HostAgent::setCurrentInstanceAgent(
Expand All @@ -140,9 +136,7 @@ void HostAgent::setCurrentInstanceAgent(

// Because we can only have a single instance, we can report all contexts
// as cleared.
folly::dynamic contextsCleared =
folly::dynamic::object("method", "Runtime.executionContextsCleared");
frontendChannel_(folly::toJson(contextsCleared));
frontendChannel_(cdp::jsonNotification("Runtime.executionContextsCleared"));
}
if (instanceAgent_) {
// TODO: Send Runtime.executionContextCreated here - at the moment we expect
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

#include <jsinspector-modern/InspectorInterfaces.h>
#include <jsinspector-modern/InstanceAgent.h>
#include <jsinspector-modern/Parsing.h>

#include <functional>
#include <string_view>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
*/

#include "HostTarget.h"
#include "CdpJson.h"
#include "HostAgent.h"
#include "InspectorInterfaces.h"
#include "InspectorUtilities.h"
#include "InstanceTarget.h"
#include "Parsing.h"
#include "SessionState.h"

#include <folly/dynamic.h>
Expand Down Expand Up @@ -53,14 +53,12 @@ class HostTargetSession {
try {
request = cdp::preparse(message);
} catch (const cdp::ParseError& e) {
frontendChannel_(folly::toJson(folly::dynamic::object("id", nullptr)(
"error",
folly::dynamic::object("code", -32700)("message", e.what()))));
frontendChannel_(
cdp::jsonError(std::nullopt, cdp::ErrorCode::ParseError, e.what()));
return;
} catch (const cdp::TypeError& e) {
frontendChannel_(folly::toJson(folly::dynamic::object("id", nullptr)(
"error",
folly::dynamic::object("code", -32600)("message", e.what()))));
frontendChannel_(cdp::jsonError(
std::nullopt, cdp::ErrorCode::InvalidRequest, e.what()));
return;
}

Expand All @@ -69,9 +67,8 @@ class HostTargetSession {
try {
hostAgent_.handleRequest(request);
} catch (const cdp::TypeError& e) {
frontendChannel_(folly::toJson(folly::dynamic::object("id", request.id)(
"error",
folly::dynamic::object("code", -32600)("message", e.what()))));
frontendChannel_(
cdp::jsonError(request.id, cdp::ErrorCode::InvalidRequest, e.what()));
return;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

#include <jsinspector-modern/InstanceAgent.h>
#include "CdpJson.h"
#include "RuntimeTarget.h"

namespace facebook::react::jsinspector_modern {
Expand Down Expand Up @@ -49,9 +50,8 @@ void InstanceAgent::setCurrentRuntime(RuntimeTarget* runtimeTarget) {
if (previousContext.uniqueId.has_value()) {
params["executionContextUniqueId"] = *previousContext.uniqueId;
}
folly::dynamic contextDestroyed = folly::dynamic::object(
"method", "Runtime.executionContextDestroyed")("params", params);
frontendChannel_(folly::toJson(contextDestroyed));
frontendChannel_(
cdp::jsonNotification("Runtime.executionContextDestroyed", params));
}
maybeSendExecutionContextCreatedNotification();
}
Expand All @@ -66,9 +66,8 @@ void InstanceAgent::maybeSendExecutionContextCreatedNotification() {
if (newContext.uniqueId.has_value()) {
params["uniqueId"] = *newContext.uniqueId;
}
folly::dynamic contextCreated = folly::dynamic::object(
"method", "Runtime.executionContextCreated")("params", params);
frontendChannel_(folly::toJson(contextCreated));
frontendChannel_(
cdp::jsonNotification("Runtime.executionContextCreated", params));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@

#pragma once

#include "CdpJson.h"
#include "RuntimeTarget.h"
#include "SessionState.h"

#include <jsinspector-modern/InspectorInterfaces.h>
#include <jsinspector-modern/Parsing.h>
#include <jsinspector-modern/RuntimeAgent.h>

#include <functional>
Expand Down
Loading

0 comments on commit 3ed0ff3

Please sign in to comment.