xeme 0.3.1 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
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. |