ur
A Universal Result macro/object that normalizes the information returned from a procedure
Summary
| Latest Version | Unknown |
|---|---|
| License | MIT |
| CI Status | Passing |
| Downloads | 0 |
| Last Indexed | 2026-07-21 05:24 |
Tags
Installation
nimble install ur
choosenim install ur
git clone https://github.com/JohnAD/ur
OS Compatibility
| Platform | Linux | macOS | Windows | FreeBSD | OpenBSD | NetBSD | Android | iOS | WASM | Embedded |
|---|---|---|---|---|---|---|---|---|---|---|
| ur | ✓ | ✓ | ✓ | - | - | - | - | - | - | - |
Source
| Repository | https://github.com/JohnAD/ur |
|---|---|
| Homepage | https://github.com/JohnAD/ur |
| Documentation | View Documentation |
| Registry Source | nimble_official |
README
Module ur
.. image:: https://raw.githubusercontent.com/yglukhov/nimble-tag/master/nimble.png :height: 34 :width: 131 :alt: nimble
A Universal Result (UR) is an object that allows the programmer to return either a value or a sequence of messages (or both) from a procedure. This could, of course, be done by hand using tuple or other options, but the goal of this package is two-fold:
- Make it easy (and predictable) to create such "dynamic" results.
- Make it possible to integrate such a system with other libraries.
Table of Contents
Video Introduction <#video-introduction>__A Simple Example <#a-simple-example>__Using With Logging <#using-with-logging>__The UR Object <#the-ur-object>__Bonus: Adding Detail <#bonus-adding-detail>__See also <#see-also>__
Video Introduction
.. figure:: https://img.youtube.com/vi/HI5MBF5MTaQ/0.jpg :width: 480px :align: center :height: 360px :alt: Using UR: A Nim Library :figclass: align-center :target: https://youtu.be/HI5MBF5MTaQ
A Simple Example
The following is a very simple example of UR.
First, we are going to import the library and "wrap" the type of element we want to return.
.. code:: nim
import ur
type
Vector = tuple[x: float, y: float]
wrap_UR(Vector)
The wrap_UR macro creates a UR_Vector object with large set of
useful methods.
(Don't worry, with conditional compiling, Nim should later remove the methods you don't use.)
Now, we use the new object for returning a flexible result:
.. code:: nim
import ur
type
Vector = tuple[x: float, y: float]
wrap_UR(Vector)
proc reduceXByNumber(v: Vector, denominator: float): UR_Vector =
result = newUR_Vector() # this procedure was generated by 'wrap_UR'
if denominator == 0.0:
result.set_failure("You can't divide by zero; Ever")
return
if denominator < 0.0:
result.set_failure("Negative denominators are not allowed")
return
if denominator < 0.1:
result.set_warning("That is an awefully small denominator")
var newVector = v
newVector.x = newVector.x / denominator
result.value = newVector
result.set_expected_success("Vector x reduced")
Now let's use it:
.. code:: nim
var a: Vector = (4.0, 3.2)
var response = reduceXByNumber(a, 2.0)
if response.ok:
echo "my new x is " & $response.value.x
should display:
.. code::
my new x is 2.0
and
.. code:: nim
response = reduceXByNumber(a, 0.0)
if not response.ok:
echo "error messages: "
echo $response
should display:
.. code::
error messages:
UR events: (class: danger, msg: You can't divide by zero; Ever)
and
.. code:: nim
response = reduceXByNumber(a, 0.0001)
if response.ok:
echo "my new x is " & $response.value.x
if response.has_warning:
echo "my warnings are " & $response.warning_msgs
should display:
.. code::
my new x is 40000.0
my warnings are @["That is an awefully small denominator"]
In general, if a returned result is .ok then there is a .value. If it is
not .ok, then there isn't and the details are in the events created.
However, even .ok events can have success, info, and warning messages.
Using With Logging
UR already has one library integrated: Nim's standard
logging module. You can use it by importing 'ur/log'.
For example:
.. code:: nim
import
strutils,
logging
import
ur,
ur/log
var L = newFileLogger("test.log", fmtStr = verboseFmtStr)
addHandler(L)
type
Vector = tuple[x: float, y: float]
wrap_UR(Vector)
proc example(v: Vector): UR_Vector:
result = newUR_Vector()
result.value = v
result.value.x = result.value.x + 1.0
result.set_expected_success("x incremented by 1.0")
var a = Vector(x: 9.3, y: 3.0)
var response = a.example()
echo "message: $1, x: $2".format(response.msg, response.value.x)
response.sendLog() # this sends the event(s) to logging
Now "test.log" will have an entry similar to this:
.. code:: log
D, [2018-06-29T12:34:42] -- app: success; user; x incremented by 1.0
All filtering for sendLog is done by logging; and that library
strictly looks at the level attribute.
The UR Object
UR is all about the automatically generate UR_\ object objects. The objects are defined internally as:
.. code:: nim
type
URevent*
msg*: string
level*: Level
class*: DisplayClass
audience*: Audience
UR_<type>
events*: seq[URevent]
value*: <type>
So, essentially, there is a list of events (messages) and the value being returned.
Each event has a message and three very distinct attributes.
level
The ``level`` is the degree of distribution for the message.
It answers the question: *How Important is This?*
The available levels:
- ``lvlAll``
- ``lvlDebug``
- ``lvlInfo``
- ``lvlNotice``
- ``lvlWarn``
- ``lvlError``
- ``lvlFatal``
- ``lvlNone``
The ``level`` definitions are set by the ``logging`` standard library
that is part of Nim. See: https://nim-lang.org/docs/logging.html
NOTE: the names of the levels are somewhat misleading. Using a level of
``lvlError`` does NOT mean that an error has occured. It means *"if I'm
filtering a log for mostly errors, this message should show up in that
log"*.
For judging the character of the event, use the ``class``.
class
The class is the judgement of the event.
it answers the question: Is this a good or bad event?
Only four classes are possible:
info- a neutral message adding extra informationsuccess- everything workedwarning- everything worked, but something is suspiciousdanger- failure/error/bug
The class definitions are from the Boostrap CSS project. See:
https://getbootstrap.com
audience ~~~~~~~~
The audience is, not surpisingly, the intended audience for any
message about the event.
In a traditional 'logging' or SYSLOG system, the intended audience is
strictly ops. UR allows for further targets; useful when UR is
integrated with web apps or other development frameworks.
It answers the question: Who is permitted to see This?
The possible audiences are:
ops- IT staff, developers, software agentsadmin- users with admin clearanceuser- regular end users / registered memberspublic- the whole world (no restrictions)
Each audience permission is more restrictive than the previous. So,
ops can see all events. But admin can only see admin,
user and public events. And so on.
Combining the attributes together. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The attributes are meant to be combined when making decisions.
For example, an event with an audience of user but a level
of lvlDebug probably won't be shown to the end user. Essentially,
they have permission to see the message, but won't because harrasing an
end user with debug messages is not a friendly thing to do.
Bonus: Adding Detail
There is also wrapper called wrap_UR_detail that adds a table of
strings to a UR called detail. The purpose of this is to allow more
sophisticated logging and handling of events. Of course, adding such
support also increases the overhead of UR; so please take that into
consideration.
Building on the earlier example for logging:
.. code:: nim
import
strutils,
logging
import
ur,
ur/log
var L = newFileLogger("test.log", fmtStr = verboseFmtStr)
addHandler(L)
type
Vector = tuple[x: float, y: float]
wrap_UR_detail(Vector)
proc example(v: Vector, category: string): UR_Vector:
result = newUR_Vector()
result.value = v
result.value.x = result.value.x + 1.0
result.set_expected_success("x incremented by 1.0")
result.detail["category"] = category
var a = Vector(x: 9.3, y: 3.0)
var response = a.example("project abc")
echo "message: $1, category: $2".format(response.msg, response.detail["category"])
To use the detail in the context of ur/log, there is a procedure
called setURLogFormat. It is expecting a pointer to a procedure.
That procedure must have the following parameters:
.. code:: nim
(event: UREvent, detail: Table[string, string]): string
So, for example:
.. code:: nim
var L = newFileLogger("test.log", fmtStr = verboseFmtStr)
addHandler(L)
proc my_example_format(event: UREvent, detail: Table[string, string]): string =
var category = "unknown"
if detail.hasKey("category"):
category = detail["category"]
result = "[$1] [$2] $3".format(event.class, category, event.msg)
setURLogFormat(my_example_format)
Now, the entry in "test.log" will look like:
.. code:: log
D, [2018-06-29T12:34:42] -- app: [success] [project abc] x incremented by 1.0
NOTE: the setURLLogFormat procedure also works with the simpler
wrap_UR. The detail table will simply be empty.
See also
Reference for module ur <docs/ur-ref.rst>__Reference for module ur/log <docs/ur-log-ref.rst>__