structured_reader 1.0.0 → 1.0.1
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.
- checksums.yaml +4 -4
- data/CHANGES.md +7 -0
- data/README.md +29 -1
- data/examples/exceptions.rb +54 -0
- data/lib/structured_reader.rb +27 -4
- data/lib/structured_reader/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fc24067c752c75359adf7819eb53548cabe39037
|
4
|
+
data.tar.gz: 468e6deb0c0326c0ac431c9d77a63d40bc366b81
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d44c39fb69b8f9b9f29f361eb2df44bbb5d74e355f847edca948c6dfed19034b5d2812573652baec4baebe0617b70e619ec562fc1722e9fca834cead1b6d8885
|
7
|
+
data.tar.gz: d6738272399d1f8716573644969fe58d932fa5c657253b76cfb6b01fcb8f74699ccbe4c356d6dee38ab84bf78f8d27397700b7dabe4599fd42cb73c137eb53a3
|
data/CHANGES.md
ADDED
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# StructuredReader
|
2
2
|
|
3
|
-
[](https://travis-ci.org/mboeh/structured_reader)
|
3
|
+
[](https://travis-ci.org/mboeh/structured_reader) [](https://badge.fury.io/rb/structured_reader)
|
4
4
|
|
5
5
|
This library allows you to create declarative rulesets (or schemas) for reading primitive data structures (hashes + arrays + strings + numbers) or JSON into validated data objects. Free yourself from `json.fetch(:widget).fetch(:box).fetch(:dimensions).fetch(:width)`. Get that good `widget.box.dimensions.width` without risking NoMethodErrors. Have confidence that if you're passed total unexpected nonsense, it won't be smoothed over by a convenient MagicHashyMash.
|
6
6
|
|
@@ -91,6 +91,34 @@ p result.widgets[1].last_updated_at # ==> #<DateTime: 2017-12-24T01:05:00-08:00
|
|
91
91
|
p result.widgets[1].comment # ! ==> NoMethodError
|
92
92
|
```
|
93
93
|
|
94
|
+
## Validation
|
95
|
+
|
96
|
+
Readers validate input to ensure it is of the expected type:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
reader = StructuredReader.json do |o|
|
100
|
+
o.object :point do |pt|
|
101
|
+
pt.number :x
|
102
|
+
pt.number :y
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
reader.read({}) # ! ==> StructuredReader::WrongTypeError: expected a Hash, got a NilClass (at .point)
|
107
|
+
reader.read({point: {} }) # ! ==> StructuredReader::WrongTypeError: expected a Numeric, got a NilClass (at .point.x)
|
108
|
+
reader.read({point: { x: 1, y: "f" } }) # ! ==> StructuredReader::WrongTypeError: expected a Numeric, got a String (at .point.y)
|
109
|
+
```
|
110
|
+
|
111
|
+
A full validation mode is also available, which avoids exceptions:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
r = reader.validate({point: { x: nil, y: "f" } })
|
115
|
+
p r.ok? # ==> false
|
116
|
+
p r.errors # ==> [[".point.x", "expected a Numeric"], [".point.y", "expected a Numeric"]
|
117
|
+
|
118
|
+
r2 = reader.validate({point: { x: 5, y: 10 }})
|
119
|
+
p r2.object.point.x # ==> 5
|
120
|
+
```
|
121
|
+
|
94
122
|
## Reader Types
|
95
123
|
|
96
124
|
* `string`: The classic.
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
|
3
|
+
require 'structured_reader'
|
4
|
+
|
5
|
+
reader = StructuredReader.json do |o|
|
6
|
+
o.collection :widgets do |w|
|
7
|
+
w.string :type, "widgetType"
|
8
|
+
w.number :price
|
9
|
+
w.string :description, nullable: true
|
10
|
+
w.array :tags, of: :string
|
11
|
+
w.object :dimensions, nullable: true do |dims|
|
12
|
+
dims.number :weight
|
13
|
+
dims.number :width
|
14
|
+
dims.number :height
|
15
|
+
end
|
16
|
+
w.time :last_updated_at, "lastUpdated"
|
17
|
+
end
|
18
|
+
o.object :pagination do |pg|
|
19
|
+
pg.string :next_url, "nextUrl", nullable: true
|
20
|
+
pg.number :total_items, "totalItems"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
document = {
|
25
|
+
widgets: [
|
26
|
+
{
|
27
|
+
widgetType: "squorzit",
|
28
|
+
price: 99.99,
|
29
|
+
description: "who can even say?",
|
30
|
+
tags: ["mysterious", "magical"],
|
31
|
+
dimensions: {
|
32
|
+
weight: 10,
|
33
|
+
width: 5,
|
34
|
+
height: nil
|
35
|
+
},
|
36
|
+
lastUpdated: "2017-12-24 01:01 AM PST"
|
37
|
+
},
|
38
|
+
{
|
39
|
+
widgetType: "frobulator",
|
40
|
+
price: "0.79",
|
41
|
+
tags: [123, { foo: "bar" }],
|
42
|
+
lastUpdated: nil
|
43
|
+
}
|
44
|
+
],
|
45
|
+
pagination: {
|
46
|
+
nextUrl: nil,
|
47
|
+
totalItems: 2
|
48
|
+
}
|
49
|
+
}
|
50
|
+
|
51
|
+
result = reader.validate(document)
|
52
|
+
|
53
|
+
p result.ok?
|
54
|
+
p result.errors
|
data/lib/structured_reader.rb
CHANGED
@@ -27,7 +27,13 @@ module StructuredReader
|
|
27
27
|
if document.kind_of?(String)
|
28
28
|
document = JSON.parse document
|
29
29
|
end
|
30
|
-
|
30
|
+
context.open do
|
31
|
+
@root_reader.read(document, context)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate(document)
|
36
|
+
read(document, ValidatorContext.new)
|
31
37
|
end
|
32
38
|
|
33
39
|
class ObjectReader
|
@@ -283,8 +289,11 @@ module StructuredReader
|
|
283
289
|
|
284
290
|
def read(fragment, context)
|
285
291
|
@readers.each do |reader|
|
286
|
-
|
287
|
-
|
292
|
+
result = ValidatorContext.new.open do |context|
|
293
|
+
reader.read(fragment, context)
|
294
|
+
end
|
295
|
+
if result.ok?
|
296
|
+
return context.accept(result.object)
|
288
297
|
end
|
289
298
|
end
|
290
299
|
|
@@ -320,6 +329,10 @@ module StructuredReader
|
|
320
329
|
fragment
|
321
330
|
end
|
322
331
|
|
332
|
+
def open(&blk)
|
333
|
+
blk.call(self)
|
334
|
+
end
|
335
|
+
|
323
336
|
def flunk(fragment, reason)
|
324
337
|
raise WrongTypeError, "#{reason}, got a #{fragment.class} (at #{@where})"
|
325
338
|
end
|
@@ -331,14 +344,24 @@ module StructuredReader
|
|
331
344
|
end
|
332
345
|
|
333
346
|
class ValidatorContext
|
347
|
+
Result = Struct.new(:object, :errors) do
|
348
|
+
def ok?
|
349
|
+
errors.empty?
|
350
|
+
end
|
351
|
+
end
|
334
352
|
|
335
353
|
def initialize(where = "", errors = [])
|
336
354
|
@where = where.dup.freeze
|
337
355
|
@errors = errors
|
338
356
|
end
|
339
357
|
|
358
|
+
def open(&blk)
|
359
|
+
result = blk.call(self)
|
360
|
+
Result.new(@errors.any? ? nil : result, @errors)
|
361
|
+
end
|
362
|
+
|
340
363
|
def accept(fragment)
|
341
|
-
|
364
|
+
fragment
|
342
365
|
end
|
343
366
|
|
344
367
|
def flunk(fragment, reason)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: structured_reader
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthew Boeh
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -64,6 +64,7 @@ files:
|
|
64
64
|
- ".gitignore"
|
65
65
|
- ".rspec"
|
66
66
|
- ".travis.yml"
|
67
|
+
- CHANGES.md
|
67
68
|
- Gemfile
|
68
69
|
- LICENSE.txt
|
69
70
|
- README.md
|
@@ -71,6 +72,7 @@ files:
|
|
71
72
|
- bin/console
|
72
73
|
- bin/setup
|
73
74
|
- examples/example.rb
|
75
|
+
- examples/exceptions.rb
|
74
76
|
- examples/selection.rb
|
75
77
|
- examples/union.rb
|
76
78
|
- lib/structured_reader.rb
|