No description
Find a file
2018-07-07 20:36:48 -05:00
docs Add YT video link to doc, update ver 2018-07-07 20:36:11 -05:00
src Merge branch 'master' of https://github.com/JohnAD/ur 2018-07-07 20:36:48 -05:00
.gitignore Initial commit 2018-06-17 21:11:25 -05:00
docnimble.json Add doc blocks and docnimble's settings file 2018-07-06 13:10:19 -05:00
LICENSE Initial commit 2018-06-17 21:11:25 -05:00
README.rst Add YT video link to doc, update ver 2018-07-07 20:36:11 -05:00
ur.nimble Add YT video link to doc, update ver 2018-07-07 20:36:11 -05:00

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>`__