-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
swagger route #51
Comments
I like the idea 🚀
"/posts" -> post(body {.help: "Contains the post data blah blah".} : Json[Post]):
# ...
"/posts" -> post(body: Json[Post]):
## Comment describing the route
##
## * body: This would get parsed and attached to the body Probably deserves it's own issue but I always had an idea in the back of my mind for typed return types. Since I don't want to break compatibility with anything I'd have to squeeze it into the current DSL like (this isn't final, just playing around) "/posts" -> post(body): string:
# .. create post
return newPost.id Having that should give us ability for return types
|
while i thinking of how to add supporting for return types i think come with such design let router = newRouter()
type MyEnum = enum
A
B
C
router.get("/items/:item_id") do (item_id: MyEnum): Json =
# GET /items?q=heh&short=true
router.get("/items") do (q: string, short: bool): Json =
return %{"hello": "world"}
router.post("/items") do (item: Json[Item]): Response =
Response(text="heh", headers={"blabla": "dsf"})
# maybe without specifying that it is json it should search up in different places
# depending on request Content-Type
# if application/json -> parse json
# if xxx-form -> parse form multipart data
# either try to search for query parameters
# and somehow add plugins for parsing other types
router.post("/items") do (item: Item): Response =
Response(text="heh", headers={"blabla": "dsf"})
router.post("/header_test") do (myHeader: Header) =
discard
router.post("/:path") do (path: Path): File =
File(path)
router.post("/stream") do (): Stream =
Stream
# and for addition this can support router merging
let subRoute = newRouter()
router.mount("/sub", subRoute) the macro used here
|
about pragmas for comments import std/macros
import std/sets
type Obj = object
name: string
help: string
fields: seq[(string, string)]
type Objects = seq[Obj]
proc parseTypeDeep(x: NimNode, visited: HashSet[string], objs: var Objects) =
if x.kind == nnkTypeDef:
var objFields = newSeq[(string, string)]()
var fields: NimNode
if x[2].kind == nnkRefTy:
fields = x[2][0][2]
else:
fields = x[2][2]
for field in fields:
if field[1].kind == nnkSym:
let fieldType = field[1].getImpl
if fieldType.kind == nnkTypeDef:
parseTypeDeep(fieldType, visited, objs)
else:
# TODO: generics
echo field[1].getTypeImpl.repr
objFields.add((field[0].repr, field[1].repr))
var help: string
var name: string
if x[0].kind == nnkPragmaExpr:
name = x[0][0].repr
for pragma in x[0][1]:
if pragma[0].repr == "help":
help = $pragma[1]
else:
name = x[0].repr
objs.add Obj(name: name, help: help, fields: objFields)
template help(s: string) {.pragma.}
macro parseType(x: typed) =
var info = newSeq[Obj]()
parseTypeDeep(x.getImpl, initHashSet[string](), info)
echo info
type
Author = object
Post {.help: "contains post data".} = object
id {.help: "Post Identificator".}: int
author {.help: "Post author".}: Author
content {.help: "contains post text".}: string
parseType(Post)
unfortunately nim ast doesnt contain comments for types definitions, but it may be parsed manually using lineInfo and reading source files proc countIndent(line: string): int =
for c in line:
if c != ' ':
break
result += 1
macro readObj(n: typed) =
let x = n.getImpl
echo x.lineInfoObj.repr
let li = x.lineInfoObj
let lines = readFile(li.filename).split("\n")
let startIdent = countIndent(lines[li.line-1])
var i = 0
while i < lines.len - li.line and countIndent(lines[li.line+i]) > startIdent:
i += 1
echo lines[li.line-1..li.line+i-1] maybe in future it will need integration with lib like this |
proc swagerizePath(path: string): string =
result = newStringOfCap(path.len+path.count(':')*2)
var searchSlash = false;
for c in path:
if c == '/' and searchSlash:
result &= '}'
searchSlash = false
if c == ':':
result &= '{'
searchSlash = true
else:
result &= c
if searchSlash:
result &= '}'
proc dumpInfo*(info: HandlerInfo): NimNode =
let path = swagerizePath(info.path)
let verbs = info.verbs
var preParams = newSeq[JsonNode]()
let pathParameters = block:
var params: seq[string]
let nodes = info.path.toNodes()
for node in nodes:
if node.kind in {Param, Greedy} and node.val != "":
params &= node.val
params
var prebody: JsonNode = %*{"content": {}}
for p in info.params:
# not support
# {p} -> get(p: Option[int])
# option path
# {p} -> get(p: Path[int])
# direct specifying that its path
if p.name in pathParameters:
preParams &= %*{
"name": p.name,
"in": "path",
"schema": {"type": p.kind.repr},
"description": "",
"required": true
}
elif p.kind.kind == nnkBracketExpr:
if $(p.kind[0]) in @["Query", "Header", "Cookie"]:
var inn: string
if $(p.kind[0]) == "Query":
# not supported content
# objects as form or json
inn = "query"
if $(p.kind[0]) == "Header":
inn = "header"
if $(p.kind[0]) == "Cookie":
inn = "cookie"
var t: string = p.kind[1].repr
var required: bool = true
if p.kind[1].kind == nnkBracketExpr and $(p.kind[1][0]) == "Option":
t = p.kind[1][1].repr
required = false
preParams &= %*{
"name": p.name,
"in": inn,
"schema": {"type": t},
"description": "",
"required": required
}
if $(p.kind[0]) in @["Json"]:
prebody["content"]["application/json"] = %*{
"schema": {
"$ref": "#/components/schemas/" & p.kind[1].repr
}
}
let params = $ %preParams
let body = $prebody
return quote do:
if not mikeRoutesInfo.contains(`path`):
mikeRoutesInfo[`path`] = JsonNode(kind: JObject)
for verb in `verbs`:
var spec = JsonNode(kind: JObject)
mikeRoutesInfo[`path`][($verb).toLowerAscii()] = spec
spec["tags"] = %*{}
spec["parameters"] = parseJson(`params`)
spec["requestBody"] = parseJson(`body`)
var
mikeRouter = Router[Route]()
errorHandlers: Table[cstring, AsyncHandler]
mikeRoutesInfo* = JsonNode(kind: JObject)
macro `->`*(path: static[string], info: untyped, body: untyped): untyped =
let info = getHandlerInfo(path, info, body)
let handlerProc = createAsyncHandler(body, info.path, info.params)
let infoJson = dumpInfo(info)
result = genAst(path = info.path, verbs = info.verbs, pos = info.pos, handlerProc, infoJson=infoJson):
addHandler(path, verbs, pos, handlerProc)
infoJson i managed to generate first version which generates openapi spec for my toy example |
i see there one library for jester i mean all it needs is to receive HandlerInfo at compile time and inject some ast back (to make data available in runtime) maybe make first some sort of plugin system var transformers {.compileTime.} = newSeq[proc(x:NimNode): NimNode]()
macro test(x: untyped): untyped =
echo transformers.len
result = transformers[0](x)
for t in transformers[1..^1]:
result = t(result)
static:
transformers.add(proc(x: NimNode): NimNode=
return x)
static:
transformers.add(proc(x: NimNode): NimNode=
return x)
test echo "a" |
Sorry for the delayed response work has been busy, this is some cool stuff though. Hopefully I have time this weekend to properly look at integrating your code. Shouldn't be too difficult to just store more information in a macrocache list/table, then a user can just place before the end of their routes something like "/swagger" -> get:
return renderSwagger() For comments yeah having pragmas is going to have to be the way. I recently was writing something and wanted comments so was going to make a PR to Nim to expose the comments that is stored in nodes but it doesn't work in IC so I didn't put effort into it (Not that IC is really available :P), maybe NIR will change that limitation. For annotating the parameters, I'd probably go down a route more like type
SwaggerInfo = object
kind: # Enum of path, query, header, cookie
required: bool
schema: # Schema object
# ...
proc swaggerRouteInfo(x: typedesc[SomeType]): SwaggerInfo That way custom types can change how it gets converted. Like the thing you mentioned about parsing depending on |
what about adding swagger routes?
i see it is possible with saving handlerInfo in dsl.nim
and few things to generate better doc:
i am not sure how to add return type in current dsl
The text was updated successfully, but these errors were encountered: