From a308e839594e844ef3c49752cc4670d26f4e6d03 Mon Sep 17 00:00:00 2001 From: NagyZoltanPeter <113987313+NagyZoltanPeter@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:10:02 +0100 Subject: [PATCH] cherry pick from chore-sync-mode-request-broker to get latest sync mode RequestBroker --- tests/common/test_request_broker.nim | 310 ++++++++++++++++++- waku/common/broker/request_broker.nim | 427 ++++++++++++++++++++------ 2 files changed, 633 insertions(+), 104 deletions(-) diff --git a/tests/common/test_request_broker.nim b/tests/common/test_request_broker.nim index 2ffd9cbf8..c7a8d8453 100644 --- a/tests/common/test_request_broker.nim +++ b/tests/common/test_request_broker.nim @@ -6,6 +6,10 @@ import std/strutils import waku/common/broker/request_broker +## --------------------------------------------------------------------------- +## Async-mode brokers + tests +## --------------------------------------------------------------------------- + RequestBroker: type SimpleResponse = object value*: string @@ -31,11 +35,14 @@ RequestBroker: suffix: string ): Future[Result[DualResponse, string]] {.async.} -RequestBroker: +RequestBroker(async): type ImplicitResponse = ref object note*: string -suite "RequestBroker macro": +static: + doAssert typeof(SimpleResponse.request()) is Future[Result[SimpleResponse, string]] + +suite "RequestBroker macro (async mode)": test "serves zero-argument providers": check SimpleResponse .setProvider( @@ -78,7 +85,6 @@ suite "RequestBroker macro": .setProvider( proc(key: string, subKey: int): Future[Result[KeyedResponse, string]] {.async.} = raise newException(ValueError, "simulated failure") - ok(KeyedResponse(key: key, payload: "")) ) .isOk() @@ -196,3 +202,301 @@ suite "RequestBroker macro": check nowSuccWithOverride.value.count == 1 DualResponse.clearProvider() + +## --------------------------------------------------------------------------- +## Sync-mode brokers + tests +## --------------------------------------------------------------------------- + +RequestBroker(sync): + type SimpleResponseSync = object + value*: string + + proc signatureFetch*(): Result[SimpleResponseSync, string] + +RequestBroker(sync): + type KeyedResponseSync = object + key*: string + payload*: string + + proc signatureFetchWithKey*( + key: string, subKey: int + ): Result[KeyedResponseSync, string] + +RequestBroker(sync): + type DualResponseSync = object + note*: string + count*: int + + proc signatureNoInput*(): Result[DualResponseSync, string] + proc signatureWithInput*(suffix: string): Result[DualResponseSync, string] + +RequestBroker(sync): + type ImplicitResponseSync = ref object + note*: string + +static: + doAssert typeof(SimpleResponseSync.request()) is Result[SimpleResponseSync, string] + doAssert not ( + typeof(SimpleResponseSync.request()) is Future[Result[SimpleResponseSync, string]] + ) + doAssert typeof(KeyedResponseSync.request("topic", 1)) is + Result[KeyedResponseSync, string] + +suite "RequestBroker macro (sync mode)": + test "serves zero-argument providers (sync)": + check SimpleResponseSync + .setProvider( + proc(): Result[SimpleResponseSync, string] = + ok(SimpleResponseSync(value: "hi")) + ) + .isOk() + + let res = SimpleResponseSync.request() + check res.isOk() + check res.value.value == "hi" + + SimpleResponseSync.clearProvider() + + test "zero-argument request errors when unset (sync)": + let res = SimpleResponseSync.request() + check res.isErr + check res.error.contains("no zero-arg provider") + + test "serves input-based providers (sync)": + var seen: seq[string] = @[] + check KeyedResponseSync + .setProvider( + proc(key: string, subKey: int): Result[KeyedResponseSync, string] = + seen.add(key) + ok(KeyedResponseSync(key: key, payload: key & "-payload+" & $subKey)) + ) + .isOk() + + let res = KeyedResponseSync.request("topic", 1) + check res.isOk() + check res.value.key == "topic" + check res.value.payload == "topic-payload+1" + check seen == @["topic"] + + KeyedResponseSync.clearProvider() + + test "catches provider exception (sync)": + check KeyedResponseSync + .setProvider( + proc(key: string, subKey: int): Result[KeyedResponseSync, string] = + raise newException(ValueError, "simulated failure") + ) + .isOk() + + let res = KeyedResponseSync.request("neglected", 11) + check res.isErr() + check res.error.contains("simulated failure") + + KeyedResponseSync.clearProvider() + + test "input request errors when unset (sync)": + let res = KeyedResponseSync.request("foo", 2) + check res.isErr + check res.error.contains("input signature") + + test "supports both provider types simultaneously (sync)": + check DualResponseSync + .setProvider( + proc(): Result[DualResponseSync, string] = + ok(DualResponseSync(note: "base", count: 1)) + ) + .isOk() + + check DualResponseSync + .setProvider( + proc(suffix: string): Result[DualResponseSync, string] = + ok(DualResponseSync(note: "base" & suffix, count: suffix.len)) + ) + .isOk() + + let noInput = DualResponseSync.request() + check noInput.isOk + check noInput.value.note == "base" + + let withInput = DualResponseSync.request("-extra") + check withInput.isOk + check withInput.value.note == "base-extra" + check withInput.value.count == 6 + + DualResponseSync.clearProvider() + + test "clearProvider resets both entries (sync)": + check DualResponseSync + .setProvider( + proc(): Result[DualResponseSync, string] = + ok(DualResponseSync(note: "temp", count: 0)) + ) + .isOk() + DualResponseSync.clearProvider() + + let res = DualResponseSync.request() + check res.isErr + + test "implicit zero-argument provider works by default (sync)": + check ImplicitResponseSync + .setProvider( + proc(): Result[ImplicitResponseSync, string] = + ok(ImplicitResponseSync(note: "auto")) + ) + .isOk() + + let res = ImplicitResponseSync.request() + check res.isOk + + ImplicitResponseSync.clearProvider() + check res.value.note == "auto" + + test "implicit zero-argument request errors when unset (sync)": + let res = ImplicitResponseSync.request() + check res.isErr + check res.error.contains("no zero-arg provider") + + test "implicit zero-argument provider raises error (sync)": + check ImplicitResponseSync + .setProvider( + proc(): Result[ImplicitResponseSync, string] = + raise newException(ValueError, "simulated failure") + ) + .isOk() + + let res = ImplicitResponseSync.request() + check res.isErr + check res.error.contains("simulated failure") + + ImplicitResponseSync.clearProvider() + +## --------------------------------------------------------------------------- +## POD / external type brokers + tests (distinct/alias behavior) +## --------------------------------------------------------------------------- + +type ExternalDefinedTypeAsync = object + label*: string + +type ExternalDefinedTypeSync = object + label*: string + +type ExternalDefinedTypeShared = object + label*: string + +RequestBroker: + type PodResponse = int + + proc signatureFetch*(): Future[Result[PodResponse, string]] {.async.} + +RequestBroker: + type ExternalAliasedResponse = ExternalDefinedTypeAsync + + proc signatureFetch*(): Future[Result[ExternalAliasedResponse, string]] {.async.} + +RequestBroker(sync): + type ExternalAliasedResponseSync = ExternalDefinedTypeSync + + proc signatureFetch*(): Result[ExternalAliasedResponseSync, string] + +RequestBroker(sync): + type DistinctStringResponseA = distinct string + +RequestBroker(sync): + type DistinctStringResponseB = distinct string + +RequestBroker(sync): + type ExternalDistinctResponseA = distinct ExternalDefinedTypeShared + +RequestBroker(sync): + type ExternalDistinctResponseB = distinct ExternalDefinedTypeShared + +suite "RequestBroker macro (POD/external types)": + test "supports non-object response types (async)": + check PodResponse + .setProvider( + proc(): Future[Result[PodResponse, string]] {.async.} = + ok(PodResponse(123)) + ) + .isOk() + + let res = waitFor PodResponse.request() + check res.isOk() + check int(res.value) == 123 + + PodResponse.clearProvider() + + test "supports aliased external types (async)": + check ExternalAliasedResponse + .setProvider( + proc(): Future[Result[ExternalAliasedResponse, string]] {.async.} = + ok(ExternalAliasedResponse(ExternalDefinedTypeAsync(label: "ext"))) + ) + .isOk() + + let res = waitFor ExternalAliasedResponse.request() + check res.isOk() + check ExternalDefinedTypeAsync(res.value).label == "ext" + + ExternalAliasedResponse.clearProvider() + + test "supports aliased external types (sync)": + check ExternalAliasedResponseSync + .setProvider( + proc(): Result[ExternalAliasedResponseSync, string] = + ok(ExternalAliasedResponseSync(ExternalDefinedTypeSync(label: "ext"))) + ) + .isOk() + + let res = ExternalAliasedResponseSync.request() + check res.isOk() + check ExternalDefinedTypeSync(res.value).label == "ext" + + ExternalAliasedResponseSync.clearProvider() + + test "distinct response types avoid overload ambiguity (sync)": + check DistinctStringResponseA + .setProvider( + proc(): Result[DistinctStringResponseA, string] = + ok(DistinctStringResponseA("a")) + ) + .isOk() + + check DistinctStringResponseB + .setProvider( + proc(): Result[DistinctStringResponseB, string] = + ok(DistinctStringResponseB("b")) + ) + .isOk() + + check ExternalDistinctResponseA + .setProvider( + proc(): Result[ExternalDistinctResponseA, string] = + ok(ExternalDistinctResponseA(ExternalDefinedTypeShared(label: "ea"))) + ) + .isOk() + + check ExternalDistinctResponseB + .setProvider( + proc(): Result[ExternalDistinctResponseB, string] = + ok(ExternalDistinctResponseB(ExternalDefinedTypeShared(label: "eb"))) + ) + .isOk() + + let resA = DistinctStringResponseA.request() + let resB = DistinctStringResponseB.request() + check resA.isOk + check resB.isOk + check string(resA.value) == "a" + check string(resB.value) == "b" + + let resEA = ExternalDistinctResponseA.request() + let resEB = ExternalDistinctResponseB.request() + check resEA.isOk + check resEB.isOk + check ExternalDefinedTypeShared(resEA.value).label == "ea" + check ExternalDefinedTypeShared(resEB.value).label == "eb" + + DistinctStringResponseA.clearProvider() + DistinctStringResponseB.clearProvider() + ExternalDistinctResponseA.clearProvider() + ExternalDistinctResponseB.clearProvider() diff --git a/waku/common/broker/request_broker.nim b/waku/common/broker/request_broker.nim index a8a6651d7..b4000e1f3 100644 --- a/waku/common/broker/request_broker.nim +++ b/waku/common/broker/request_broker.nim @@ -24,6 +24,56 @@ ## proc signature*(arg1: ArgType, arg2: AnotherArgType): Future[Result[TypeName, string]] ## ## ``` +## +## Sync mode (no `async` / `Future`) can be generated with: +## +## ```nim +## RequestBroker(sync): +## type TypeName = object +## field1*: FieldType +## +## proc signature*(): Result[TypeName, string] +## proc signature*(arg1: ArgType): Result[TypeName, string] +## ``` +## +## Note: When the request type is declared as a POD / alias / externally-defined +## type (i.e. not an inline `object` / `ref object` definition), RequestBroker +## will wrap it in `distinct` automatically unless you already used `distinct`. +## This avoids overload ambiguity when multiple brokers share the same +## underlying base type (Nim overload resolution does not consider return type). +## +## This means that for non-object request types you typically: +## - construct values with an explicit cast/constructor, e.g. `MyType("x")` +## - unwrap with a cast when needed, e.g. `string(myVal)` or `BaseType(myVal)` +## +## Example (POD response type): +## ```nim +## RequestBroker(sync): +## type MyCount = int # exported as: `distinct int` +## +## MyCount.setProvider(proc(): Result[MyCount, string] = ok(MyCount(42))) +## let res = MyCount.request() +## if res.isOk(): +## let raw = int(res.get()) +## ``` +## +## Example (externally-defined type): +## ```nim +## type External = object +## label*: string +## +## RequestBroker: +## type MyExternal = External # exported as: `distinct External` +## +## MyExternal.setProvider( +## proc(): Future[Result[MyExternal, string]] {.async.} = +## ok(MyExternal(External(label: "hi"))) +## ) +## let res = await MyExternal.request() +## if res.isOk(): +## let base = External(res.get()) +## echo base.label +## ``` ## The 'TypeName' object defines the requestable data (but also can be seen as request for action with return value). ## The 'signature' proc defines the provider(s) signature, that is enforced at compile time. ## One signature can be with no arguments, another with any number of arguments - where the input arguments are @@ -31,12 +81,12 @@ ## ## After this, you can register a provider anywhere in your code with ## `TypeName.setProvider(...)`, which returns error if already having a provider. -## Providers are async procs or lambdas that take no arguments and return a Future[Result[TypeName, string]]. +## Providers are async procs/lambdas in default mode and sync procs in sync mode. ## Only one provider can be registered at a time per signature type (zero arg and/or multi arg). ## ## Requests can be made from anywhere with no direct dependency on the provider by ## calling `TypeName.request()` - with arguments respecting the signature(s). -## This will asynchronously call the registered provider and return a Future[Result[TypeName, string]]. +## In async mode, this returns a Future[Result[TypeName, string]]. In sync mode, it returns Result[TypeName, string]. ## ## Whenever you no want to process requests (or your object instance that provides the request goes out of scope), ## you can remove it from the broker with `TypeName.clearProvider()`. @@ -49,10 +99,10 @@ ## text*: string ## ## ## Define the request and provider signature, that is enforced at compile time. -## proc signature*(): Future[Result[Greeting, string]] +## proc signature*(): Future[Result[Greeting, string]] {.async.} ## ## ## Also possible to define signature with arbitrary input arguments. -## proc signature*(lang: string): Future[Result[Greeting, string]] +## proc signature*(lang: string): Future[Result[Greeting, string]] {.async.} ## ## ... ## Greeting.setProvider( @@ -60,6 +110,23 @@ ## ok(Greeting(text: "hello")) ## ) ## let res = await Greeting.request() +## +## +## ... +## # using POD type as response for a synchronous request. +## RequestBroker(sync): +## type NeedThatInfo = string +## +##... +## NeedThatInfo.setProvider( +## proc(): Result[NeedThatInfo, string] = +## ok("this is the info you wanted") +## ) +## let res = NeedThatInfo.request().valueOr: +## echo "not ok due to: " & error +## NeedThatInfo(":-(") +## +## echo string(res) ## ``` ## If no `signature` proc is declared, a zero-argument form is generated ## automatically, so the caller only needs to provide the type definition. @@ -77,7 +144,11 @@ proc errorFuture[T](message: string): Future[Result[T, string]] {.inline.} = fut.complete(err(Result[T, string], message)) fut -proc isReturnTypeValid(returnType, typeIdent: NimNode): bool = +type RequestBrokerMode = enum + rbAsync + rbSync + +proc isAsyncReturnTypeValid(returnType, typeIdent: NimNode): bool = ## Accept Future[Result[TypeIdent, string]] as the contract. if returnType.kind != nnkBracketExpr or returnType.len != 2: return false @@ -92,6 +163,23 @@ proc isReturnTypeValid(returnType, typeIdent: NimNode): bool = return false inner[2].kind == nnkIdent and inner[2].eqIdent("string") +proc isSyncReturnTypeValid(returnType, typeIdent: NimNode): bool = + ## Accept Result[TypeIdent, string] as the contract. + if returnType.kind != nnkBracketExpr or returnType.len != 3: + return false + if returnType[0].kind != nnkIdent or not returnType[0].eqIdent("Result"): + return false + if returnType[1].kind != nnkIdent or not returnType[1].eqIdent($typeIdent): + return false + returnType[2].kind == nnkIdent and returnType[2].eqIdent("string") + +proc isReturnTypeValid(returnType, typeIdent: NimNode, mode: RequestBrokerMode): bool = + case mode + of rbAsync: + isAsyncReturnTypeValid(returnType, typeIdent) + of rbSync: + isSyncReturnTypeValid(returnType, typeIdent) + proc cloneParams(params: seq[NimNode]): seq[NimNode] = ## Deep copy parameter definitions so they can be inserted in multiple places. result = @[] @@ -109,73 +197,122 @@ proc collectParamNames(params: seq[NimNode]): seq[NimNode] = continue result.add(ident($nameNode)) -proc makeProcType(returnType: NimNode, params: seq[NimNode]): NimNode = +proc makeProcType( + returnType: NimNode, params: seq[NimNode], mode: RequestBrokerMode +): NimNode = var formal = newTree(nnkFormalParams) formal.add(returnType) for param in params: formal.add(param) - let pragmas = newTree(nnkPragma, ident("async")) - newTree(nnkProcTy, formal, pragmas) + case mode + of rbAsync: + let pragmas = newTree(nnkPragma, ident("async")) + newTree(nnkProcTy, formal, pragmas) + of rbSync: + let raisesPragma = newTree( + nnkExprColonExpr, ident("raises"), newTree(nnkBracket, ident("CatchableError")) + ) + let pragmas = newTree(nnkPragma, raisesPragma) + newTree(nnkProcTy, formal, pragmas) -macro RequestBroker*(body: untyped): untyped = +proc parseMode(modeNode: NimNode): RequestBrokerMode = + ## Parses the mode selector for the 2-argument macro overload. + ## Supported spellings: `sync` / `async` (case-insensitive). + let raw = ($modeNode).strip().toLowerAscii() + case raw + of "sync", "rbsync": + rbSync + of "async", "rbasync": + rbAsync + else: + error("RequestBroker mode must be `sync` or `async` (default is async)", modeNode) + +proc ensureDistinctType(rhs: NimNode): NimNode = + ## For PODs / aliases / externally-defined types, wrap in `distinct` unless + ## it's already distinct. + if rhs.kind == nnkDistinctTy: + return copyNimTree(rhs) + newTree(nnkDistinctTy, copyNimTree(rhs)) + +proc generateRequestBroker(body: NimNode, mode: RequestBrokerMode): NimNode = when defined(requestBrokerDebug): echo body.treeRepr + echo "RequestBroker mode: ", $mode var typeIdent: NimNode = nil var objectDef: NimNode = nil - var isRefObject = false for stmt in body: if stmt.kind == nnkTypeSection: for def in stmt: if def.kind != nnkTypeDef: continue + if not typeIdent.isNil(): + error("Only one type may be declared inside RequestBroker", def) + + typeIdent = baseTypeIdent(def[0]) let rhs = def[2] - var objectType: NimNode + + ## Support inline object types (fields are auto-exported) + ## AND non-object types / aliases (e.g. `string`, `int`, `OtherType`). case rhs.kind of nnkObjectTy: - objectType = rhs + let recList = rhs[2] + if recList.kind != nnkRecList: + error("RequestBroker object must declare a standard field list", rhs) + var exportedRecList = newTree(nnkRecList) + for field in recList: + case field.kind + of nnkIdentDefs: + ensureFieldDef(field) + var cloned = copyNimTree(field) + for i in 0 ..< cloned.len - 2: + cloned[i] = exportIdentNode(cloned[i]) + exportedRecList.add(cloned) + of nnkEmpty: + discard + else: + error( + "RequestBroker object definition only supports simple field declarations", + field, + ) + objectDef = newTree( + nnkObjectTy, copyNimTree(rhs[0]), copyNimTree(rhs[1]), exportedRecList + ) of nnkRefTy: - isRefObject = true - if rhs.len != 1 or rhs[0].kind != nnkObjectTy: - error( - "RequestBroker ref object must wrap a concrete object definition", rhs + if rhs.len != 1: + error("RequestBroker ref type must have a single base", rhs) + if rhs[0].kind == nnkObjectTy: + let obj = rhs[0] + let recList = obj[2] + if recList.kind != nnkRecList: + error("RequestBroker object must declare a standard field list", obj) + var exportedRecList = newTree(nnkRecList) + for field in recList: + case field.kind + of nnkIdentDefs: + ensureFieldDef(field) + var cloned = copyNimTree(field) + for i in 0 ..< cloned.len - 2: + cloned[i] = exportIdentNode(cloned[i]) + exportedRecList.add(cloned) + of nnkEmpty: + discard + else: + error( + "RequestBroker object definition only supports simple field declarations", + field, + ) + let exportedObjectType = newTree( + nnkObjectTy, copyNimTree(obj[0]), copyNimTree(obj[1]), exportedRecList ) - objectType = rhs[0] - else: - continue - if not typeIdent.isNil(): - error("Only one object type may be declared inside RequestBroker", def) - typeIdent = baseTypeIdent(def[0]) - let recList = objectType[2] - if recList.kind != nnkRecList: - error("RequestBroker object must declare a standard field list", objectType) - var exportedRecList = newTree(nnkRecList) - for field in recList: - case field.kind - of nnkIdentDefs: - ensureFieldDef(field) - var cloned = copyNimTree(field) - for i in 0 ..< cloned.len - 2: - cloned[i] = exportIdentNode(cloned[i]) - exportedRecList.add(cloned) - of nnkEmpty: - discard + objectDef = newTree(nnkRefTy, exportedObjectType) else: - error( - "RequestBroker object definition only supports simple field declarations", - field, - ) - let exportedObjectType = newTree( - nnkObjectTy, - copyNimTree(objectType[0]), - copyNimTree(objectType[1]), - exportedRecList, - ) - if isRefObject: - objectDef = newTree(nnkRefTy, exportedObjectType) + ## `ref SomeType` (SomeType can be defined elsewhere) + objectDef = ensureDistinctType(rhs) else: - objectDef = exportedObjectType + ## Non-object type / alias (e.g. `string`, `int`, `SomeExternalType`). + objectDef = ensureDistinctType(rhs) if typeIdent.isNil(): - error("RequestBroker body must declare exactly one object type", body) + error("RequestBroker body must declare exactly one type", body) when defined(requestBrokerDebug): echo "RequestBroker generating type: ", $typeIdent @@ -183,7 +320,6 @@ macro RequestBroker*(body: untyped): untyped = let exportedTypeIdent = postfix(copyNimTree(typeIdent), "*") let typeDisplayName = sanitizeIdentName(typeIdent) let typeNameLit = newLit(typeDisplayName) - let isRefObjectLit = newLit(isRefObject) var zeroArgSig: NimNode = nil var zeroArgProviderName: NimNode = nil var zeroArgFieldName: NimNode = nil @@ -211,10 +347,14 @@ macro RequestBroker*(body: untyped): untyped = if params.len == 0: error("Signature must declare a return type", stmt) let returnType = params[0] - if not isReturnTypeValid(returnType, typeIdent): - error( - "Signature must return Future[Result[`" & $typeIdent & "`, string]]", stmt - ) + if not isReturnTypeValid(returnType, typeIdent, mode): + case mode + of rbAsync: + error( + "Signature must return Future[Result[`" & $typeIdent & "`, string]]", stmt + ) + of rbSync: + error("Signature must return Result[`" & $typeIdent & "`, string]", stmt) let paramCount = params.len - 1 if paramCount == 0: if zeroArgSig != nil: @@ -258,14 +398,20 @@ macro RequestBroker*(body: untyped): untyped = var typeSection = newTree(nnkTypeSection) typeSection.add(newTree(nnkTypeDef, exportedTypeIdent, newEmptyNode(), objectDef)) - let returnType = quote: - Future[Result[`typeIdent`, string]] + let returnType = + case mode + of rbAsync: + quote: + Future[Result[`typeIdent`, string]] + of rbSync: + quote: + Result[`typeIdent`, string] if not zeroArgSig.isNil(): - let procType = makeProcType(returnType, @[]) + let procType = makeProcType(returnType, @[], mode) typeSection.add(newTree(nnkTypeDef, zeroArgProviderName, newEmptyNode(), procType)) if not argSig.isNil(): - let procType = makeProcType(returnType, cloneParams(argParams)) + let procType = makeProcType(returnType, cloneParams(argParams), mode) typeSection.add(newTree(nnkTypeDef, argProviderName, newEmptyNode(), procType)) var brokerRecList = newTree(nnkRecList) @@ -316,33 +462,69 @@ macro RequestBroker*(body: untyped): untyped = quote do: `accessProcIdent`().`zeroArgFieldName` = nil ) - result.add( - quote do: - proc request*( - _: typedesc[`typeIdent`] - ): Future[Result[`typeIdent`, string]] {.async: (raises: []).} = - let provider = `accessProcIdent`().`zeroArgFieldName` - if provider.isNil(): - return err( - "RequestBroker(" & `typeNameLit` & "): no zero-arg provider registered" - ) - let catchedRes = catch: - await provider() + case mode + of rbAsync: + result.add( + quote do: + proc request*( + _: typedesc[`typeIdent`] + ): Future[Result[`typeIdent`, string]] {.async: (raises: []).} = + let provider = `accessProcIdent`().`zeroArgFieldName` + if provider.isNil(): + return err( + "RequestBroker(" & `typeNameLit` & "): no zero-arg provider registered" + ) + let catchedRes = catch: + await provider() - if catchedRes.isErr(): - return err("Request failed:" & catchedRes.error.msg) + if catchedRes.isErr(): + return err( + "RequestBroker(" & `typeNameLit` & "): provider threw exception: " & + catchedRes.error.msg + ) - let providerRes = catchedRes.get() - when `isRefObjectLit`: + let providerRes = catchedRes.get() if providerRes.isOk(): let resultValue = providerRes.get() - if resultValue.isNil(): - return err( - "RequestBroker(" & `typeNameLit` & "): provider returned nil result" - ) - return providerRes + when compiles(resultValue.isNil()): + if resultValue.isNil(): + return err( + "RequestBroker(" & `typeNameLit` & "): provider returned nil result" + ) + return providerRes - ) + ) + of rbSync: + result.add( + quote do: + proc request*( + _: typedesc[`typeIdent`] + ): Result[`typeIdent`, string] {.raises: [].} = + let provider = `accessProcIdent`().`zeroArgFieldName` + if provider.isNil(): + return err( + "RequestBroker(" & `typeNameLit` & "): no zero-arg provider registered" + ) + + var providerRes: Result[`typeIdent`, string] + try: + providerRes = provider() + except CatchableError as e: + return err( + "RequestBroker(" & `typeNameLit` & "): provider threw exception: " & + e.msg + ) + + if providerRes.isOk(): + let resultValue = providerRes.get() + when compiles(resultValue.isNil()): + if resultValue.isNil(): + return err( + "RequestBroker(" & `typeNameLit` & "): provider returned nil result" + ) + return providerRes + + ) if not argSig.isNil(): result.add( quote do: @@ -363,10 +545,7 @@ macro RequestBroker*(body: untyped): untyped = let argNameIdents = collectParamNames(requestParamDefs) let providerSym = genSym(nskLet, "provider") var formalParams = newTree(nnkFormalParams) - formalParams.add( - quote do: - Future[Result[`typeIdent`, string]] - ) + formalParams.add(copyNimTree(returnType)) formalParams.add( newTree( nnkIdentDefs, @@ -378,8 +557,14 @@ macro RequestBroker*(body: untyped): untyped = for paramDef in requestParamDefs: formalParams.add(paramDef) - let requestPragmas = quote: - {.async: (raises: []), gcsafe.} + let requestPragmas = + case mode + of rbAsync: + quote: + {.async: (raises: []), gcsafe.} + of rbSync: + quote: + {.raises: [].} var providerCall = newCall(providerSym) for argName in argNameIdents: providerCall.add(argName) @@ -396,23 +581,49 @@ macro RequestBroker*(body: untyped): untyped = "): no provider registered for input signature" ) ) - requestBody.add( - quote do: - let catchedRes = catch: - await `providerCall` - if catchedRes.isErr(): - return err("Request failed:" & catchedRes.error.msg) - let providerRes = catchedRes.get() - when `isRefObjectLit`: + case mode + of rbAsync: + requestBody.add( + quote do: + let catchedRes = catch: + await `providerCall` + if catchedRes.isErr(): + return err( + "RequestBroker(" & `typeNameLit` & "): provider threw exception: " & + catchedRes.error.msg + ) + + let providerRes = catchedRes.get() if providerRes.isOk(): let resultValue = providerRes.get() - if resultValue.isNil(): - return err( - "RequestBroker(" & `typeNameLit` & "): provider returned nil result" - ) - return providerRes - ) + when compiles(resultValue.isNil()): + if resultValue.isNil(): + return err( + "RequestBroker(" & `typeNameLit` & "): provider returned nil result" + ) + return providerRes + ) + of rbSync: + requestBody.add( + quote do: + var providerRes: Result[`typeIdent`, string] + try: + providerRes = `providerCall` + except CatchableError as e: + return err( + "RequestBroker(" & `typeNameLit` & "): provider threw exception: " & e.msg + ) + + if providerRes.isOk(): + let resultValue = providerRes.get() + when compiles(resultValue.isNil()): + if resultValue.isNil(): + return err( + "RequestBroker(" & `typeNameLit` & "): provider returned nil result" + ) + return providerRes + ) # requestBody.add(providerCall) result.add( newTree( @@ -436,3 +647,17 @@ macro RequestBroker*(body: untyped): untyped = when defined(requestBrokerDebug): echo result.repr + + return result + +macro RequestBroker*(body: untyped): untyped = + ## Default (async) mode. + generateRequestBroker(body, rbAsync) + +macro RequestBroker*(mode: untyped, body: untyped): untyped = + ## Explicit mode selector. + ## Example: + ## RequestBroker(sync): + ## type Foo = object + ## proc signature*(): Result[Foo, string] + generateRequestBroker(body, parseMode(mode))