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 +4 -4
- data/.travis.yml +1 -3
- data/CHANGELOG.md +7 -0
- data/README.md +53 -0
- data/Rakefile +11 -1
- data/example/example.rb +27 -0
- data/example/example.rbi +11 -0
- data/lib/strong_json/type.rb +19 -6
- data/lib/strong_json/types.rb +3 -0
- data/lib/strong_json/version.rb +3 -1
- data/sig/strong_json.rbi +44 -0
- data/sig/type.rbi +89 -0
- data/strong_json.gemspec +2 -0
- metadata +23 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2ddde917e3030412ba154b0b99ea307e2c50af8ff9ca7b45ba9730e96d6dfff0
|
4
|
+
data.tar.gz: a0fbfc9b8245dae4683236e1c6d5b92c97b5ce30ad4eef628e54bde56539f47c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a2051a77f37d08d171ad355ae97fdd0b46bf42fbaaa771824f3cff3be4e0498e1a3844881036e6877981ae44852eee89347894768e86b28d49a44d73c9c8867c
|
7
|
+
data.tar.gz: 460f595197f2772851bcf4e400ca08bd7410a797cc2161dc39e7f35526211f508b0940ee2733b5ba07d661b43a5ccbd535599debd722e0f5115eaf795c53a5f9
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
ADDED
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
|
data/example/example.rb
ADDED
@@ -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
|
data/example/example.rbi
ADDED
@@ -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
|
data/lib/strong_json/type.rb
CHANGED
@@ -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
|
-
|
170
|
-
|
171
|
-
|
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(
|
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:)
|
data/lib/strong_json/types.rb
CHANGED
@@ -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
|
data/lib/strong_json/version.rb
CHANGED
data/sig/strong_json.rbi
ADDED
@@ -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.
|
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
|
+
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:
|