Xeme

Xeme provides a common format for returning the results of a process. In its simplest use, you create a Xeme object and add errors as necessary:

require 'xeme'
xeme = Xeme.new
xeme.error 'error-1'

If there are any errors, such as in this first example, success returns false:

puts xeme.success # false

A Xeme object without any errors returns true for success:

xeme = Xeme.new
puts xeme.success # true

Xeme is a good way to report results that are more complex than just success or failure. For example, consider the situation in which a record must have a name field and an email field. If the record does not meet these requirements, it is not sufficient to merely report success or failure, nor to just report that one field or the other is missing. With Xeme you can report all of that information in a single object:

xeme = Xeme.new
xeme.error 'name'
xeme.error 'email'

You can return the Xeme object to another Ruby process, or you can output it to JSON for use in some other program:

puts xeme.to_json

# outputs:
{"success":false,"messages":{"errors":[{"id":"name"},{"id":"email"}]}}

You might prefer to add the pretty option for more readable output:

puts xeme.to_json('pretty'=>true)

# outputs:
{
    "success": false,
    "messages": {
        "errors": [
            {
                "id": "name"
            },
            {
                "id": "email"
            }
        ]
    }
}

In some situations, it's useful to give details about the error. For example, if the name field is too long, you can report not just that there is an error in that field, but specifics about that error:

xeme.error('name') do |msg|
    msg['too-long'] = true
    msg['max-length'] = 45
end

That would produce a structure like this:

{
    "success": false,
    "messages": {
        "errors": [
            {
                "id": "name",
                "details": {
                    "too-long": true,
                    "max-length": 45
                }
            }
        ]
    }
}

You can also add warnings and notes. A warning indicates a problem but not an actual failure. A note does not indicate any problem at all but just some information that should be passed along.

xeme.warning 'database-reset'
xeme.note 'database ok'

It's often useful to add misc details to the results. For example, a database query might result in some rows from a table. You might add these rows to the misc property like this:

xeme = Xeme.new
xeme.misc['rows'] = []
xeme.misc['rows'].push 'a'
xeme.misc['rows'].push 'b'
xeme.misc['rows'].push 'c'
puts xeme.to_json('pretty'=>true)

# outputs:
{
    "success": true,
    "misc": {
        "rows": [
            "a",
            "b",
            "c"
        ]
    }
}

Xeme can input a Xeme JSON structure to make a new Xeme object:

# json
json = <<~'JSON'
{
    "success": false,
    "messages": {
        "errors": [
            { "id": "location" }
        ]
    }
}
JSON

# xeme
xeme = Xeme.from_json(json)

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. Xeme would be a good way for REST applications to provide results from a query. The Xeme#to_json method outputs such a structure. We'll use JSON for these examples.

An empty structure indicates success:

{}

That structure indicates no messages such as errors, and no details. To make the structure easier to use by software that doesn't have a Xeme module, it is customary to also output an explicit indication of success or failure:

{"success":true}

A Xeme structure can report any number of messages. A message is an error, warning, or note. An error indicates that the process failed, and gives a resaon why. If there are any errors, the process is considered a failure. A warning indicates a problem, but not an actual failure. A note reports useful information that does not indicate any kind of problem at all. Neither a warning nor a note cause failure.

Messages, if any, are stored in a messages hash, which consists of arrays for errors, warnings, and notes.

{
    "success": false,
    "messages": {
        "errors": [
            {
                "id": "missing-city"
            }
        ],
        "warnings": [
            {
                "id": "database-reset"
            }
        ],
        "notes": [
            {
                "id": "database-online"
            }
        ]
    }
}

Any number of messages can be reported. So, for example, the following structure has two errors and no warnings or notes.

{
    "success": false,
    "messages": {
        "errors": [
            {
                "id": "missing-city"
            }
            ],
            "warnings": [
            {
                "id": "missing-zip-code"
            }
        ]
    }
}

A message can have associated details. For example, the following error indicates that the problem with the name field is that it's too long, and the maximum length is 45:

{
    "success": false,
    "messages": {
        "errors": [
            {
                "id": "name",
                "details": {
                    "too-long": true,
                    "max-length": 45
                }
            }
        ]
    }
}

Sometimes it is useful to add arbitrary information to the results. For example, the results from a database query might return the rows that the query produced. In that situation, the misc element is handy.

{
    "success": true,
    "misc": {
        "rows": [
            "Fred",
            "Mary",
            "Jane"
        ]
    }
}

Sometimes it is useful to provide information about the specific transaction, e.g. the call to a REST server. The transaction element can be used to provide that information. If there is a transaction element, it should always provide a timestamp for the transaction and a unique ID for it.

{
    "success": true,
        "transaction": {
            "response": "08214643960776591",
            "timestamp": "2020-01-07T18:41:37+00:00"
        }
}

If the request includes an ID for that specific request, then that ID can be supplied as part of the transaction element:

{
    "success": true,
    "transaction": {
        "request": "64e57c8a-bd3e-47b9-9221-e9e3e0263341",
        "response": "3301315461336113",
        "timestamp": "2020-01-07T18:42:01+00:00"
    }
}

The general adoption of the Xeme structure could simplify implementing interoperable applications such as REST APIs.

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.

Install

gem install xeme

Author

Mike O'Sullivan [email protected]

History

version date notes
0.1 Jan 7, 2020 Initial upload.
0.2 Jan 8, 2020 Fixed bug in exception()
0.3 Jan 19, 2020 Fixed bug in Xeme.from_json()