xeme 0.3.1 → 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +352 -260
  3. data/lib/xeme.rb +422 -450
  4. metadata +24 -11
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1413d7abd0e3cbfa5bebfad9dd795b007df52d93b177ab7233fbafc4bdd79c98
4
- data.tar.gz: bad5d9f7de703c07d092a88f68ab97f5dbf01eae52ebdebc4632c611c5c3656d
3
+ metadata.gz: 2dd9c9b62b1d0d2f4db4657ff6880cea68476a47ca4e366da77eab004d5ad71f
4
+ data.tar.gz: b15afd00d574e1382597d43df90f50668e0c8a651f2623ab5447af0d717bd404
5
5
  SHA512:
6
- metadata.gz: 03d58e05f1544a1b68400bfb76be1ae38e880b3e4bde7c4e0894591b83015d9eee26b81803b96910cc045e06045adc75943798a42a3ceeeaa9c3600dd6d8305a
7
- data.tar.gz: 674de2ab9106711fef9c123e68ddba3cc4293f3d8a3f1c2c7032e361e0864005e4c2a047e41df1eba98419d92a6420095fcfc27f36d3bac6c88f2e7a0eb3dd1d
6
+ metadata.gz: 800b710d55a2745c7591a8c5867c5b97125750d525b9ed941f8826093c60efac36f03a135ca9104728ffe849351d176f257a0dc13382a25a87b5d2e9f553653f
7
+ data.tar.gz: 90c33ffa2e86e06895176ce1a16aa06fef19991fb0c7fbcadc8cb79f9480d6fc4ee1764e162d8b5e3e00993c07523926d38befb74c1a3b9ef22193b968eb64ff
data/README.md CHANGED
@@ -1,263 +1,362 @@
1
1
  # Xeme
2
2
 
3
- Xeme provides a common format for returning the results of a process. In its
4
- simplest use, you create a Xeme object and add errors as necessary:
5
-
6
- require 'xeme'
7
- xeme = Xeme.new
8
- xeme.error 'error-1'
9
-
10
- If there are any errors, such as in this first example, <tt>success</tt> returns false:
11
-
12
- puts xeme.success # false
13
-
14
- A Xeme object without any errors returns true for <tt>success</tt>:
15
-
16
- xeme = Xeme.new
17
- puts xeme.success # true
18
-
19
- Xeme is a good way to report results that are more complex than just success or
20
- failure. For example, consider the situation in which a record must have a name
21
- field and an email field. If the record does not meet these requirements, it is
22
- not sufficient to merely report success or failure, nor to just report that one
23
- field or the other is missing. With Xeme you can report all of that information
24
- in a single object:
25
-
26
- xeme = Xeme.new
27
- xeme.error 'name'
28
- xeme.error 'email'
29
-
30
- You can return the Xeme object to another Ruby process, or you can output it to
31
- JSON for use in some other program:
32
-
33
- puts xeme.to_json
34
-
35
- # outputs:
36
- {"success":false,"messages":{"errors":[{"id":"name"},{"id":"email"}]}}
37
-
38
- You might prefer to add the <tt>pretty</tt> option for more readable output:
39
-
40
- puts xeme.to_json('pretty'=>true)
41
-
42
- # outputs:
43
- {
44
- "success": false,
45
- "messages": {
46
- "errors": [
47
- {
48
- "id": "name"
49
- },
50
- {
51
- "id": "email"
52
- }
53
- ]
54
- }
55
- }
56
-
57
- In some situations, it's useful to give details about the error. For example,
58
- if the <tt>name</tt> field is too long, you can report not just that there is
59
- an error in that field, but specifics about that error:
60
-
61
- xeme.error('name') do |msg|
62
- msg['too-long'] = true
63
- msg['max-length'] = 45
64
- end
65
-
66
- That would produce a structure like this:
67
-
68
- {
69
- "success": false,
70
- "messages": {
71
- "errors": [
72
- {
73
- "id": "name",
74
- "details": {
75
- "too-long": true,
76
- "max-length": 45
77
- }
78
- }
79
- ]
80
- }
81
- }
82
-
83
-
84
- You can also add warnings and notes. A warning indicates a problem but not an
85
- actual failure. A note does not indicate any problem at all but just some
86
- information that should be passed along.
87
-
88
- xeme.warning 'database-reset'
89
- xeme.note 'database ok'
90
-
91
- It's often useful to add misc details to the results. For example, a database
92
- query might result in some rows from a table. You might add these rows to the
93
- <tt>misc</tt> property like this:
94
-
95
- xeme = Xeme.new
96
- xeme.misc['rows'] = []
97
- xeme.misc['rows'].push 'a'
98
- xeme.misc['rows'].push 'b'
99
- xeme.misc['rows'].push 'c'
100
- puts xeme.to_json('pretty'=>true)
101
-
102
- # outputs:
103
- {
104
- "success": true,
105
- "misc": {
106
- "rows": [
107
- "a",
108
- "b",
109
- "c"
110
- ]
111
- }
112
- }
113
-
114
- Xeme can input a Xeme JSON structure to make a new Xeme object:
115
-
116
- # json
117
- json = <<~'JSON'
118
- {
119
- "success": false,
120
- "messages": {
121
- "errors": [
122
- { "id": "location" }
123
- ]
124
- }
125
- }
126
- JSON
127
-
128
- # xeme
129
- xeme = Xeme.from_json(json)
3
+ Xeme provides a common format for returning the results of a process. First
4
+ we'll look at the Xeme format, which is language independent, then we'll look at
5
+ how the Xeme gem implements the format.
6
+
130
7
 
