xeme

Standard structure for reporting results of an operation.


License
MIT
Install
gem install xeme -v 2.0

Documentation

Xeme

Xeme provides a common format for returning the results of a process. First we'll look at the Xeme format, which is language independent, then we'll look at how the Xeme gem implements the format.

Xeme structure

The Xeme structure can be used by any software, Ruby or otherwise, as a standard way to report results. A Xeme structure can be stored in any format that recognizes hashes, arrays, strings, numbers, booleans, and null. Such formats include JSON and YAML. In these examples we'll use JSON.

A xeme can be as simple as an empty hash:

{}

That structure indicates no errors, and, in fact, no details at all. However, it also does not explicitly indicate success. In the absence of a defined value of true or false, the result should be considered either undetermined or failed.

To indicate a successful operation, a xeme must have an explicit success element:

{"success":true}

A xeme can be marked as explicitly failed:

{"success":false}

Any truthy value for success is considered to indicate success. So, for example, the following xeme should be considered to indicate success.

{"success":{}}

A xeme may contain other arbitrary information. This is useful if you need more information than just a success or failure.

{"success":true, "tags":["a", "b"]}

Metainformation

A xeme may contain a meta element. The meta element can contain a timestamp, UUID, description, or other meta information.

{
  "success":true,
  "meta": {
    "timestamp": "2023-06-21T08:57:56+00:00",
    "uuid": "e11b668c-0823-4b70-aa28-5ac83757a37c",
    "description": "directory tests"
  }
}

meta can contain any arbitrary information you want, but several elements, if present, should follow some standards.

key description
id A string, typically without spaces.
description A short description of the xeme as a string.
timestamp Timestamp in a standard ISO 8601 format.
UUID A UUID.

Nested xemes

A xeme represents the results of a single process. However, a xeme can also contain nested xemes which provide information about sub-processes. In this sense, a xeme can be considered as the final results of the xemes nested within it.

Child xemes are contained in the nested array.

{
  "success":true,
  "meta": {"id":"directory"},
  "nested":[
    {"success":true, "meta": {"id":"database-connection"}},
    {"success":true, "meta": {"id":"update"}}
  ]
}

Nested xemes can themselves have nested xemes, forming a tree of process results.

{
  "success":true,
  "meta": {"id":"database"},
  "nested": [
    {
      "success":true,
      "meta": {"id":"database-connection"},
      "nested": [
        {"success":true, "meta":{"id":"initialization"}},
        {"success":true, "meta":{"id":"disconnection"}}
      ]
    }
  ]
}

Resolution

Xeme operates on the concept of "least successful outcome". A xeme cannot represent more success than its descendent xemes. So, for example, the following structure contains conflicts:

{
  "success":true,
  "nested":[
    {"success":false}
  ]
}

It is necessary to resolve these conflicts before the xeme can be considered valid. A conforming processor should implement the following rules:

  • If a xeme is marked as failure, all of its ancestor xemes should be marked as failed. So the example above would be resolved as follows:
{
  "success":false,
  "nested":[
    {"success":false}
  ]
}

A process can be marked as failed regardless of its nested xemes. So there is no conflict in the following structure.

{
  "success":false,
  "nested":[
    {"success":true}
  ]
}
  • If a xeme's success is null, then all its ancestors' success values must be set to null if they are not already set to false. The following structure is invalid.
{
  "success":true,
  "nested":[
    {"success":null}
  ]
}

It should be resolved as follows:

{
  "success":null,
  "nested":[
    {"success":null}
  ]
}

Advisory and promise xemes

Three types of Xemes operate on a different ruleset than standard xemes. Those types are warnings, notes, and promises. They are indicated by the type property:

{"type": "warning"}

Warnings and notes

Warnings and notes provide advisory information about a process. They have no affect on the success/failure determination. Warnings provide information about non-fatal problems in a prosess. Notes do not indicate problems of any kind and simply provide whatever arbitrary information might be useful. Advisory xemes should not have success properties. So, for example, the following structure is valid, even though the nested advisory xemes do not have success properties.

{
  "success":true,
  "nested":[
    {"type":"warning", "id":"invalid-setting"},
    {"type":"note", "id":"database-connected"}
  ]
}

Advisory xemes can have nested xemes. However, all descendents of an advisory xeme should themselves be advisory.

Promises

A promise xeme indicates that the success/failure of a process has yet to be determined. A promise should have a success value of null, unless it has also has a truthy value in supplanted. For example, the following xemes are valid:

{ "type":"promise" }
{ "type":"promise", "supplanted":true, "success": true }
{ "type":"promise", "supplanted":"2023-06-22T06:28:43+0000", "success": true }

Typically, a promise should have an indication of how the promise can be resolved. For example, the following xeme is a promise, with further information to indicate a URI where the final result can be determined, and how soon to query that URI.

{
  "type":"promise",
  "uri": "https://example.com/20435t",
  "delay": 6000
}

