all repos — litestore @ 03a48496e6fb3fb0b1a1660a4018bba58255f6a3

A minimalist nosql document store.

Implemented query string support for GET and HEAD requests.
h3rald h3rald@h3rald.com
Fri, 30 Jan 2015 21:57:59 +0100
commit

03a48496e6fb3fb0b1a1660a4018bba58255f6a3

parent

9210fa51fc9e620d5e71408e3708f92b59dfeb28

3 files changed, 71 insertions(+), 29 deletions(-)

jump to
M server.nimserver.nim

@@ -1,4 +1,4 @@

-import asynchttpserver, asyncdispatch, times, strutils, pegs, strtabs +import asynchttpserver, asyncdispatch, times, strutils, pegs, strtabs, cgi import types, api proc getReqInfo(req: Request): string =

@@ -7,6 +7,46 @@

proc handleCtrlC() {.noconv.} = echo "\nExiting..." quit() + +proc validOrderBy(clause: string):bool = + return clause == "id ASC" or + clause == "id DESC" or + clause == "created ASC" or + clause == "created DESC" or + clause == "modified ASC" or + clause == "modified DESC" + +proc parseQueryOption(fragment: string, options: var QueryOptions) = + var pair = fragment.split('=') + if pair.len < 2: + return + try: + pair[1] = pair[1].decodeURL + except: + raise newException(EInvalidRequest, "Unable to decode query string fragment '$1'" % fragment) + case pair[0]: + of "search": + options.search = pair[1] + of "tags": + options.tags = pair[1] + of "limit": + try: + options.limit = pair[1].parseInt + except: + raise newException(EInvalidRequest, "LIMIT - $1" % getCurrentExceptionMsg()) + of "orderby": + if pair[1].validOrderBy(): + options.orderby = pair[1] + else: + raise newException(EInvalidRequest, "ORDERBY - Invalid clause '$1'" % pair[1]) + else: + return + +proc parseQueryOptions(querystring: string, options: var QueryOptions) = + var fragments = querystring.split('&') + for f in fragments: + f.parseQueryOption(options) + proc rDocs(path: string, matches: var seq[string]): bool = return path.find(peg"""^\/? {(.*)}""", matches) != -1

@@ -27,30 +67,39 @@ return resError(Http400, "Bad request: $1" % req.url.path)

proc headRoutes(req: Request, LS: LiteStore): Response = var matches = @[""] - var options = newQueryOptions() - options.select = "id, content_type, binary, searchable, created, modified" if req.url.path.rDocs(matches): - if matches[0] != "": - # Retrieve a single document - return LS.getRawDocument(matches[0], options) - else: - # Retrieve a multiple documents - return LS.getRawDocuments(options) + var options = newQueryOptions() + options.select = "id, content_type, binary, searchable, created, modified" + try: + parseQueryOptions(req.url.query, options); + if matches[0] != "": + # Retrieve a single document + return LS.getRawDocument(matches[0], options) + else: + # Retrieve a multiple documents + return LS.getRawDocuments(options) + except: + return resError(Http400, "Bad request: $1" % getCurrentExceptionMsg()) else: return resError(Http400, "Bad request: $1" % req.url.path) proc getRoutes(req: Request, LS: LiteStore): Response = var matches = @[""] if req.url.path.rDocs(matches): - if matches[0] != "": - # Retrieve a single document - if req.url.query.contains("raw=true") or req.headers["Content-Type"] == "application/json": - return LS.getRawDocument(matches[0]) + var options = newQueryOptions() + try: + parseQueryOptions(req.url.query, options); + if matches[0] != "": + # Retrieve a single document + if req.url.query.contains("raw=true") or req.headers["Content-Type"] == "application/json": + return LS.getRawDocument(matches[0], options) + else: + return LS.getDocument(matches[0], options) else: - return LS.getDocument(matches[0]) - else: - # Retrieve a multiple documents - return LS.getRawDocuments() + # Retrieve a multiple documents + return LS.getRawDocuments(options) + except: + return resError(Http400, "Bad request: $1" % getCurrentExceptionMsg()) else: return resError(Http400, "Bad request: $1" % req.url.path)
M types.nimtypes.nim

@@ -8,6 +8,7 @@ EDatastoreDoesNotExist* = object of Exception

EDatastoreUnavailable* = object of Exception EInvalidTag* = object of Exception EDirectoryNotFound* = object of Exception + EInvalidRequest* = object of Exception Datastore* = object db*: TDbConn path*: string
M utils.nimutils.nim

@@ -2,11 +2,11 @@ import json, db_sqlite, strutils, pegs

import types, queries, contenttypes let PEG_TAG* = peg""" -^\$? [a-zA-Z0-9_-?~:.@#^%!]+$ +^\$? [a-zA-Z0-9_\-?~:.@#^!]+$ """ let PEG_USER_TAG* = peg""" -^[a-zA-Z0-9_-?~:.@#^%!]+$ +^[a-zA-Z0-9_\-?~:.@#^!]+$ """ proc dbQuote*(s: string): string = result = "'"

@@ -23,14 +23,6 @@ if not tag.match(PEG_TAG):

raise newException(EInvalidTag, "Invalid tag '$1'" % tag) result = result & "AND id IN (" & select_tagged & tag & "\") " -proc validOrderBy*(clause):bool = - return clause == "id ASC" or - clause == "id DESC" or - clause == "created ASC" or - clause == "created DESC" or - clause == "modified ASC" or - clause == "modified DESC" - proc prepareSelectDocumentsQuery*(options: QueryOptions): string = result = "SELECT " & options.select & " " if options.search.len > 0:

@@ -44,7 +36,7 @@ if options.tags.len > 0:

result = result & options.tags.selectDocumentsByTags() if options.search.len > 0: result = result & "AND content MATCH \"" & options.search & "\"" - if options.orderby.validOrderBy(): + if options.orderby.len > 0: result = result & "ORDER BY " & options.orderby & " " if options.limit > 0: result = result & "LIMIT " & $options.limit & " "

@@ -55,7 +47,7 @@ result = result & "FROM tags "

if options.single: result = result & "WHERE tag_id = ?" result = result & "GROUP BY tag_id" - if options.orderby.validOrderBy(): + if options.orderby.len > 0: result = result & "ORDER BY " & options.orderby&" " if options.limit > 0: result = result & "LIMIT " & $options.limit & " "