Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add helpers for formatting JSON CDP responses #43340

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading