Skip to content

Commit 736b315

Browse files
Lorenzo DelgadoLNSD
authored andcommitted
feat(rest): Add HTTP REST API (#727). Debug API POC
1 parent 469b49f commit 736b315

File tree

13 files changed

+483
-1
lines changed

13 files changed

+483
-1
lines changed

.gitmodules

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,8 @@
143143
[submodule "vendor/nim-toml-serialization"]
144144
path = vendor/nim-toml-serialization
145145
url = https://github.com/status-im/nim-toml-serialization.git
146+
[submodule "vendor/nim-presto"]
147+
path = vendor/nim-presto
148+
url = https://github.com/status-im/nim-presto.git
149+
ignore = untracked
150+
branch = master

tests/all_tests_v2.nim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import
1010
./v2/test_waku_swap,
1111
./v2/test_message_store,
1212
./v2/test_jsonrpc_waku,
13+
./v2/test_rest_serdes,
14+
./v2/test_rest_debug_api_serdes,
15+
./v2/test_rest_debug_api,
1316
./v2/test_peer_manager,
1417
./v2/test_web3, # TODO remove it when rln-relay tests get finalized
1518
./v2/test_waku_bridge,

tests/v2/test_rest_debug_api.nim

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import
2+
stew/shims/net,
3+
chronicles,
4+
testutils/unittests,
5+
presto,
6+
libp2p/crypto/crypto
7+
import
8+
../../waku/v2/node/wakunode2,
9+
../../waku/v2/node/rest/[server, client, debug_api]
10+
11+
12+
proc testWakuNode(): WakuNode =
13+
let
14+
rng = crypto.newRng()
15+
privkey = crypto.PrivateKey.random(Secp256k1, rng[]).tryGet()
16+
bindIp = ValidIpAddress.init("0.0.0.0")
17+
extIp = ValidIpAddress.init("127.0.0.1")
18+
port = Port(9000)
19+
20+
WakuNode.new(privkey, bindIp, port, some(extIp), some(port))
21+
22+
23+
suite "REST API - Debug":
24+
asyncTest "Get node info - GET /debug/v1/info":
25+
# Given
26+
let node = testWakuNode()
27+
await node.start()
28+
node.mountRelay()
29+
30+
let restPort = Port(8546)
31+
let restAddress = ValidIpAddress.init("0.0.0.0")
32+
let restServer = RestServerRef.init(
33+
restAddress,
34+
restPort,
35+
none(string),
36+
none(RestServerConf)
37+
)
38+
39+
installDebugApiHandlers(restServer.router, node)
40+
restServer.start()
41+
42+
# When
43+
let client = newRestHttpClient(initTAddress(restAddress, restPort))
44+
let response = await client.debugInfoV1()
45+
46+
# Then
47+
check:
48+
response.listenAddresses == @[$node.switch.peerInfo.addrs[^1] & "/p2p/" & $node.switch.peerInfo.peerId]
49+
50+
await restServer.stop()
51+
await restServer.closeWait()
52+
await node.stop()
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import std/typetraits
2+
import chronicles,
3+
unittest2,
4+
stew/[results, byteutils],
5+
json_serialization
6+
import
7+
../../waku/v2/node/rest/[serdes, debug_api]
8+
9+
10+
suite "Debug API - serialization":
11+
12+
suite "DebugWakuInfo - decode":
13+
test "optional field is not provided":
14+
# Given
15+
let jsonBytes = toBytes("""{ "listenAddresses":["123"] }""")
16+
17+
# When
18+
let res = decodeFromJsonBytes(DebugWakuInfo, jsonBytes, requireAllFields = true)
19+
20+
# Then
21+
require(res.isOk)
22+
let value = res.get()
23+
check:
24+
value.listenAddresses == @["123"]
25+
value.enrUri.isNone
26+
27+
suite "DebugWakuInfo - encode":
28+
test "optional field is none":
29+
# Given
30+
let data = DebugWakuInfo(listenAddresses: @["GO"], enrUri: none(string))
31+
32+
# When
33+
let res = encodeIntoJsonBytes(data)
34+
35+
# Then
36+
require(res.isOk)
37+
let value = res.get()
38+
check:
39+
value == toBytes("""{"listenAddresses":["GO"]}""")

tests/v2/test_rest_serdes.nim

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import std/typetraits
2+
import chronicles,
3+
unittest2,
4+
stew/[results, byteutils],
5+
json_serialization
6+
import
7+
../../waku/v2/node/rest/[serdes, debug_api]
8+
9+
10+
# TODO: Decouple this test suite from the `debug_api` module by defining
11+
# private custom types for this test suite module
12+
suite "Serdes":
13+
14+
suite "decode":
15+
test "decodeFromJsonString - use the corresponding readValue template":
16+
# Given
17+
let jsonString = JsonString("""{ "listenAddresses":["123"] }""")
18+
19+
# When
20+
let res = decodeFromJsonString(DebugWakuInfo, jsonString, requireAllFields = true)
21+
22+
# Then
23+
require(res.isOk)
24+
let value = res.get()
25+
check:
26+
value.listenAddresses == @["123"]
27+
value.enrUri.isNone
28+
29+
test "decodeFromJsonBytes - use the corresponding readValue template":
30+
# Given
31+
let jsonBytes = toBytes("""{ "listenAddresses":["123"] }""")
32+
33+
# When
34+
let res = decodeFromJsonBytes(DebugWakuInfo, jsonBytes, requireAllFields = true)
35+
36+
# Then
37+
require(res.isOk)
38+
let value = res.get()
39+
check:
40+
value.listenAddresses == @["123"]
41+
value.enrUri.isNone
42+
43+
suite "encode":
44+
test "encodeIntoJsonString - use the corresponding writeValue template":
45+
# Given
46+
let data = DebugWakuInfo(listenAddresses: @["GO"])
47+
48+
# When
49+
let res = encodeIntoJsonString(data)
50+
51+
# Then
52+
require(res.isOk)
53+
let value = res.get()
54+
check:
55+
value == """{"listenAddresses":["GO"]}"""
56+
57+
test "encodeIntoJsonBytes - use the corresponding writeValue template":
58+
# Given
59+
let data = DebugWakuInfo(listenAddresses: @["ABC"])
60+
61+
# When
62+
let res = encodeIntoJsonBytes(data)
63+
64+
# Then
65+
require(res.isOk)
66+
let value = res.get()
67+
check:
68+
value == toBytes("""{"listenAddresses":["ABC"]}""" )

vendor/nim-presto

Submodule nim-presto added at 1dba6dd

waku.nimble

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ requires "nim >= 1.2.0",
2020
"stint",
2121
"metrics",
2222
"libp2p", # Only for Waku v2
23-
"web3"
23+
"web3",
24+
"presto"
2425

2526
### Helper functions
2627
proc buildBinary(name: string, srcDir = "./", params = "", lang = "c") =

waku/v2/node/rest/client.nim

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{.push raises: [Defect].}
2+
3+
import presto/client
4+
5+
proc newRestHttpClient*(address: TransportAddress): RestClientRef =
6+
RestClientRef.new(address, HttpClientScheme.NonSecure)

waku/v2/node/rest/debug_api.nim

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
{.push raises: [Defect].}
2+
3+
import
4+
stew/byteutils,
5+
chronicles,
6+
json_serialization,
7+
json_serialization/std/options,
8+
presto/[route, client]
9+
import "."/[serdes, utils],
10+
../wakunode2
11+
12+
logScope: topics = "rest_api_debug"
13+
14+
15+
#### Types
16+
17+
type
18+
DebugWakuInfo* = object
19+
listenAddresses*: seq[string]
20+
enrUri*: Option[string]
21+
22+
23+
#### Serialization and deserialization
24+
25+
proc writeValue*(writer: var JsonWriter[RestJson], value: DebugWakuInfo)
26+
{.raises: [IOError, Defect].} =
27+
writer.beginRecord()
28+
writer.writeField("listenAddresses", value.listenAddresses)
29+
if value.enrUri.isSome:
30+
writer.writeField("enrUri", value.enrUri)
31+
writer.endRecord()
32+
33+
proc readValue*(reader: var JsonReader[RestJson], value: var DebugWakuInfo)
34+
{.raises: [SerializationError, IOError, Defect].} =
35+
var
36+
listenAddresses: Option[seq[string]]
37+
enrUri: Option[string]
38+
39+
for fieldName in readObjectFields(reader):
40+
case fieldName
41+
of "listenAddresses":
42+
if listenAddresses.isSome():
43+
reader.raiseUnexpectedField("Multiple `listenAddresses` fields found", "DebugWakuInfo")
44+
listenAddresses = some(reader.readValue(seq[string]))
45+
of "enrUri":
46+
if enrUri.isSome():
47+
reader.raiseUnexpectedField("Multiple `enrUri` fields found", "DebugWakuInfo")
48+
enrUri = some(reader.readValue(string))
49+
else:
50+
unrecognizedFieldWarning()
51+
52+
if listenAddresses.isNone():
53+
reader.raiseUnexpectedValue("Field `listenAddresses` is missing")
54+
55+
value = DebugWakuInfo(
56+
listenAddresses: listenAddresses.get,
57+
enrUri: enrUri
58+
)
59+
60+
61+
#### Server request handlers
62+
63+
proc toDebugWakuInfo(nodeInfo: WakuInfo): DebugWakuInfo =
64+
DebugWakuInfo(
65+
listenAddresses: nodeInfo.listenAddresses,
66+
enrUri: some(nodeInfo.enrUri)
67+
)
68+
69+
const ROUTE_DEBUG_INFOV1* = "/debug/v1/info"
70+
71+
proc installDebugInfoV1Handler(router: var RestRouter, node: WakuNode) =
72+
router.api(MethodGet, ROUTE_DEBUG_INFOV1) do () -> RestApiResponse:
73+
let info = node.info().toDebugWakuInfo()
74+
let resp = RestApiResponse.jsonResponse(info, status=Http200)
75+
if resp.isErr():
76+
debug "An error ocurred while building the json respose", error=resp.error()
77+
return RestApiResponse.internalServerError()
78+
79+
return resp.get()
80+
81+
proc installDebugApiHandlers*(router: var RestRouter, node: WakuNode) =
82+
installDebugInfoV1Handler(router, node)
83+
84+
85+
#### Client
86+
87+
proc decodeBytes*(t: typedesc[DebugWakuInfo], data: openArray[byte], contentType: string): RestResult[DebugWakuInfo] =
88+
if MediaType.init(contentType) != MIMETYPE_JSON:
89+
error "Unsupported respose contentType value", contentType = contentType
90+
return err("Unsupported response contentType")
91+
92+
let decoded = ?decodeFromJsonBytes(DebugWakuInfo, data)
93+
return ok(decoded)
94+
95+
# TODO: Check how we can use a constant to set the method endpoint (improve "rest" pragma under nim-presto)
96+
proc debugInfoV1*(): DebugWakuInfo {.rest, endpoint: "/debug/v1/info", meth: HttpMethod.MethodGet.}

waku/v2/node/rest/debug_api.yaml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
openapi: 3.1.0
2+
info:
3+
title: Waku REST API - Debug
4+
version: 1.0.0
5+
6+
paths:
7+
/debug/v1/info:
8+
get:
9+
summary: Get node info
10+
description: Retrieve information about a Waku v2 node.
11+
tags:
12+
- Debug
13+
responses:
14+
'200':
15+
description: Information about a Waku v2 node.
16+
content:
17+
application/json:
18+
schema:
19+
$ref: '#/components/schemas/WakuInfo'
20+
'5XX':
21+
description: Unexpected error.
22+
23+
components:
24+
schemas:
25+
WakuInfo:
26+
type: object
27+
properties:
28+
listenAddresses:
29+
type: array
30+
items:
31+
type: string
32+
enrUri:
33+
type: string
34+
required:
35+
- listenAddresses

0 commit comments

Comments
 (0)