mirror of
https://github.com/JohnAD/ur
synced 2026-01-02 17:24:43 +00:00
No description
| docs | ||
| src | ||
| .gitignore | ||
| docnimble.json | ||
| LICENSE | ||
| README.rst | ||
| ur.nimble | ||
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:
1. Make it easy (and predictable) to create such "dynamic" results.
2. 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 information
- ``success`` - everything worked
- ``warning`` - everything worked, but something is suspicious
- ``danger`` - 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 agents
- ``admin`` - users with admin clearance
- ``user`` - regular end users / registered members
- ``public`` - 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>`__