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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 0621752b200561ae03415cd92ccbd8a9c65db9ff
4
- data.tar.gz: c7af4b1c0273c3e44b87efc782eec2235a8bff21
3
+ metadata.gz: fc24067c752c75359adf7819eb53548cabe39037
4
+ data.tar.gz: 468e6deb0c0326c0ac431c9d77a63d40bc366b81
5
5
  SHA512:
6
- metadata.gz: 522146b8e92418a53a6047b675a4b1306f2d3b14a0fc8054f137051ca931080acbbb8c266de3e8e05271c06bdff8e8e5c9521dbe703ac3c7b8fe248df679a48c
7
- data.tar.gz: 6750388513520282561be86f6779f2c74a08a4b7a226b1c473d179b893a4d061256a87f4f00728e7e6a6b777fe621fa3743177383ea5cb6a1d6f6b9c958a4b3c
6
+ metadata.gz: d44c39fb69b8f9b9f29f361eb2df44bbb5d74e355f847edca948c6dfed19034b5d2812573652baec4baebe0617b70e619ec562fc1722e9fca834cead1b6d8885
7
+ data.tar.gz: d6738272399d1f8716573644969fe58d932fa5c657253b76cfb6b01fcb8f74699ccbe4c356d6dee38ab84bf78f8d27397700b7dabe4599fd42cb73c137eb53a3
data/CHANGES.md ADDED
@@ -0,0 +1,7 @@
1
+ ## 1.0.1 (2017-12-26)
2
+
3
+ * Add a full validation reading mode that reports every detected error with the document.
4
+
5
+ ## 1.0.0 (2017-12-24)
6
+
7
+ * First release!
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
@@ -27,7 +27,13 @@ module StructuredReader
27
27
  if document.kind_of?(String)
28
28
  document = JSON.parse document
29
29
  end
30
- @root_reader.read(document, context)
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
- if reader.read(fragment, ValidatorContext.new).empty?
287
- return context.accept(reader.read(fragment, context))
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
- @errors
364
+ fragment
342
365
  end
343
366
 
344
367
  def flunk(fragment, reason)
@@ -1,3 +1,3 @@
1
1
  module StructuredReader
2
- VERSION = "1.0.0"
2
+ VERSION = "1.0.1"
3
3
  end
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.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: 2017-12-25 00:00:00.000000000 Z
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