131
8
  ## Xeme structure
132
9
 
133
10
  The Xeme structure can be used by any software, Ruby or otherwise, as a standard
134
11
  way to report results. A Xeme structure can be stored in any format that
135
12
  recognizes hashes, arrays, strings, numbers, booleans, and null. Such formats
136
- include JSON and YAML. Xeme would be a good way for REST applications to
137
- provide results from a query. The Xeme#to_json method outputs such a structure.
138
- We'll use JSON for these examples.
139
-
140
- An empty structure indicates success:
141
-
142
- {}
143
-
144
- That structure indicates no messages such as errors, and no details. To make the
145
- structure easier to use by software that doesn't have a Xeme module, it is
146
- customary to also output an explicit indication of success or failure:
147
-
148
- {"success":true}
149
-
150
- A Xeme structure can report any number of messages. A message is an error,
151
- warning, or note. An error indicates that the process failed, and gives a resaon
152
- why. If there are any errors, the process is considered a failure. A warning
153
- indicates a problem, but not an actual failure. A note reports useful
154
- information that does not indicate any kind of problem at all. Neither a warning
155
- nor a note cause failure.
156
-
157
- Messages, if any, are stored in a messages hash, which consists of arrays for
158
- errors, warnings, and notes.
159
-
160
- {
161
- "success": false,
162
- "messages": {
163
- "errors": [
164
- {
165
- "id": "missing-city"
166
- }
167
- ],
168
- "warnings": [
169
- {
170
- "id": "database-reset"
171
- }
172
- ],
173
- "notes": [
174
- {
175
- "id": "database-online"
176
- }
177
- ]
178
- }
179
- }
180
-
181
- Any number of messages can be reported. So, for example, the following structure
182
- has two errors and no warnings or notes.
183
-
184
- {
185
- "success": false,
186
- "messages": {
187
- "errors": [
188
- {
189
- "id": "missing-city"
190
- }
191
- ],
192
- "warnings": [
193
- {
194
- "id": "missing-zip-code"
195
- }
196
- ]
197
- }
198
- }
199
-
200
- A message can have associated details. For example, the following error
201
- indicates that the problem with the name field is that it's too long, and the
202
- maximum length is 45:
203
-
204
- {
205
- "success": false,
206
- "messages": {
207
- "errors": [
208
- {
209
- "id": "name",
210
- "details": {
211
- "too-long": true,
212
- "max-length": 45
213
- }
214
- }
215
- ]
216
- }
217
- }
218
-
219
- Sometimes it is useful to add arbitrary information to the results. For example,
220
- the results from a database query might return the rows that the query produced.
221
- In that situation, the misc element is handy.
222
-
223
- {
224
- "success": true,
225
- "misc": {
226
- "rows": [
227
- "Fred",
228
- "Mary",
229
- "Jane"
230
- ]
231
- }
232
- }
233
-
234
- Sometimes it is useful to provide information about the specific transaction,
235
- e.g. the call to a REST server. The transaction element can be used to provide
236
- that information. If there is a transaction element, it should always provide a
237
- timestamp for the transaction and a unique ID for it.
238
-
239
- {
240
- "success": true,
241
- "transaction": {
242
- "response": "08214643960776591",
243
- "timestamp": "2020-01-07T18:41:37+00:00"
244
- }
245
- }
246
-
247
- If the request includes an ID for that specific request, then that ID can be
248
- supplied as part of the transaction element:
249
-
250
- {
251
- "success": true,
252
- "transaction": {
253
- "request": "64e57c8a-bd3e-47b9-9221-e9e3e0263341",
254
- "response": "3301315461336113",
255
- "timestamp": "2020-01-07T18:42:01+00:00"
256
- }
257
- }
258
-
259
- The general adoption of the Xeme structure could simplify implementing
260
- interoperable applications such as REST APIs.
13
+ include JSON and YAML. In these examples we'll use JSON.
14
+
15
+ A xeme can be as simple as an empty hash:
16
+
17
+ ```json
18
+ {}
19
+ ```
20
+
21
+ That structure indicates no errors, and, in fact, no details at all. However, it
22
+ also does not explicitly indicate success, so that xeme would be considered to
23
+ have failed.
24
+
25
+ To indicate a successful operation, a xeme must have an explicit `success`
26
+ element:
27
+
28
+ ```json
29
+ {"success":true}
30
+ ```
31
+
32
+ A xeme can be marked as explicitly failed:
33
+
34
+ ```json
35
+ {"success":false}
36
+ ```
37
+
38
+ If a xeme does not have an explicit `success` element, or it is set to `nil`, it
39
+ should be considered to have failed. However, depending on how you process the
40
+ xeme, `nil` could be considered as having not finished the test. In that case,
41
+ consider using [promises](#promises).
42
+
43
+ ### Messages
44
+
45
+ A message is an error, a warning, a note, or a promise. Each type of message has
46
+ an array. For example, this xeme has two errors:
47
+
48
+ ```json
49
+ { "errors":[{"id":"http-fault"}, {"id":"transaction-error"}] }
50
+ ```
51
+
52
+ A message does not have to have any particular structure, but best practice is
53
+ to give each message an identifier (`id`).
54
+
55
+ If there are any errors then that indicates a failure, regardless of the value
56
+ of `success`. Any implementation of Xeme should have a method for resolving
57
+ conflicts between `success` and `errors`, always giving priority to the presence
58
+ of errors over the value of `success`.
59
+
60
+ If a xeme has any promises then that indicates not success, though not
61
+ necessarily explicit failure. If a xeme is marked as successful, but there are
62
+ promises, then `success` should be considered nil.
63
+
64
+ Warnings indicate problems that don't outright cause a failure. Notes are
65
+ messages that don't indicate a problem of any kind.
66
+
67
+ ### Metainformation
68
+
69
+ Sometimes it's useful to store metainformation the xeme. For example, log files
70
+ are more discoverable if each xeme has a unique id and timestamp. Generally a
71
+ xeme will have at least to elements, `uuid` and `timestamp`:
72
+
73
+ ```json
74
+ {
75
+ "meta":{
76
+ "uuid":"dae1cf26-e8fa-43fa-bedc-88fea10255f4",
77
+ "timestamp":"2023-05-25 03:46:19 -0400"
78
+ }
79
+ }
80
+ ```
81
+
82
+ ### Nested xemes
83
+
84
+ A xeme can have nested xemes within it. Those nested xemes go in the `nested`
85
+ array:
86
+
87
+ ```json
88
+ {
89
+ "nested": [
90
+ { "success": true },
91
+ { "errors": [{"id":"server-fault"}] },
92
+ ]
93
+ }
94
+ ```
95
+
96
+ For a xeme to be considered successful, all of its nested xemes must be marked
97
+ as success. If any nested xemes have errors, then the outermost xeme is
98
+ considered to be considered as failed. If any nested xemes has `success` as nil,
99
+ then the outermost xeme cannot `success` as true, though it can be explicitly
100
+ false.
101
+
102
+ ## Xeme gem
103
+
104
+ ### Install
105
+
106
+ The usual:
107
+
108
+ ```sh
109
+ gem install xeme
110
+ ```
111
+
112
+ ### Using the Xeme gem
113
+
114
+ Xeme (the gem) is a thin layer over a hash that implements the Xeme format. (For
115
+ the rest of this document "Xeme" refers to the Ruby class, not the format.)
116
+ Create a new xeme by instantiating the Xeme class. Instantiation has no required
117
+ parameters.
118
+
119
+ ```ruby
120
+ require 'xeme'
121
+ xeme = Xeme.new
122
+ puts xeme # => #<Xeme:0x000055586f1340a8>
123
+ ```
124
+
125
+ Sometimes it's handy to give a xeme an identifier. You can do that by passing in
126
+ a string in `Xeme.new`.
127
+
128
+ ```ruby
129
+ xeme = Xeme.new('my-xeme')
130
+ puts xeme.id # => my-xeme
131
+ ```
132
+
133
+ If you want to access the hash stored in the xeme object, you can use the object
134
+ as if it were a hash.
135
+
136
+ ```ruby
137
+ xeme['errors'] = []
138
+ xeme['errors'].push({'id'=>'my-error'})
139
+ ```
140
+
141
+ #### Success and failure
142
+
143
+ Because a xeme isn't considered successful until it has been explicitly declared
144
+ so, a new xeme is considered to indicate failure. However, because there are no
145
+ errors and `success` has not been explicitly set, `success?` returns `nil`
146
+ instead of `false`.
147
+
148
+ ```ruby
149
+ xeme = Xeme.new
150
+ puts xeme.success?.class # => NilClas
151
+ ```
152
+
153
+ There are two ways to mark a xeme as successful, one of which usually the better
154
+ choice. The not-so-good way to mark success is with the `succeed` method.
155
+
156
+ ```ruby
157
+ xeme = Xeme.new
158
+ xeme.succeed
159
+ puts xeme.success? # => true
160
+ ```
161
+
162
+ The problem with `succeed` is that if there are errors, `succeed` will raise an
163
+ exception.
164
+
165
+ ```ruby
166
+ xeme = Xeme.new
167
+ xeme.error 'my-error'
168
+ xeme.succeed # => raises exception: `succeed': cannot-set-to-success: errors
169
+ ```
170
+
171
+ A better option is `try_succeed`. If your script gets to a point, usually at the
172
+ end of the function or script, that you want to set the xeme to success, but
173
+ only if there are no errors, use `try_succeed`.
174
+
175
+ ```ruby
176
+ xeme = Xeme.new
177
+ xeme.try_succeed
178
+ puts xeme.success? # => true
179
+ ```
180
+
181
+ If there are errors, `try_succeed` won't raise an exception, but will not set
182
+ the xeme to failure.
183
+
184
+ ```ruby
185
+ xeme = Xeme.new
186
+ xeme.error 'my-error'
187
+ xeme.try_succeed
188
+ puts xeme.success? # => false
189
+ ```
190
+
191
+ #### Creating and using messages
192
+
193
+ Messages in a xeme provide a way to indicate errors (i.e. fatal errors),
194
+ warnings (non-fatal errors), notes (not an error at all), and promises (guides
195
+ to getting the final success or failure of the process). A message is a hash
196
+ with whatever arbitrary information you want to add. Each type of message has
197
+ its own method for creating it, an array for storing them, and methods for
198
+ checking if any exist. The following script creates an error, a warning, a
199
+ note, and a promise.
200
+
201
+ ```ruby
202
+ xeme = Xeme.new
203
+ xeme.error 'my-error'
204
+ xeme.warning 'my-warning'
205
+ xeme.note 'my-note'
206
+ xeme.promise 'my-promise'
207
+ ```
208
+
209
+ `error`, `warning`, and `note` each create a message for their own type.
210
+ Although it is not required, it's usually a good idea to give a string as the
211
+ first parameter. That string will be set to the `id` element in the resulting
212
+ hash, as seen in the example above.
213
+
214
+ `errors`, `warnings`, and `notes` return arrays for each type.
215
+
216
+ ```ruby
217
+ xeme.errors.each do |e|
218
+ puts e['id'] # => my-error
219
+ end
220
+
221
+ xeme.warnings.each do |w|
222
+ puts w['id'] # => my-warning
223
+ end
224
+
225
+ xeme.notes.each do |n|
226
+ puts n['id'] # => my-note
227
+ end
228
+
229
+ xeme.promises.each do |p|
230
+ puts p['id'] # => my-promise
231
+ end
232
+ ```
233
+
234
+ **Gotcha:** These methods return frozen arrays, *not* the arrays in the xeme.
235
+ This is because these methods return not only the xeme's own message arrays, but
236
+ also any nested messages. See [Nesting xemes](#nesting-xemes) below.
237
+
238
+ There are several ways to create and populate a message. Choose whichever is
239
+ preferable to you. One way is demonstrated in the example above. You simply call
240
+ the appropriate method, passing in an identifier. If all you want to do is
241
+ create a message with an `id` then that's probably the easiest choice.
242
+
243
+ Another way to use a `do` block to add custom information to the message.
244
+
245
+ ```ruby
246
+ xeme.error('my-error') do |error|
247
+ error['database-error'] = 'some database error'
248
+ error['commands'] = ['a', 'b', 'c']
249
+ end
250
+ ```
251
+
252
+ Remember a message is just a hash, so you can add any kind of structure to the
253
+ hash you want such a strings, booleans, hashes, and arrays.
254
+
255
+ Finally, the message command returns the new message. So, if you want, you can
256
+ assign that return value to a variable and work with the message that way.
257
+
258
+ ```ruby
259
+ err = xeme.error('my-error')
260
+ err['database-error'] = 'some database error'
261
+ err['commands'] = ['a', 'b', 'c']
262
+ ```
263
+
264
+ #### Nesting xemes
265
+
266
+ In complex testing situations it can be useful to nest results within other
267
+ results. To nest a xeme within another xeme, use the `#nest` method:
268
+
269
+ ```ruby
270
+ xeme = Xeme.new('results')
271
+ xeme.nest 'child-xeme'
272
+ ```
273
+
274
+ You probably want to do something more than just create a nested xeme, so you
275
+ can use a `do` block to work with the nested xeme:
276
+
277
+ ```ruby
278
+ xeme.nest('child-xeme') do |child|
279
+ child.error 'child-error'
280
+ end
281
+ ```
282
+
283
+ You can loop through all xemes, including the outermost xeme and all nested
284
+ xemes, using the `#all` method.
285
+
286
+ ```ruby
287
+ xeme.all.each do |x|
288
+ puts x.id
289
+ end
290
+ ```
291
+
292
+ **Nested messages**
293
+
294
+ The `#errors`, `#warnings`, `#notes`, and `#promises` methods return arrays of
295
+ all messages within the xeme, including the outermost xeme and nested xemes.
296
+
297
+ ```ruby
298
+ xeme = Xeme.new
299
+ xeme.error 'outer-error'
300
+
301
+ xeme.nest do |child|
302
+ child.error 'child-error'
303
+ end
304
+
305
+ puts xeme.errors
306
+
307
+ # => {"id"=>"outer-error"}
308
+ # => {"id"=>"child-error"}
309
+ ```
310
+
311
+ If you want to search for messages with a specific `id`, add that id to the
312
+ messages method:
313
+
314
+ ```ruby
315
+ puts xeme.errors('child-error') # => {"id"=>"child-error"}
316
+ ```
317
+
318
+ **Flatten**
319
+
320
+ Finally, if you want to slurp up all messages into the outermost xeme and delete
321
+ the nested xemes, use `#flatten`.
322
+
323
+ ```ruby
324
+ puts xeme['errors']
325
+ # => {"id"=>"outer-error"}
326
+
327
+ xeme.flatten
328
+
329
+ puts xeme['errors']
330
+ # => {"id"=>"outer-error"}
331
+ # => {"id"=>"child-error"}
332
+ ```
333
+
334
+
335
+ #### Resolving xemes
336
+
337
+ A xeme can contain contradictory information. For example, if `success` is true
338
+ but there are errors, then the xeme should be considered as failed. If there are
339
+ promises, then the xeme should not be considered as failed, although `success`
340
+ may be set as nil.
341
+
342
+ The `#resolve` method resolves those contradictions. Generally you won't have to
343
+ call `#resolve` yourself, but it's worth understanding the rules:
344
+
345
+ * A xeme can always be explicitly set to false, regardless of any other
346
+ considerations. Resolution never changes a `success` of false.
347
+
348
+ * If any nested xemes have `success` explicitly set to false, then the outermost
349
+ xeme will be set to false.
350
+
351
+ * If a xeme has errors, or any of it's nested xemes has errors, then it is set
352
+ to false.
353
+
354
+ * If a xeme has promises, or any of its nested xemes do, then it cannot be set
355
+ to true. If it is already false, then it stays false. Otherwise `success` is set
356
+ to nil.
357
+
358
+ * If any nested xemes have `success` set to nil, then the outermost xeme cannot
359
+ be set to true.
261
360
 
262
361
  ## The name
263
362
 
@@ -267,12 +366,6 @@ also known as Sabine's gull, is a type of gull. See
267
366
  [the Wikipedia page](https://en.wikipedia.org/wiki/Sabine's_gull)
268
367
  if you'd like to know more.
269
368
 
270
- ## Install
271
-
272
- ```
273
- gem install xeme
274
- ```
275
-
276
369
  ## Author
277
370
 
278
371
  Mike O'Sullivan
@@ -283,5 +376,4 @@ mike@idocs.com
283
376
  | version | date | notes |
284
377
  |---------|--------------|-------------------------------|
285
378
  | 0.1 | Jan 7, 2020 | Initial upload. |
286
- | 0.2 | Jan 8, 2020 | Fixed bug in exception() |
287
- | 0.3 | Jan 19, 2020 | Fixed bug in Xeme.from_json() |
379
+ | 1.0 | May 29, 2023 | Complete overhaul. Not backward compatible. |