structured_reader 1.0.0 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
-
[![Build Status](https://travis-ci.org/mboeh/structured_reader.svg?branch=master)](https://travis-ci.org/mboeh/structured_reader)
|
3
|
+
[![Build Status](https://travis-ci.org/mboeh/structured_reader.svg?branch=master)](https://travis-ci.org/mboeh/structured_reader) [![Gem Version](https://badge.fury.io/rb/structured_reader.svg)](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
|