Hi everyone!
In my project I have a type
QueryParam* = tuple[key: string, value: string]
and functions that receives openArray of such objects as a parameter like this:
query = {"tags": "some tag", "limit": "10"}
Is there some way to make value part of my type not string but everything that has string representation, e.g has $ method, so i can pass parameters like this query = {"tags": "some tag", "limit": 10} and convert everything that I need to convert to string in my function?
I know that you can achieve something similar with varargs, for example echo pocedure:
proc echo*(x: varargs[typed, `$`])
but I can't figure out how to handle it in my case.
Thank you in advance!
The following does compile though I wouldn't recommend actually using it, it looks like a bunch of red-flags. Maybe explicitly converting every value to a string is better, or if you really want to making a macro also works.
type
QueryParam* = tuple[key: string, value: string]
proc bar*(x: (string, auto)): QueryParam =
(x[0], $x[1])
proc foo*(x: varargs[QueryParam, bar]) =
for (key, value) in x:
assert value is string
# foo({"a": "1", "b": 2}) # This doesn't work
foo(("a", "1"), ("b", 2))
To break this down into multiple steps varargs[T, conversionProc] allows you to take in comma separated values or arrays, but since arrays have to be homogeneous you cannot do query = {} or query = [].
proc toStrTup[T, Y](a: (T, Y)): (string, string) = ($a[0], $a[1])
proc doThing(args: varargs[(string, string), toStrTup]) = echo args
doThing (10, 20), ("hmm", 40), ("huh", (10, 20, 30))
If that suffices it's done and we can all go home.
If that does not suffice we need to bring in macros to solve this problem. Something along the lines of
import std/[macros, strformat]
macro toQuery*(arr: untyped, querySize: static range[2..int.high]): untyped =
result = nnkBracket.newNimNode()
case arr.kind
of nnkBracket:
for ele in arr:
if ele.kind in {nnkTupleConstr, nnkPar}:
if ele.len != querySize:
error(fmt"Expected a tuple airty of '{querySize}', but got a size of '{ele.len}'", ele)
result.add nnkTupleConstr.newNimNode()
for field in ele:
result[^1].add newCall("$", field)
of nnkTableConstr:
if querySize != 2:
error(fmt"Expected a tuple of '{querySize}' airty, but got a table.", arr)
for tupleConstr in arr:
result.add nnkTupleConstr.newNimNode
result[^1].add newCall("$", tupleConstr[0])
result[^1].add newCall("$", tupleConstr[1])
else:
error(fmt"Expected '{{a: b}}' or '[(a, b)]', but got '{arr.repr}'.", arr)
echo result.repr
template toQuery*(arr: untyped): untyped = toQuery(arr, 2)
proc doThing(args: varargs[(string, string)]) = echo args
let a = @[10, 20, 30, 40]
doThing toQuery {10: 20, "hmm": "huh", (10, 20, 30): a}
doThing toQuery [(10, 20), ("hmm", "huh"), ((10, 20, 30), a)]
assert toQuery([(10, 20), ("hmm", "huh"), ((10, 20, 30), a)]) == toQuery({10: 20, "hmm": "huh", (10, 20, 30): a})
Notice that due to how semantic analysis works you cannot do {...}.toQuery or [...].toQuery.