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 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