diff --git a/codex/rest/api.nim b/codex/rest/api.nim index ee3765af..0fd87b43 100644 --- a/codex/rest/api.nim +++ b/codex/rest/api.nim @@ -34,81 +34,18 @@ import ../conf import ../contracts import ../streams +import ./coders import ./json +logScope: + topics = "codex restapi" + proc validate( pattern: string, value: string): int {.gcsafe, raises: [Defect].} = 0 -proc encodeString(cid: type Cid): Result[string, cstring] = - ok($cid) - -proc decodeString(T: type Cid, value: string): Result[Cid, cstring] = - Cid - .init(value) - .mapErr do(e: CidError) -> cstring: - case e - of CidError.Incorrect: "Incorrect Cid".cstring - of CidError.Unsupported: "Unsupported Cid".cstring - of CidError.Overrun: "Overrun Cid".cstring - else: "Error parsing Cid".cstring - -proc encodeString(peerId: PeerID): Result[string, cstring] = - ok($peerId) - -proc decodeString(T: type PeerID, value: string): Result[PeerID, cstring] = - PeerID.init(value) - -proc encodeString(address: MultiAddress): Result[string, cstring] = - ok($address) - -proc decodeString(T: type MultiAddress, value: string): Result[MultiAddress, cstring] = - MultiAddress - .init(value) - .mapErr do(e: string) -> cstring: cstring(e) - -proc decodeString(T: type SomeUnsignedInt, value: string): Result[T, cstring] = - Base10.decode(T, value) - -proc encodeString(value: SomeUnsignedInt): Result[string, cstring] = - ok(Base10.toString(value)) - -proc decodeString(T: type Duration, value: string): Result[T, cstring] = - let v = ? Base10.decode(uint32, value) - ok(v.minutes) - -proc encodeString(value: Duration): Result[string, cstring] = - ok($value) - -proc decodeString(T: type bool, value: string): Result[T, cstring] = - try: - ok(value.parseBool()) - except CatchableError as exc: - let s: cstring = exc.msg - err(s) # err(exc.msg) won't compile - -proc encodeString(value: bool): Result[string, cstring] = - ok($value) - -proc decodeString(_: type UInt256, value: string): Result[UInt256, cstring] = - try: - ok UInt256.fromHex(value) - except ValueError as e: - err e.msg.cstring - -proc decodeString(_: type array[32, byte], - value: string): Result[array[32, byte], cstring] = - try: - ok array[32, byte].fromHex(value) - except ValueError as e: - err e.msg.cstring - -proc decodeString[T: PurchaseId | RequestId | Nonce](_: type T, - value: string): Result[T, cstring] = - array[32, byte].decodeString(value).map(id => T(id)) - proc initRestApi*(node: CodexNodeRef, conf: CodexConf): RestRouter = var router = RestRouter.init(validate) router.api( @@ -186,7 +123,7 @@ proc initRestApi*(node: CodexNodeRef, conf: CodexConf): RestRouter = trace "Excepting streaming blocks", exc = exc.msg return RestApiResponse.error(Http500) finally: - trace "Sent bytes", cid = $id.get(), bytes + trace "Sent bytes", cid = id.get(), bytes if not stream.isNil: await stream.close() @@ -210,12 +147,14 @@ proc initRestApi*(node: CodexNodeRef, conf: CodexConf): RestRouter = let nodes = params.nodes |? 1 let tolerance = params.nodes |? 0 - without purchaseId =? await node.requestStorage(cid, - params.duration, - nodes, - tolerance, - params.reward, - params.expiry), error: + without purchaseId =? await node.requestStorage( + cid, + params.duration, + nodes, + tolerance, + params.reward, + params.expiry), error: + return RestApiResponse.error(Http500, error.msg) return RestApiResponse.response(purchaseId.toHex) @@ -259,9 +198,31 @@ proc initRestApi*(node: CodexNodeRef, conf: CodexConf): RestRouter = # if we got here something went wrong? return RestApiResponse.error(Http500) + router.api( + MethodPost, + "/api/codex/v1/debug/chronicles/loglevel") do ( + level: Option[string]) -> RestApiResponse: + ## Set log level at run time + ## + ## e.g. `chronicles/loglevel?level=DEBUG` + ## + ## `level` - chronicles log level + ## + + without res =? level and level =? res: + return RestApiResponse.error(Http400, "Missing log level") + + try: + {.gcsafe.}: + updateLogLevel(level) + except CatchableError as exc: + return RestApiResponse.error(Http500, exc.msg) + + return RestApiResponse.response("") + router.api( MethodGet, - "/api/codex/v1/info") do () -> RestApiResponse: + "/api/codex/v1/debug/info") do () -> RestApiResponse: ## Print rudimentary node information ## diff --git a/codex/rest/coders.nim b/codex/rest/coders.nim new file mode 100644 index 00000000..cef76a38 --- /dev/null +++ b/codex/rest/coders.nim @@ -0,0 +1,95 @@ +## Nim-Codex +## Copyright (c) 2022 Status Research & Development GmbH +## Licensed under either of +## * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) +## * MIT license ([LICENSE-MIT](LICENSE-MIT)) +## at your option. +## This file may not be copied, modified, or distributed except according to +## those terms. + +import std/sugar + +import pkg/presto +import pkg/chronos +import pkg/libp2p +import pkg/stew/base10 +import pkg/stew/byteutils +import pkg/stew/results +import pkg/stint + +import ../sales +import ../purchasing + +proc encodeString*(cid: type Cid): Result[string, cstring] = + ok($cid) + +proc decodeString*(T: type Cid, value: string): Result[Cid, cstring] = + Cid + .init(value) + .mapErr do(e: CidError) -> cstring: + case e + of CidError.Incorrect: "Incorrect Cid".cstring + of CidError.Unsupported: "Unsupported Cid".cstring + of CidError.Overrun: "Overrun Cid".cstring + else: "Error parsing Cid".cstring + +proc encodeString*(peerId: PeerId): Result[string, cstring] = + ok($peerId) + +proc decodeString*(T: type PeerId, value: string): Result[PeerId, cstring] = + PeerID.init(value) + +proc encodeString*(address: MultiAddress): Result[string, cstring] = + ok($address) + +proc decodeString*(T: type MultiAddress, value: string): Result[MultiAddress, cstring] = + MultiAddress + .init(value) + .mapErr do(e: string) -> cstring: cstring(e) + +proc decodeString*(T: type SomeUnsignedInt, value: string): Result[T, cstring] = + Base10.decode(T, value) + +proc encodeString*(value: SomeUnsignedInt): Result[string, cstring] = + ok(Base10.toString(value)) + +proc decodeString*(T: type Duration, value: string): Result[T, cstring] = + let v = ? Base10.decode(uint32, value) + ok(v.minutes) + +proc encodeString*(value: Duration): Result[string, cstring] = + ok($value) + +proc decodeString*(T: type bool, value: string): Result[T, cstring] = + try: + ok(value.parseBool()) + except CatchableError as exc: + let s: cstring = exc.msg + err(s) # err(exc.msg) won't compile + +proc encodeString*(value: bool): Result[string, cstring] = + ok($value) + +proc decodeString*(_: type UInt256, value: string): Result[UInt256, cstring] = + try: + ok UInt256.fromHex(value) + except ValueError as e: + err e.msg.cstring + +proc decodeString*(_: type array[32, byte], + value: string): Result[array[32, byte], cstring] = + try: + ok array[32, byte].fromHex(value) + except ValueError as e: + err e.msg.cstring + +proc decodeString*[T: PurchaseId | RequestId | Nonce](_: type T, + value: string): Result[T, cstring] = + array[32, byte].decodeString(value).map(id => T(id)) + +proc decodeString*(t: typedesc[string], + value: string): Result[string, cstring] = + ok(value) + +proc encodeString*(value: string): RestResult[string] = + ok(value) diff --git a/tests/testIntegration.nim b/tests/testIntegration.nim index 35f3a459..d58e461c 100644 --- a/tests/testIntegration.nim +++ b/tests/testIntegration.nim @@ -35,17 +35,22 @@ ethersuite "Integration tests": "--nat=127.0.0.1", "--disc-ip=127.0.0.1", "--disc-port=8090", - "--persistence", - "--eth-account=" & $accounts[0] + "--persistence", + "--eth-account=" & $accounts[0] ], debug = false) + let + bootstrap = strip( + $(parseJson(client.get(baseurl1 & "/debug/info").body)["spr"]), + chars = {'"'}) + node2 = startNode([ "--api-port=8081", "--data-dir=" & dataDir2, "--nat=127.0.0.1", "--disc-ip=127.0.0.1", "--disc-port=8091", - "--bootstrap-node=" & strip($(parseJson(client.get(baseurl1 & "/info").body)["spr"]), chars = {'"'}), + "--bootstrap-node=" & bootstrap, "--persistence", "--eth-account=" & $accounts[1] ], debug = false) @@ -59,10 +64,15 @@ ethersuite "Integration tests": dataDir2.removeDir() test "nodes can print their peer information": - let info1 = client.get(baseurl1 & "/info").body - let info2 = client.get(baseurl2 & "/info").body + let info1 = client.get(baseurl1 & "/debug/info").body + let info2 = client.get(baseurl2 & "/debug/info").body check info1 != info2 + test "nodes should set chronicles log level": + client.headers = newHttpHeaders({ "Content-Type": "text/plain" }) + let filter = "/debug/chronicles/loglevel?level=DEBUG;TRACE:codex" + check client.request(baseurl1 & filter, httpMethod = HttpPost, body = "").status == "200 OK" + test "node accepts file uploads": let url = baseurl1 & "/upload" let response = client.post(url, "some file contents")