Add object to standardize the API response data format.

This commit is contained in:
2023-08-09 09:18:29 -05:00
parent 3b1e9b5a8d
commit 9a510389d3
4 changed files with 79 additions and 23 deletions

View File

@ -1,4 +1,4 @@
import std/json, std/logging, std/options, std/strutils, std/sequtils
import std/[json, jsonutils, logging, options, strutils, sequtils]
import jester, namespaced_logging
import ./apierror
@ -12,6 +12,35 @@ template log(): untyped =
logNs
## Response Utilities
## ------------------
type ApiResponse*[T] = object
details*: Option[string]
data*: Option[T]
nextOffset*: Option[int]
totalItems*: Option[int]
nextLink*: Option[string]
prevLink*: Option[string]
func initApiResponse*[T](
details = none[string](),
data: Option[T] = none[T](),
nextOffset = none[int](),
totalItems = none[int](),
nextLink = none[string](),
prevLink = none[string]()): ApiResponse[T] =
ApiResponse[T](details: details, data: data, nextOffset: nextOffset,
totalItems: totalItems, nextLink: nextLink, prevLink: prevLink)
func `%`*(r: ApiResponse): JsonNode =
result = newJObject()
if r.details.isSome: result["details"] = %r.details
if r.data.isSome: result["data"] = %r.data
if r.nextOffset.isSome: result["nextOffset"] = %r.nextOffset
if r.totalItems.isSome: result["totalItems"] = %r.totalItems
if r.nextLink.isSome: result["nextLink"] = %r.nextLink
if r.prevLink.isSome: result["prevLink"] = %r.prevLink
template halt*(
code: HttpCode,
headers: RawHeaders,
@ -31,8 +60,8 @@ template halt*(
template sendJsonResp*(
body: JsonNode,
code: HttpCode = Http200,
knownOrigins: seq[string],
headersToSend: RawHeaders) =
knownOrigins: seq[string] = @[],
headersToSend: RawHeaders = @{:}) =
## Immediately send a JSON response and stop processing the request.
let reqOrigin =
if headers(request).hasKey("Origin"): $(headers(request)["Origin"])
@ -59,26 +88,17 @@ template sendJsonResp*(
$body
)
proc makeDataBody*(
data: JsonNode,
nextOffset = none[int](),
totalItems = none[int](),
nextLink = none[string](),
prevLink = none[string]()): JsonNode =
result = %*{"details":"","data":data}
if nextOffset.isSome: result["nextOffset"] = %nextOffset.get
if totalItems.isSome: result["totalItems"] = %totalItems.get
if nextLink.isSome: result["next"] = %nextLink.get
if prevLink.isSome: result["prev"] = %prevLink.get
proc makeStatusBody*(details: string): JsonNode = %*{"details":details}
template sendResp*[T](
resp: ApiResponse[T],
code = Http200,
allowedOrigins = newSeq[string](),
headersToSend: RawHeaders = @{:}) =
sendJsonResp(%resp, code, allowedOrigins, headersToSend)
template sendErrorResp*(err: ref ApiError, knownOrigins: seq[string]): void =
log().error err.respMsg & ( if err.msg.len > 0: ": " & err.msg else: "")
if not err.parent.isNil: log().error " original exception: " & err.parent.msg
sendJsonResp(makeStatusBody(err.respMsg), err.respCode, knownOrigins, @{:})
sendJsonResp( %*{"details":err.respMsg}, err.respCode, knownOrigins)
## CORS support
template sendOptionsResp*(

View File

@ -3,14 +3,14 @@ import json, times, timeutils, uuids
const MONTH_FORMAT* = "YYYY-MM"
proc getOrFail*(n: JsonNode, key: string): JsonNode =
func getOrFail*(n: JsonNode, key: string): JsonNode =
## convenience method to get a key from a JObject or raise an exception
if not n.hasKey(key):
raise newException(ValueError, "missing key '" & key & "'")
return n[key]
proc parseUUID*(n: JsonNode, key: string): UUID =
func parseUUID*(n: JsonNode, key: string): UUID =
return parseUUID(n.getOrFail(key).getStr)
proc parseIso8601*(n: JsonNode, key: string): DateTime =
@ -19,5 +19,5 @@ proc parseIso8601*(n: JsonNode, key: string): DateTime =
proc parseMonth*(n: JsonNode, key: string): DateTime =
return parse(n.getOrFail(key).getStr, MONTH_FORMAT)
proc formatMonth*(dt: DateTime): string =
func formatMonth*(dt: DateTime): string =
return dt.format(MONTH_FORMAT)