strong_json 0.7.1 → 0.8.0

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