No standards are defined on how promises should provide such information. A substandard may be defined down the road.

Best practice is that when a promise xeme is supplanted, it should have a nested xeme that provides the final success/failure of the process.

{
  "success": true,
  "supplanted": true,
  "type":"promise",
  "uri": "https://example.com/20435t",
  "delay": 6000,
  
  "nested": [
    {"success":true}
  ]
}

Xeme gem

Install

The usual:

sudo gem install xeme

Basic Xeme concepts

Xeme (the gem) is a thin layer over a hash that implements the Xeme format. (For the rest of this document "Xeme" refers to the Ruby class, not the format.) Create a new xeme by instantiating the Xeme class. Instantiation has no required parameters.

require 'xeme'
xeme = Xeme.new
puts xeme # => #<Xeme:0x000055586f1340a8>

If you want to access the hash stored in the xeme object, you can use the object as if it were a hash.

xeme['errors'] = []
xeme['errors'].push({'id'=>'my-error'})

Success and failure

Because a xeme isn't considered successful until it has been explicitly declared so, a new xeme is considered to indicate failure or lack of determination of success/failure.

xeme = Xeme.new
puts xeme.success?.class # => NilClas

To set a xeme to indicate failure, use the fail method.

xeme = Xeme.new
xeme.fail
puts xeme.success? # => false

Perhaps counter-intuitively, there is no succeed method. That's because a xeme cannot be reliably marked as succeeding without checking nested xemes (see resolution).

Instead there is a try_succeed method. As its name implies, that method resolves the xeme and its descendents, only marking the xeme as successful if resolution allows. We'll get more into handling nested xemes later. The following example shows setting a single xeme to success using try_succeed.

xeme = Xeme.new
xeme.try_succeed
puts xeme.success? # => true

try_succeed will only set a xeme to success if the current success is null or true. It will not override an explicit setting of false.

xeme = Xeme.new
xeme.fail
xeme.try_succeed
puts xeme.success? # => false

Nesting xemes

Use nest to create nested xemes. If a block is sent, nest yields the new xeme.

xeme.nest() do |child|
  # do stuff with nested xeme
end

nest also returns the new xeme.

child = xeme.nest()

Several methods exist to provide shortcuts for creating nested xemes of various types: success, error, warning, note, and promise.

xeme.success() do |child|
  puts child.success? # => true
end

xeme.error() do |child|
  puts child.success? # => false
end

# Xeme#failure does same thing as Xeme#error
xeme.failure() do |child|
  puts child.success? # => false
end

xeme.warning() do |child|
  puts child.class # => Xeme::Warning
end

xeme.note() do |child|
  puts child.class # => Xeme::Note
end

xeme.promise() do |child|
  puts child.class # => Xeme::Promise
end

Querying nested xemes

Xeme provides several methods for traversing through a stack of nested xemes. In the following examples, we'll use this structure:

xeme = Xeme.new()
xeme.id = 'top'

xeme.nest() do |child|
  child.id = 'foo'
  
  child.nest() do |grandchild|
    grandchild.id = 'bar'
    grandchild.warning.id = 'my-warning'
    grandchild.promise.id = 'my-promise'
    grandchild.success
  end
  
  child.error.id = 'my-error'
  child.note.id = 'my-note'
end

The simplest is the all method, which returns the xeme itself and all nested xemes as a locked array.

xeme.all.each do |x|
   puts x.id
end

# => top
# => foo
# => bar
# => my-warning
# => my-promise
# => my-error
# => my-note

There are several methods for selecting xemes based on their success status.

# xemes marked as success=false
puts xeme.errors.length # => 3

# xemes marked as success=true
puts xeme.successes.length # => 1

# xemes marked as success=null
# does not return advisory xemes
puts xeme.nils.length # => 2

There are also several methods for selecting specific xemes based on their types.

puts xeme.warnings.length # => 1
puts xeme.notes.length # => 2
puts xeme.promises.length # => 1

resolve() and try_succeed()

Xemes can be resolved using the resolve method.

xeme.resolve

It's common that your process will get to a point, typically at the end of the script, where you want to mark the process as successful, but only if there were no errors. Use try_succeed for that. That method first resolves the xeme and its descendents, then marks the xeme (and descendents) as successful if there are no errors.

xeme.try_succeed
puts xeme.success? # => true, false, or nil

The name

The word "xeme" has no particular association with the concept of results reporting. I got the word from a random word generator and I liked it. The xeme, also known as Sabine's gull, is a type of gull. See the Wikipedia page if you'd like to know more.

Author

Mike O'Sullivan mike@idocs.com

History

version date notes
0.1 Jan 7, 2020 Initial upload.
1.0 May 29, 2023 Complete overhaul. Not backward compatible.
1.1 May 29, 2023 Added and cleaned up documentation. No change to functionality.
1.2 May 29, 2023 More cleanup to documentation.
2.0 Jun 22, 2023 Another complete overhaul. This should be the last non-backwards compatible revision.