Skip to content

Commit

Permalink
chore: Allow text/plain content type descriptor for json formatted co…
Browse files Browse the repository at this point in the history
…ntent body (#2209)

* Allow text/plain content type descriptor for json formatted content body. Refactored duplicated encode/decode functions for rest api

* Fix relay endpoint decodings of content bodies to accept text/plain

* Added support for content body decoder for checking media type if additional parameters are present

* Fix wrong usage of ContentTypeData - appeared only for tests
  • Loading branch information
NagyZoltanPeter authored Nov 14, 2023
1 parent 2cb0989 commit 6d81e38
Show file tree
Hide file tree
Showing 14 changed files with 131 additions and 336 deletions.
31 changes: 2 additions & 29 deletions waku/waku_api/rest/admin/client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import
import
../serdes,
../responses,
../rest_serdes,
./types

export types
Expand All @@ -21,37 +22,9 @@ export types
logScope:
topics = "waku node rest admin api"

proc decodeBytes*(t: typedesc[seq[WakuPeer]], data: openArray[byte],
contentType: Opt[ContentTypeData]): RestResult[seq[WakuPeer]] =
if MediaType.init($contentType) != MIMETYPE_JSON:
error "Unsupported response contentType value", contentType = contentType
return err("Unsupported response contentType")

let decoded = decodeFromJsonBytes(seq[WakuPeer], data).valueOr:
return err("Invalid response from server, could not decode.")

return ok(decoded)

proc decodeBytes*(t: typedesc[string], value: openArray[byte],
contentType: Opt[ContentTypeData]): RestResult[string] =
if MediaType.init($contentType) != MIMETYPE_TEXT:
error "Unsupported contentType value", contentType = contentType
return err("Unsupported contentType")

var res: string
if len(value) > 0:
res = newString(len(value))
copyMem(addr res[0], unsafeAddr value[0], len(value))
return ok(res)

proc encodeBytes*(value: seq[string],
contentType: string): RestResult[seq[byte]] =
if MediaType.init(contentType) != MIMETYPE_JSON:
error "Unsupported contentType value", contentType = contentType
return err("Unsupported contentType")

let encoded = ?encodeIntoJsonBytes(value)
return ok(encoded)
return encodeBytesOf(value, contentType)

proc getPeers*():
RestResponse[seq[WakuPeer]]
Expand Down
18 changes: 1 addition & 17 deletions waku/waku_api/rest/admin/handlers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import
../../../node/peer_manager,
../responses,
../serdes,
../rest_serdes,
./types

export types
Expand All @@ -32,23 +33,6 @@ const ROUTE_ADMIN_V1_PEERS* = "/admin/v1/peers"

type PeerProtocolTuple = tuple[multiaddr: string, protocol: string, connected: bool]

func decodeRequestBody[T](contentBody: Option[ContentBody]) : Result[T, RestApiResponse] =
if contentBody.isNone():
return err(RestApiResponse.badRequest("Missing content body"))

let reqBodyContentType = MediaType.init($contentBody.get().contentType)
if reqBodyContentType != MIMETYPE_JSON:
return err(RestApiResponse.badRequest("Wrong Content-Type, expected application/json"))

let reqBodyData = contentBody.get().data

let requestResult = decodeFromJsonBytes(T, reqBodyData)
if requestResult.isErr():
return err(RestApiResponse.badRequest("Invalid content body, could not decode. " &
$requestResult.error))

return ok(requestResult.get())

proc tuplesToWakuPeers(peers: var WakuPeers, peersTup: seq[PeerProtocolTuple]) =
for peer in peersTup:
peers.add(peer.multiaddr, peer.protocol, peer.connected)
Expand Down
24 changes: 1 addition & 23 deletions waku/waku_api/rest/debug/client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import
import
../serdes,
../responses,
../rest_serdes,
./types

export types
Expand All @@ -19,31 +20,8 @@ export types
logScope:
topics = "waku node rest debug_api"


proc decodeBytes*(t: typedesc[DebugWakuInfo], data: openArray[byte],
contentType: Opt[ContentTypeData]): RestResult[DebugWakuInfo] =
if MediaType.init($contentType) != MIMETYPE_JSON:
error "Unsupported respose contentType value", contentType = contentType
return err("Unsupported response contentType")

let decoded = ?decodeFromJsonBytes(DebugWakuInfo, data)
return ok(decoded)

# TODO: Check how we can use a constant to set the method endpoint (improve "rest" pragma under nim-presto)
proc debugInfoV1*(): RestResponse[DebugWakuInfo] {.rest, endpoint: "/debug/v1/info", meth: HttpMethod.MethodGet.}


proc decodeBytes*(t: typedesc[string], value: openArray[byte],
contentType: Opt[ContentTypeData]): RestResult[string] =
if MediaType.init($contentType) != MIMETYPE_TEXT:
error "Unsupported contentType value", contentType = contentType
return err("Unsupported contentType")

var res: string
if len(value) > 0:
res = newString(len(value))
copyMem(addr res[0], unsafeAddr value[0], len(value))
return ok(res)

# TODO: Check how we can use a constant to set the method endpoint (improve "rest" pragma under nim-presto)
proc debugVersionV1*(): RestResponse[string] {.rest, endpoint: "/debug/v1/version", meth: HttpMethod.MethodGet.}
52 changes: 5 additions & 47 deletions waku/waku_api/rest/filter/client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import
../../../waku_core,
../serdes,
../responses,
../rest_serdes,
./types

export types
Expand All @@ -25,52 +26,19 @@ logScope:

proc encodeBytes*(value: FilterSubscribeRequest,
contentType: string): RestResult[seq[byte]] =
if MediaType.init(contentType) != MIMETYPE_JSON:
error "Unsupported contentType value", contentType = contentType
return err("Unsupported contentType")

let encoded = ?encodeIntoJsonBytes(value)
return ok(encoded)
return encodeBytesOf(value, contentType)

proc encodeBytes*(value: FilterSubscriberPing,
contentType: string): RestResult[seq[byte]] =
if MediaType.init(contentType) != MIMETYPE_JSON:
error "Unsupported contentType value", contentType = contentType
return err("Unsupported contentType")

let encoded = ?encodeIntoJsonBytes(value)
return ok(encoded)
return encodeBytesOf(value, contentType)

proc encodeBytes*(value: FilterUnsubscribeRequest,
contentType: string): RestResult[seq[byte]] =
if MediaType.init(contentType) != MIMETYPE_JSON:
error "Unsupported contentType value", contentType = contentType
return err("Unsupported contentType")

let encoded = ?encodeIntoJsonBytes(value)
return ok(encoded)
return encodeBytesOf(value, contentType)

proc encodeBytes*(value: FilterUnsubscribeAllRequest,
contentType: string): RestResult[seq[byte]] =
if MediaType.init(contentType) != MIMETYPE_JSON:
error "Unsupported contentType value", contentType = contentType
return err("Unsupported contentType")

let encoded = ?encodeIntoJsonBytes(value)
return ok(encoded)

proc decodeBytes*(t: typedesc[FilterSubscriptionResponse],
value: openarray[byte],
contentType: Opt[ContentTypeData]):

RestResult[FilterSubscriptionResponse] =

if MediaType.init($contentType) != MIMETYPE_JSON:
error "Unsupported contentType value", contentType = contentType
return err("Unsupported contentType")

let decoded = ?decodeFromJsonBytes(FilterSubscriptionResponse, value)
return ok(decoded)
return encodeBytesOf(value, contentType)

proc filterSubscriberPing*(requestId: string):
RestResponse[FilterSubscriptionResponse]
Expand All @@ -92,16 +60,6 @@ proc filterDeleteAllSubscriptions*(body: FilterUnsubscribeAllRequest):
RestResponse[FilterSubscriptionResponse]
{.rest, endpoint: "/filter/v2/subscriptions/all", meth: HttpMethod.MethodDelete.}

proc decodeBytes*(t: typedesc[FilterGetMessagesResponse],
data: openArray[byte],
contentType: Opt[ContentTypeData]): RestResult[FilterGetMessagesResponse] =
if MediaType.init($contentType) != MIMETYPE_JSON:
error "Unsupported response contentType value", contentType = contentType
return err("Unsupported response contentType")

let decoded = ?decodeFromJsonBytes(FilterGetMessagesResponse, data)
return ok(decoded)

proc filterGetMessagesV1*(contentTopic: string):
RestResponse[FilterGetMessagesResponse]
{.rest, endpoint: "/filter/v2/messages/{contentTopic}", meth: HttpMethod.MethodGet.}
28 changes: 6 additions & 22 deletions waku/waku_api/rest/filter/handlers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import
../../handlers,
../serdes,
../responses,
../rest_serdes,
./types

export types
Expand All @@ -44,23 +45,6 @@ const filterMessageCacheDefaultCapacity* = 30
type
MessageCache* = message_cache.MessageCache[ContentTopic]

func decodeRequestBody[T](contentBody: Option[ContentBody]) : Result[T, RestApiResponse] =
if contentBody.isNone():
return err(RestApiResponse.badRequest("Missing content body"))

let reqBodyContentType = MediaType.init($contentBody.get().contentType)
if reqBodyContentType != MIMETYPE_JSON:
return err(RestApiResponse.badRequest("Wrong Content-Type, expected application/json"))

let reqBodyData = contentBody.get().data

let requestResult = decodeFromJsonBytes(T, reqBodyData)
if requestResult.isErr():
return err(RestApiResponse.badRequest("Invalid content body, could not decode. " &
$requestResult.error))

return ok(requestResult.get())

proc getErrorCause(err: filter_protocol_type.FilterSubscribeError): string =
## Retrieve proper error cause of FilterSubscribeError - due stringify make some parts of text double

Expand Down Expand Up @@ -169,7 +153,7 @@ proc filterPostPutSubscriptionRequestHandler(

let peer = node.peerManager.selectPeer(WakuFilterSubscribeCodec).valueOr:
let handler = discHandler.valueOr:
return makeRestResponse(req.requestId, NoPeerNoDiscoError)
return makeRestResponse(req.requestId, NoPeerNoDiscoError)

let peerOp = (await handler()).valueOr:
return RestApiResponse.internalServerError($error)
Expand Down Expand Up @@ -233,7 +217,7 @@ proc installFilterDeleteSubscriptionsHandler(

let peer = node.peerManager.selectPeer(WakuFilterSubscribeCodec).valueOr:
let handler = discHandler.valueOr:
return makeRestResponse(req.requestId, NoPeerNoDiscoError)
return makeRestResponse(req.requestId, NoPeerNoDiscoError)

let peerOp = (await handler()).valueOr:
return RestApiResponse.internalServerError($error)
Expand Down Expand Up @@ -276,7 +260,7 @@ proc installFilterDeleteAllSubscriptionsHandler(

let peer = node.peerManager.selectPeer(WakuFilterSubscribeCodec).valueOr:
let handler = discHandler.valueOr:
return makeRestResponse(req.requestId, NoPeerNoDiscoError)
return makeRestResponse(req.requestId, NoPeerNoDiscoError)

let peerOp = (await handler()).valueOr:
return RestApiResponse.internalServerError($error)
Expand All @@ -285,7 +269,7 @@ proc installFilterDeleteAllSubscriptionsHandler(
return makeRestResponse(req.requestId, NoPeerNoneFoundError)

let unsubFut = node.filterUnsubscribeAll(peer)

if not await unsubFut.withTimeout(futTimeoutForSubscriptionProcessing):
error "Failed to unsubscribe from contentFilters due to timeout!"
return makeRestResponse(req.requestId,
Expand All @@ -310,7 +294,7 @@ proc installFilterPingSubscriberHandler(

let peer = node.peerManager.selectPeer(WakuFilterSubscribeCodec).valueOr:
let handler = discHandler.valueOr:
return makeRestResponse(requestId.get(), NoPeerNoDiscoError)
return makeRestResponse(requestId.get(), NoPeerNoDiscoError)

let peerOp = (await handler()).valueOr:
return RestApiResponse.internalServerError($error)
Expand Down
42 changes: 8 additions & 34 deletions waku/waku_api/rest/filter/legacy_client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import
../../../waku_core,
../serdes,
../responses,
../rest_serdes,
./types

export types
Expand All @@ -23,46 +24,19 @@ logScope:

proc encodeBytes*(value: FilterLegacySubscribeRequest,
contentType: string): RestResult[seq[byte]] =
if MediaType.init(contentType) != MIMETYPE_JSON:
error "Unsupported contentType value", contentType = contentType
return err("Unsupported contentType")

let encoded = ?encodeIntoJsonBytes(value)
return ok(encoded)

proc decodeBytes*(t: typedesc[string], value: openarray[byte],
contentType: Opt[ContentTypeData]): RestResult[string] =
if MediaType.init($contentType) != MIMETYPE_TEXT:
error "Unsupported contentType value", contentType = contentType
return err("Unsupported contentType")

var res: string
if len(value) > 0:
res = newString(len(value))
copyMem(addr res[0], unsafeAddr value[0], len(value))
return ok(res)
return encodeBytesOf(value, contentType)

# TODO: Check how we can use a constant to set the method endpoint (improve "rest" pragma under nim-presto)
proc filterPostSubscriptionsV1*(body: FilterLegacySubscribeRequest):
RestResponse[string]
proc filterPostSubscriptionsV1*(body: FilterLegacySubscribeRequest):
RestResponse[string]
{.rest, endpoint: "/filter/v1/subscriptions", meth: HttpMethod.MethodPost.}

# TODO: Check how we can use a constant to set the method endpoint (improve "rest" pragma under nim-presto)
proc filterDeleteSubscriptionsV1*(body: FilterLegacySubscribeRequest):
RestResponse[string]
proc filterDeleteSubscriptionsV1*(body: FilterLegacySubscribeRequest):
RestResponse[string]
{.rest, endpoint: "/filter/v1/subscriptions", meth: HttpMethod.MethodDelete.}

proc decodeBytes*(t: typedesc[FilterGetMessagesResponse],
data: openArray[byte],
contentType: Opt[ContentTypeData]): RestResult[FilterGetMessagesResponse] =
if MediaType.init($contentType) != MIMETYPE_JSON:
error "Unsupported response contentType value", contentType = contentType
return err("Unsupported response contentType")

let decoded = ?decodeFromJsonBytes(FilterGetMessagesResponse, data)
return ok(decoded)

# TODO: Check how we can use a constant to set the method endpoint (improve "rest" pragma under nim-presto)
proc filterGetMessagesV1*(contentTopic: string):
RestResponse[FilterGetMessagesResponse]
proc filterGetMessagesV1*(contentTopic: string):
RestResponse[FilterGetMessagesResponse]
{.rest, endpoint: "/filter/v1/messages/{contentTopic}", meth: HttpMethod.MethodGet.}
18 changes: 1 addition & 17 deletions waku/waku_api/rest/filter/legacy_handlers.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import
../../message_cache,
../serdes,
../responses,
../rest_serdes,
./types

export types
Expand All @@ -38,23 +39,6 @@ const filterMessageCacheDefaultCapacity* = 30
type
MessageCache* = message_cache.MessageCache[ContentTopic]

func decodeRequestBody[T](contentBody: Option[ContentBody]) : Result[T, RestApiResponse] =
if contentBody.isNone():
return err(RestApiResponse.badRequest("Missing content body"))

let reqBodyContentType = MediaType.init($contentBody.get().contentType)
if reqBodyContentType != MIMETYPE_JSON:
return err(RestApiResponse.badRequest("Wrong Content-Type, expected application/json"))

let reqBodyData = contentBody.get().data

let requestResult = decodeFromJsonBytes(T, reqBodyData)
if requestResult.isErr():
return err(RestApiResponse.badRequest("Invalid content body, could not decode. " &
$requestResult.error))

return ok(requestResult.get())

proc installFilterV1PostSubscriptionsV1Handler*(router: var RestRouter,
node: WakuNode,
cache: MessageCache) =
Expand Down
15 changes: 2 additions & 13 deletions waku/waku_api/rest/health/client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,10 @@ import
presto/[route, client]
import
../serdes,
../responses
../responses,
../rest_serdes

logScope:
topics = "waku node rest health_api"

proc decodeBytes*(t: typedesc[string], value: openArray[byte],
contentType: Opt[ContentTypeData]): RestResult[string] =
if MediaType.init($contentType) != MIMETYPE_TEXT:
error "Unsupported contentType value", contentType = contentType
return err("Unsupported contentType")

var res: string
if len(value) > 0:
res = newString(len(value))
copyMem(addr res[0], unsafeAddr value[0], len(value))
return ok(res)

proc healthCheck*(): RestResponse[string] {.rest, endpoint: "/health", meth: HttpMethod.MethodGet.}
Loading

0 comments on commit 6d81e38

Please sign in to comment.