110 lines
3.4 KiB
Nim
110 lines
3.4 KiB
Nim
import std/json, std/logging, std/options, std/strutils, std/sequtils
|
|
import jester, namespaced_logging
|
|
|
|
import ./apierror
|
|
|
|
const CONTENT_TYPE_JSON* = "application/json"
|
|
|
|
var logNs {.threadvar.}: LoggingNamespace
|
|
|
|
template log(): untyped =
|
|
if logNs.isNil: logNs = getLoggerForNamespace("buffoonery/apiutils", lvlDebug)
|
|
logNs
|
|
|
|
## Response Utilities
|
|
template halt*(
|
|
code: HttpCode,
|
|
headers: RawHeaders,
|
|
content: string) =
|
|
## Immediately replies with the specified request. This means any further
|
|
## code will not be executed after calling this template in the current
|
|
## route.
|
|
bind TCActionSend, newHttpHeaders
|
|
result[0] = CallbackAction.TCActionSend
|
|
result[1] = code
|
|
result[2] = if isSome(result[2]): some(result[2].get & headers)
|
|
else: some(headers)
|
|
result[3] = content
|
|
result.matched = true
|
|
break allRoutes
|
|
|
|
template sendJsonResp*(
|
|
body: JsonNode,
|
|
code: HttpCode = Http200,
|
|
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"])
|
|
else: ""
|
|
|
|
let corsHeaders =
|
|
if knownOrigins.contains(reqOrigin):
|
|
@{
|
|
"Access-Control-Allow-Origin": reqOrigin,
|
|
"Access-Control-Allow-Credentials": "true",
|
|
"Access-Control-Allow-Methods": $(reqMethod(request)),
|
|
"Access-Control-Allow-Headers": "Authorization,X-CSRF-TOKEN"
|
|
}
|
|
else:
|
|
log().debug "Unrecognized Origin '" & reqOrigin & "', excluding CORS headers."
|
|
@{:}
|
|
|
|
halt(
|
|
code,
|
|
cast[RawHeaders](headersToSend) & corsHeaders & @{
|
|
"Content-Type": CONTENT_TYPE_JSON,
|
|
"Cache-Control": "no-cache"
|
|
},
|
|
$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 sendErrorResp*(err: ref ApiError, knownOrigins: seq[string]): void =
|
|
log().debug err.respMsg & ( if err.msg.len > 0: ": " & err.msg else: "")
|
|
if not err.parent.isNil: log().debug " original exception: " & err.parent.msg
|
|
sendJsonResp(makeStatusBody(err.respMsg), err.respCode, knownOrigins, @{:})
|
|
|
|
## CORS support
|
|
template sendOptionsResp*(
|
|
allowedMethods: seq[HttpMethod],
|
|
knownOrigins: seq[string]) =
|
|
|
|
let reqOrigin =
|
|
if headers(request).hasKey("Origin"): $(headers(request)["Origin"])
|
|
else: ""
|
|
|
|
let corsHeaders =
|
|
if knownOrigins.contains(reqOrigin):
|
|
@{
|
|
"Access-Control-Allow-Origin": reqOrigin,
|
|
"Access-Control-Allow-Credentials": "true",
|
|
"Access-Control-Allow-Methods": allowedMethods.mapIt($it).join(", "),
|
|
"Access-Control-Allow-Headers": "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-CSRF-TOKEN"
|
|
}
|
|
else:
|
|
log().debug "Unrecognized Origin '" & reqOrigin & "', excluding CORS headers."
|
|
log().debug "Valid origins: " & knownOrigins.join(", ")
|
|
@{:}
|
|
|
|
halt(
|
|
Http200,
|
|
corsHeaders,
|
|
""
|
|
)
|