strong_json 0.7.1 → 0.8.0

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
  SHA256:
3
- metadata.gz: b38c3654e379c46a4b5567f8c256c72da14ca8efa3bd64deef2b56c4e037957d
4
- data.tar.gz: b3b3832aa8b1e76a84c2210234f34b8abcecc02a3d42848ae871e648f41e35f8
3
+ metadata.gz: 2ddde917e3030412ba154b0b99ea307e2c50af8ff9ca7b45ba9730e96d6dfff0
4
+ data.tar.gz: a0fbfc9b8245dae4683236e1c6d5b92c97b5ce30ad4eef628e54bde56539f47c
5
5
  SHA512:
6
- metadata.gz: c2a949f49b357ffa0496bf1d6646d57190b6c26633ce604ef2cb30f6faf72b9d89de3b88172dac22776914b053eb5081c80a75bbdbf88217d1ff04d3bfdfe3c8
7
- data.tar.gz: d74c050d81ee145056978925f475e216692c107536258306857b4379288318930fafecce803d9e02454cd7997ab3b925005019ddad6a098a9750c0db7b0276cf
6
+ metadata.gz: a2051a77f37d08d171ad355ae97fdd0b46bf42fbaaa771824f3cff3be4e0498e1a3844881036e6877981ae44852eee89347894768e86b28d49a44d73c9c8867c
7
+ data.tar.gz: 460f595197f2772851bcf4e400ca08bd7410a797cc2161dc39e7f35526211f508b0940ee2733b5ba07d661b43a5ccbd535599debd722e0f5115eaf795c53a5f9
data/.travis.yml CHANGED
@@ -1,5 +1,3 @@
1
1
  language: ruby
2
2
  rvm:
3
- - "2.3.3"
4
- - "2.4.1"
5
- - "2.5.1"
3
+ - "2.5.3"
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # CHANGELOG
2
+
3
+ ## master
4
+
5
+ ## 0.8.0 (2018-10-29)
6
+
7
+ * Add steep typechecking
data/README.md CHANGED
@@ -124,6 +124,59 @@ Shortcuts for complex data are also defined as the following:
124
124
  * `optional(object(fields))` → `object?(fields)`
125
125
  * `optional(enum(types))` → `enum?(types)`
126
126
 
