188 lines
7.1 KiB
Nim
188 lines
7.1 KiB
Nim
#
|
|
# REST API framework implementation
|
|
# (c) Copyright 2021-Present
|
|
# Status Research & Development GmbH
|
|
#
|
|
# Licensed under either of
|
|
# Apache License, version 2.0, (LICENSE-APACHEv2)
|
|
# MIT license (LICENSE-MIT)
|
|
import chronos/apps/http/httptable
|
|
import stew/[results, byteutils], httputils
|
|
|
|
export results, httputils, httptable
|
|
|
|
{.push raises: [].}
|
|
|
|
type
|
|
ContentBody* = object
|
|
contentType*: ContentTypeData
|
|
data*: seq[byte]
|
|
|
|
ResponseContentBody* = object
|
|
contentType*: string
|
|
data*: seq[byte]
|
|
|
|
RestResult*[T] = Result[T, cstring]
|
|
|
|
RestApiError* = object
|
|
status*: HttpCode
|
|
message*: string
|
|
contentType*: string
|
|
|
|
RestApiResponseKind* {.pure.} = enum
|
|
Empty, Error, Redirect, Content, Status
|
|
|
|
RestApiResponse* = object
|
|
status*: HttpCode
|
|
headers*: HttpTable
|
|
case kind*: RestApiResponseKind
|
|
of RestApiResponseKind.Empty:
|
|
discard
|
|
of RestApiResponseKind.Status:
|
|
discard
|
|
of RestApiResponseKind.Content:
|
|
content*: ResponseContentBody
|
|
of RestApiResponseKind.Error:
|
|
errobj*: RestApiError
|
|
of RestApiResponseKind.Redirect:
|
|
location*: string
|
|
preserveQuery*: bool
|
|
|
|
ByteChar* = string | seq[byte]
|
|
|
|
RestDefect* = object of Defect
|
|
RestError* = object of CatchableError
|
|
RestBadRequestError* = object of RestError
|
|
RestEncodingError* = object of RestError
|
|
field*: cstring
|
|
RestDecodingError* = object of RestError
|
|
RestCommunicationError* = object of RestError
|
|
exc*: ref CatchableError
|
|
RestRedirectionError* = object of RestError
|
|
RestResponseError* = object of RestError
|
|
status*: int
|
|
contentType*: string
|
|
message*: string
|
|
RestKeyValueTuple* = tuple[key: string, value: string]
|
|
|
|
RestServerMetricsType* {.pure.} = enum
|
|
Response, ## Enable metric which measures time used to prepare response
|
|
Status ## Enable metric which counts response's HTTP status.
|
|
|
|
RestServerMetricsTypes* = set[RestServerMetricsType]
|
|
|
|
const
|
|
RestServerMetrics* = {
|
|
RestServerMetricsType.Response,
|
|
RestServerMetricsType.Status
|
|
}
|
|
|
|
proc init*(t: typedesc[ContentBody],
|
|
contentType: MediaType, data: openArray[byte]): ContentBody =
|
|
ContentBody(
|
|
contentType: ContentTypeData(status: HttpStatus.Success,
|
|
mediaType: contentType),
|
|
data: @data
|
|
)
|
|
|
|
proc error*(t: typedesc[RestApiResponse],
|
|
status: HttpCode = Http200, msg: string = "",
|
|
contentType: string = "text/html",
|
|
headers: HttpTable): RestApiResponse =
|
|
## Create REST API error response with status ``status`` and content specified
|
|
## by type ``contentType`` and data ``data``. You can also specify
|
|
## additional HTTP response headers using ``headers`` argument.
|
|
##
|
|
## Please note that ``contentType`` argument's value has priority over
|
|
## ``Content-Type`` header's value in ``headers`` table.
|
|
RestApiResponse(kind: RestApiResponseKind.Error, status: status,
|
|
headers: headers,
|
|
errobj: RestApiError(status: status, message: msg,
|
|
contentType: contentType))
|
|
|
|
proc error*(t: typedesc[RestApiResponse],
|
|
status: HttpCode = Http200, msg: string = "",
|
|
contentType: string = "text/html",
|
|
headers: openArray[RestKeyValueTuple]): RestApiResponse =
|
|
error(t, status, msg, contentType, HttpTable.init(headers))
|
|
|
|
proc error*(t: typedesc[RestApiResponse],
|
|
status: HttpCode = Http200, msg: string = "",
|
|
contentType: string = "text/html"): RestApiResponse =
|
|
error(t, status, msg, contentType, HttpTable.init())
|
|
|
|
proc response*(t: typedesc[RestApiResponse], data: ByteChar,
|
|
status: HttpCode = Http200, contentType = "text/plain",
|
|
headers: HttpTable): RestApiResponse =
|
|
## Create REST API data response with status ``status`` and content specified
|
|
## by type ``contentType`` and data ``data``. You can also specify
|
|
## additional HTTP response headers using ``headers`` argument.
|
|
##
|
|
## Please note that ``contentType`` argument's value has priority over
|
|
## ``Content-Type`` header's value in ``headers`` table.
|
|
let content =
|
|
when data is seq[byte]:
|
|
ResponseContentBody(contentType: contentType, data: data)
|
|
else:
|
|
block:
|
|
var default: seq[byte]
|
|
ResponseContentBody(contentType: contentType,
|
|
data: if len(data) > 0: toBytes(data) else: default)
|
|
RestApiResponse(kind: RestApiResponseKind.Content, status: status,
|
|
headers: headers, content: content)
|
|
|
|
proc response*(t: typedesc[RestApiResponse], data: ByteChar,
|
|
status: HttpCode = Http200, contentType = "text/plain",
|
|
headers: openArray[RestKeyValueTuple]): RestApiResponse =
|
|
response(t, data, status, contentType, HttpTable.init(headers))
|
|
|
|
proc response*(t: typedesc[RestApiResponse], data: ByteChar,
|
|
status: HttpCode = Http200,
|
|
contentType = "text/plain"): RestApiResponse =
|
|
response(t, data, status, contentType, HttpTable.init())
|
|
|
|
proc response*(t: typedesc[RestApiResponse], status: HttpCode,
|
|
headers: HttpTable): RestApiResponse =
|
|
## Create REST API data response with status ``status`` and without any
|
|
## content. You can also specify additional HTTP response headers using
|
|
## ``headers`` argument.
|
|
RestApiResponse(kind: RestApiResponseKind.Status, status: status,
|
|
headers: headers)
|
|
|
|
proc response*(t: typedesc[RestApiResponse],
|
|
status: HttpCode = Http200): RestApiResponse =
|
|
response(t, status, HttpTable.init())
|
|
|
|
proc response*(t: typedesc[RestApiResponse], status: HttpCode,
|
|
headers: openArray[RestKeyValueTuple]): RestApiResponse =
|
|
response(t, status, HttpTable.init(headers))
|
|
|
|
proc redirect*(t: typedesc[RestApiResponse], status: HttpCode = Http307,
|
|
location: string, preserveQuery = false,
|
|
headers: HttpTable): RestApiResponse =
|
|
## Create REST API redirect response with status ``status`` and new location
|
|
## ``location``.
|
|
##
|
|
## You can preserve HTTP query string `uri.query` part using ``preserveQuery``
|
|
## argument. When ``preserveQuery`` is true new query string will be formed as
|
|
## concatenation of original HTTP request query string and ``location`` query
|
|
## string.
|
|
##
|
|
## You can also specify additional HTTP response headers using ``headers``
|
|
## argument.
|
|
##
|
|
## Please note that ``location`` argument's value has priority over
|
|
## ``Location`` header's value in ``headers`` table.
|
|
RestApiResponse(kind: RestApiResponseKind.Redirect, status: status,
|
|
headers: headers, location: location,
|
|
preserveQuery: preserveQuery)
|
|
|
|
proc redirect*(t: typedesc[RestApiResponse], status: HttpCode = Http307,
|
|
location: string, preserveQuery = false,
|
|
headers: openArray[RestKeyValueTuple]): RestApiResponse =
|
|
redirect(t, status, location, preserveQuery, HttpTable.init(headers))
|
|
|
|
proc redirect*(t: typedesc[RestApiResponse], status: HttpCode = Http307,
|
|
location: string, preserveQuery = false): RestApiResponse =
|
|
redirect(t, status, location, preserveQuery, HttpTable.init())
|