127
+ ## Type checking
128
+
129
+ StrongJSON ships with type definitions for [Steep](https://github.com/soutaro/steep).
130
+ You can type check your programs using StrongJSON by Steep.
131
+
132
+ ### Type definition
133
+
134
+ Define your types as the following.
135
+
136
+ ```
137
+ class JSONSchema::Account < StrongJSON
138
+ def account: -> StrongJSON::_Schema<{ id: Integer, name: String }>
139
+ end
140
+
141
+ Schema: JSONSchema::Account
142
+ ```
143
+
144
+ And write your schema definition as the following.
145
+
146
+ ```rb
147
+ Schema = _ = StrongJSON.new do
148
+ # @type self: JSONSchema::Account
149
+
150
+ let :account, object(id: number, name: string)
151
+ end
152
+
153
+ id = Schema.account.coerce(hash)[:id] # id is Integer
154
+ name = Schema.account.coerce(hash)[:name] # name is String
155
+ ```
156
+
157
+ Note that you need two tricks:
158
+
159
+ * A cast `_ = StrongJSON.new ...` on assignment to `Schema` constant
160
+ * A `@type self` annotation in the block
161
+
162
+ See the `example` directory.
163
+
164
+ ### Commandline
165
+
166
+ Steep 0.8.1 supports loading type definitions from gems.
167
+
168
+ Pass `-G` option to type check your program.
169
+
170
+ ```
171
+ $ steep check -G strong_json lib
172
+ ```
173
+
174
+ When you are using `bundler`, it automatically detects that StrongJSON has type definitions.
175
+
176
+ ```
177
+ $ bundle exec steep check lib
178
+ ```
179
+
127
180
  ## Contributing
128
181
 
129
182
  1. Fork it ( https://github.com/soutaro/strong_json/fork )
data/Rakefile CHANGED
@@ -3,4 +3,14 @@ require 'rspec/core/rake_task'
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task :default => [:spec, :typecheck, :"example:typecheck"]
7
+
8
+ task :typecheck do
9
+ sh "bundle exec steep check --strict lib"
10
+ end
11
+
12
+ namespace :example do
13
+ task :typecheck do
14
+ sh "bundle exec steep check --strict -I sig -I example example"
15
+ end
16
+ end
@@ -0,0 +1,27 @@
1
+ Schema = _ = StrongJSON.new do
2
+ # @type self: AddressSchema
3
+
4
+ let :address, object(address: string, country: symbol?)
5
+ let :email, object(email: string)
6
+ let :contact, enum(address, email)
7
+ let :person, object(name: string, contacts: array(contact))
8
+ end
9
+
10
+ person = Schema.person.coerce(nil)
11
+
12
+ # @type var name: String
13
+ name = person[:name]
14
+
15
+ # @type var contacts: Array<email | address>
16
+ contacts = person[:contacts]
17
+
18
+ contacts.each do |contact|
19
+ case
20
+ when contact.keys.include?(:email)
21
+ # @type var contact: email
22
+ puts "Email: #{contact[:email]}"
23
+ when contact.keys.include?(:address)
24
+ # @type var contact: address
25
+ puts "Address: #{contact[:address]} (#{contact[:country] || "unspecified"})"
26
+ end
27
+ end
@@ -0,0 +1,11 @@
1
+ type address = { address: String, country: Symbol? }
2
+ type email = { email: String }
3
+
4
+ class AddressSchema < StrongJSON
5
+ def address: -> StrongJSON::_Schema<address>
6
+ def email: -> StrongJSON::_Schema<email>
7
+ def contact: -> StrongJSON::_Schema<email | address>
8
+ def person: -> StrongJSON::_Schema<{ name: String, contacts: Array<email | address> }>
9
+ end
10
+
11
+ Schema: AddressSchema
@@ -18,6 +18,7 @@ class StrongJSON
18
18
  class Base
19
19
  include Match
20
20
 
21
+ # @dynamic type
21
22
  attr_reader :type
22
23
 
23
24
  def initialize(type)
@@ -87,6 +88,7 @@ class StrongJSON
87
88
  class Literal
88
89
  include Match
89
90
 
91
+ # @dynamic value
90
92
  attr_reader :value
91
93
 
92
94
  def initialize(value)
@@ -98,7 +100,7 @@ class StrongJSON
98
100
  end
99
101
 
100
102
  def coerce(value, path: [])
101
- raise Error.new(path: path, type: self, value: value) unless self.value == value
103
+ raise Error.new(path: path, type: self, value: value) unless (_ = self.value) == value
102
104
  value
103
105
  end
104
106
  end
@@ -137,6 +139,7 @@ class StrongJSON
137
139
  raise Error.new(path: path, type: self, value: object)
138
140
  end
139
141
 
142
+ # @type var result: ::Hash<Symbol, any>
140
143
  result = {}
141
144
 
142
145
  object.each do |key, value|
@@ -153,7 +156,7 @@ class StrongJSON
153
156
  end
154
157
  end
155
158
 
156
- result
159
+ _ = result
157
160
  end
158
161
 
159
162
  def test_value_type(path, type, value)
@@ -166,11 +169,16 @@ class StrongJSON
166
169
  end
167
170
 
168
171
  def merge(fields)
169
- if fields.is_a?(Object)
170
- fields = fields.instance_variable_get("@fields")
171
- end
172
+ # @type var fs: Hash<Symbol, _Schema<any>>
173
+
174
+ fs = case fields
175
+ when Object
176
+ fields.instance_variable_get(:"@fields")
177
+ when Hash
178
+ fields
179
+ end
172
180
 
173
- Object.new(@fields.merge(fields))
181
+ Object.new(@fields.merge(fs))
174
182
  end
175
183
 
176
184
  def except(*keys)
@@ -180,6 +188,7 @@ class StrongJSON
180
188
  end
181
189
 
182
190
  def to_s
191
+ # @type var fields: ::Array<String>
183
192
  fields = []
184
193
 
185
194
  @fields.each do |name, type|
@@ -193,6 +202,7 @@ class StrongJSON
193
202
  class Enum
194
203
  include Match
195
204
 
205
+ # @dynamic types
196
206
  attr_reader :types
197
207
 
198
208
  def initialize(types)
@@ -216,6 +226,7 @@ class StrongJSON
216
226
  end
217
227
 
218
228
  class UnexpectedFieldError < StandardError
229
+ # @dynamic path, value
219
230
  attr_reader :path, :value
220
231
 
221
232
  def initialize(path: , value:)
@@ -230,6 +241,7 @@ class StrongJSON
230
241
  end
231
242
 
232
243
  class IllegalTypeError < StandardError
244
+ # @dynamic type
233
245
  attr_reader :type
234
246
 
235
247
  def initialize(type:)
@@ -242,6 +254,7 @@ class StrongJSON
242
254
  end
243
255
 
244
256
  class Error < StandardError
257
+ # @dynamic path, type, value
245
258
  attr_reader :path, :type, :value
246
259
 
247
260
  def initialize(path:, type:, value:)
@@ -1,13 +1,16 @@
1
1
  class StrongJSON
2
2
  module Types
3
+ # @type method object: (?Hash<Symbol, ty>) -> _Schema<any>
3
4
  def object(fields = {})
4
5
  Type::Object.new(fields)
5
6
  end
6
7
 
8
+ # @type method array: (?ty) -> _Schema<any>
7
9
  def array(type = any)
8
10
  Type::Array.new(type)
9
11
  end
10
12
 
13
+ # @type method optional: (?ty) -> _Schema<any>
11
14
  def optional(type = any)
12
15
  Type::Optional.new(type)
13
16
  end
@@ -1,3 +1,5 @@
1
1
  class StrongJSON
2
- VERSION = "0.7.1"
2
+ # @dynamic initialize, let
3
+
4
+ VERSION = "0.8.0"
3
5
  end
@@ -0,0 +1,44 @@
1
+ class StrongJSON
2
+ def initialize: { (self) -> void } -> any
3
+ def let: (Symbol, ty) -> void
4
+ include StrongJSON::Types
5
+ end
6
+
7
+ StrongJSON::VERSION: String
8
+
9
+ interface StrongJSON::_Schema<'type>
10
+ def coerce: (any, ?path: ::Array<Symbol>) -> 'type
11
+ def =~: (any) -> bool
12
+ def to_s: -> String
13
+ def is_a?: (any) -> bool
14
+ end
15
+
16
+ type StrongJSON::ty = _Schema<any>
17
+
18
+ module StrongJSON::Types
19
+ def object: <'x> (Hash<Symbol, ty>) -> _Schema<'x>
20
+ | () -> _Schema<Hash<Symbol, any>>
21
+ def object?: <'x> (Hash<Symbol, ty>) -> _Schema<'x | nil>
22
+ def any: () -> _Schema<any>
23
+ def optional: <'x> (?_Schema<'x>) -> _Schema<'x | nil>
24
+ | () -> _Schema<any>
25
+ def string: () -> _Schema<String>
26
+ def string?: () -> _Schema<String?>
27
+ def number: () -> _Schema<Numeric>
28
+ def number?: () -> _Schema<Numeric?>
29
+ def numeric: () -> _Schema<Numeric>
30
+ def numeric?: () -> _Schema<Numeric?>
31
+ def boolean: () -> _Schema<bool>
32
+ def boolean?: () -> _Schema<bool?>
33
+ def symbol: () -> _Schema<Symbol>
34
+ def symbol?: () -> _Schema<Symbol?>
35
+ def array: <'x> (_Schema<'x>) -> _Schema<Array<'x>>
36
+ | () -> _Schema<Array<any>>
37
+ def array?: <'x> (_Schema<'x>) -> _Schema<Array<'x>?>
38
+ def literal: <'x> ('x) -> _Schema<'x>
39
+ def literal?: <'x> ('x) -> _Schema<'x?>
40
+ def enum: <'x> (*_Schema<any>) -> _Schema<'x>
41
+ def enum?: <'x> (*_Schema<any>) -> _Schema<'x?>
42
+ def ignored: () -> _Schema<nil>
43
+ def prohibited: () -> _Schema<nil>
44
+ end
data/sig/type.rbi ADDED
@@ -0,0 +1,89 @@
1
+ module StrongJSON::Type
2
+ end
3
+
4
+ StrongJSON::Type::NONE: any
5
+
6
+ module StrongJSON::Type::Match: _Schema<any>
7
+ def =~: (any) -> bool
8
+ def ===: (any) -> bool
9
+ end
10
+
11
+ type StrongJSON::base_type_name = :ignored | :any | :number | :string | :boolean | :numeric | :symbol | :prohibited
12
+
13
+ class StrongJSON::Type::Base<'a>
14
+ include Match
15
+
16
+ attr_reader type: base_type_name
17
+
18
+ def initialize: (base_type_name) -> any
19
+ def test: (any) -> bool
20
+ def coerce: (any, ?path: ::Array<Symbol>) -> 'a
21
+ end
22
+
23
+ class StrongJSON::Type::Optional<'t>
24
+ include Match
25
+
26
+ @type: _Schema<'t>
27
+
28
+ def initialize: (_Schema<'t>) -> any
29
+ def coerce: (any, ?path: ::Array<Symbol>) -> ('t | nil)
30
+ end
31
+
32
+ class StrongJSON::Type::Literal<'t>
33
+ include Match
34
+
35
+ attr_reader value: 't
36
+
37
+ def initialize: ('t) -> any
38
+ def coerce: (any, ?path: ::Array<Symbol>) -> 't
39
+ end
40
+
41
+ class StrongJSON::Type::Array<'t>
42
+ include Match
43
+
44
+ @type: _Schema<'t>
45
+
46
+ def initialize: (_Schema<'t>) -> any
47
+ def coerce: (any, ?path: ::Array<Symbol>) -> ::Array<'t>
48
+ end
49
+
50
+ class StrongJSON::Type::Object<'t>
51
+ include Match
52
+
53
+ @fields: Hash<Symbol, _Schema<'t>>
54
+
55
+ def initialize: (Hash<Symbol, _Schema<'t>>) -> any
56
+ def coerce: (any, ?path: ::Array<Symbol>) -> 't
57
+ def test_value_type: <'x, 'y> (::Array<Symbol>, _Schema<'x>, any) { ('x) -> 'y } -> 'y
58
+ def merge: (Object<any> | Hash<Symbol, _Schema<any>>) -> Object<any>
59
+ def except: (*Symbol) -> Object<any>
60
+ end
61
+
62
+ class StrongJSON::Type::Enum<'t>
63
+ include Match
64
+
65
+ attr_reader types: ::Array<_Schema<any>>
66
+
67
+ def initialize: (::Array<_Schema<any>>) -> any
68
+ def coerce: (any, ?path: ::Array<Symbol>) -> 't
69
+ end
70
+
71
+ class StrongJSON::Type::Error
72
+ attr_reader path: ::Array<Symbol>
73
+ attr_reader type: ty
74
+ attr_reader value: any
75
+
76
+ def initialize: (path: ::Array<Symbol>, type: ty, value: any) -> any
77
+ end
78
+
79
+ class StrongJSON::Type::UnexpectedFieldError
80
+ attr_reader path: ::Array<Symbol>
81
+ attr_reader value: any
82
+
83
+ def initialize: (path: ::Array<Symbol>, value: any) -> any
84
+ end
85
+
86
+ class StrongJSON::Type::IllegalTypeError
87
+ attr_reader type: ty
88
+ def initialize: (type: ty) -> any
89
+ end
data/strong_json.gemspec CHANGED
@@ -17,8 +17,10 @@ Gem::Specification.new do |spec|
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
+ spec.metadata = { "steep_types" => "sig" }
20
21
 
21
22
  spec.add_development_dependency "bundler", "~> 1.6"
22
23
  spec.add_development_dependency "rake", "~> 10.0"
23
24
  spec.add_development_dependency "rspec", "~> 3.0"
25
+ spec.add_development_dependency "steep", "~> 0.8"
24
26
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: strong_json
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Soutaro Matsumoto
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-10-11 00:00:00.000000000 Z
11
+ date: 2018-10-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: steep
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '0.8'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '0.8'
55
69
  description: Type check JSON objects
56
70
  email:
57
71
  - matsumoto@soutaro.com
@@ -62,14 +76,19 @@ files:
62
76
  - ".gitignore"
63
77
  - ".ruby-version"
64
78
  - ".travis.yml"
79
+ - CHANGELOG.md
65
80
  - Gemfile
66
81
  - LICENSE.txt
67
82
  - README.md
68
83
  - Rakefile
84
+ - example/example.rb
85
+ - example/example.rbi
69
86
  - lib/strong_json.rb
70
87
  - lib/strong_json/type.rb
71
88
  - lib/strong_json/types.rb
72
89
  - lib/strong_json/version.rb
90
+ - sig/strong_json.rbi
91
+ - sig/type.rbi
73
92
  - spec/array_spec.rb
74
93
  - spec/basetype_spec.rb
75
94
  - spec/case_subsumption_operator_spec.rb
@@ -83,7 +102,8 @@ files:
83
102
  homepage: https://github.com/soutaro/strong_json
84
103
  licenses:
85
104
  - MIT
86
- metadata: {}
105
+ metadata:
106
+ steep_types: sig
87
107
  post_install_message:
88
108
  rdoc_options: []
89
109
  require_paths: