json_world 0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: eae4719f06114b2907a119beee5c4b9ec7b04d15
4
+ data.tar.gz: c5685b9c6d82161d9ec95efad514d87b655062d6
5
+ SHA512:
6
+ metadata.gz: 76eabf0ae91aacb188238c878843c57c6c85b765ede90c26dc6d57372382f1cf87c9292a0fedff7f4b851dcb374b97f432c50d65f4ca8943e2fb96d3ed8e96f1
7
+ data.tar.gz: a31d598b74b08ef2785c732685c9f140d85de5dfb4edb730a5098e97ceea4f7d08f4a69724582427bca86b2764f0f94b3b26171b2a571b953c6a460cef38695c
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in json_world.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Ryo Nakamura
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,78 @@
1
+ # JsonWorld
2
+ Provides DSL to define JSON Schema representation of your class.
3
+
4
+ ## Usage
5
+ ### `.property` and `.link`
6
+ ```rb
7
+ class User
8
+ include JsonWorld::PropertyDefinable
9
+
10
+ property(
11
+ :id,
12
+ example: 1,
13
+ type: Integer,
14
+ )
15
+
16
+ property(
17
+ :name,
18
+ example: "alice",
19
+ pattern: /^\w{5}$/,
20
+ type: String,
21
+ )
22
+
23
+ link(
24
+ :get_user,
25
+ path: "/users/:user_id",
26
+ )
27
+
28
+ attr_reader :id, :name
29
+
30
+ # @param [Integer] id
31
+ # @param [String] name
32
+ def initialize(id: nil, name: nil)
33
+ @id = id
34
+ @name = name
35
+ end
36
+ end
37
+ ```
38
+
39
+ ### `#to_json` and `.to_json_schema`
40
+ ```rb
41
+ User.new(id: 1, name: "alice").to_json
42
+ # '{"id":1,"name":"alice"}'
43
+
44
+ User.to_json_schema
45
+ # '{
46
+ # "links": [
47
+ # {
48
+ # "href": "/users/:user_id",
49
+ # "title": "get_user"
50
+ # }
51
+ # ],
52
+ # "properties": {
53
+ # "id": {
54
+ # "example": 1,
55
+ # "type": "integer"
56
+ # },
57
+ # "name": {
58
+ # "example": "alice",
59
+ # "pattern": "^\\w{5}$",
60
+ # "type": "string"
61
+ # }
62
+ # },
63
+ # "required": [
64
+ # "id",
65
+ # "name"
66
+ # ]
67
+ # }'
68
+ ```
69
+
70
+ See [our tests](https://github.com/r7kamura/json_world/blob/master/spec/json_world/property_definable_spec.rb)
71
+ for more examples.
72
+
73
+ ## Installation
74
+ Add this line to your application's Gemfile:
75
+
76
+ ```ruby
77
+ gem "json_world"
78
+ ```
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,22 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "json_world/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "json_world"
7
+ spec.version = JsonWorld::VERSION
8
+ spec.authors = ["Ryo Nakamura"]
9
+ spec.email = ["r7kamura@gmail.com"]
10
+ spec.summary = "Provides DSL to define JSON Schema representation of your class."
11
+ spec.homepage = "https://github.com/r7kamura/json_world"
12
+ spec.license = "MIT"
13
+
14
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec)/}) }
15
+ spec.require_paths = ["lib"]
16
+
17
+ spec.add_dependency "activesupport"
18
+ spec.add_dependency "json"
19
+ spec.add_development_dependency "bundler", "~> 1.9"
20
+ spec.add_development_dependency "rake", "~> 10.0"
21
+ spec.add_development_dependency "rspec", "3.2.0"
22
+ end
@@ -0,0 +1,26 @@
1
+ module JsonWorld
2
+ module JsonEncodable
3
+ # @param [Hash{Symbol => Object}] options :except / :only
4
+ # @return [Hash]
5
+ def as_json(options = {})
6
+ properties(options).as_json(options)
7
+ end
8
+
9
+ private
10
+
11
+ # @note receiver.class.property_names must be implemented
12
+ # @param [Hash{Symbol => Object}] options :except / :only
13
+ # @return [Hash]
14
+ def properties(options = {})
15
+ names = self.class.property_names
16
+ names = names - Array(options[:except]) if options[:except]
17
+ names = names & Array(options[:only]) if options[:only]
18
+ names.inject({}) do |hash, property_name|
19
+ key = property_name
20
+ value = send(property_name)
21
+ value = value.iso8601 if value.is_a?(Time)
22
+ hash.merge(key => value)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,62 @@
1
+ module JsonWorld
2
+ class LinkDefinition
3
+ DEFAULT_HTTP_METHOD = "GET"
4
+
5
+ # @return [Symbol]
6
+ attr_reader :link_name
7
+
8
+ # @param [Symbol] link_name
9
+ # @param [Hash{Symbol => Object}] options
10
+ def initialize(link_name: nil, **options)
11
+ @options = options
12
+ @link_name = link_name
13
+ end
14
+
15
+ # @todo
16
+ # @return [Hash]
17
+ def as_json_schema
18
+ {
19
+ description: description,
20
+ href: path,
21
+ method: http_method,
22
+ schema: schema,
23
+ title: title,
24
+ }.reject do |_key, value|
25
+ value.nil? || value.empty?
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ # @return [String, nil]
32
+ def description
33
+ @options[:description]
34
+ end
35
+
36
+ # @note #method is reserved by Kernel.#method ;(
37
+ # @return [String]
38
+ def http_method
39
+ @options[:method] || DEFAULT_HTTP_METHOD
40
+ end
41
+
42
+ # @return [String]
43
+ def path
44
+ @options[:path]
45
+ end
46
+
47
+ # @return [Hash{Symbol => Object}, nil]
48
+ def schema
49
+ if @options[:parameters]
50
+ JsonWorld::PropertyDefinition.new(
51
+ properties: @options[:parameters],
52
+ property_name: :schema,
53
+ ).as_json_schema
54
+ end
55
+ end
56
+
57
+ # @return [String]
58
+ def title
59
+ @options[:title] || @link_name.to_s
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,118 @@
1
+ require "active_support/concern"
2
+ require "active_support/core_ext/object/json"
3
+ require "active_support/json"
4
+ require "json"
5
+ require "json_world/json_encodable"
6
+ require "json_world/link_definition"
7
+ require "json_world/property_definition"
8
+
9
+ module JsonWorld
10
+ module PropertyDefinable
11
+ extend ActiveSupport::Concern
12
+
13
+ included do
14
+ include JsonWorld::JsonEncodable
15
+ end
16
+
17
+ private
18
+
19
+ module ClassMethods
20
+ attr_writer :link_definitions
21
+ attr_writer :property_definitions
22
+
23
+ # @return [Hash]
24
+ def as_json_schema
25
+ {
26
+ description: description,
27
+ links: links_as_json_schema,
28
+ properties: properties_as_json_schema,
29
+ required: required_property_names,
30
+ title: title,
31
+ }.reject do |_key, value|
32
+ value.nil? || value.empty?
33
+ end
34
+ end
35
+
36
+ # @note Override
37
+ def inherited(child)
38
+ super
39
+ child.link_definitions = link_definitions
40
+ child.property_definitions = property_definitions.clone
41
+ end
42
+
43
+ # @param [Symbol] link_name
44
+ # @param [Hash{Symbol => Object}] options
45
+ def link(link_name, options = {})
46
+ link_definitions << JsonWorld::LinkDefinition.new(
47
+ options.merge(
48
+ parent: self,
49
+ link_name: link_name,
50
+ ),
51
+ )
52
+ end
53
+
54
+ # @return [Array<JsonWorld::LinkDefinition>]
55
+ def link_definitions
56
+ @link_definitions ||= []
57
+ end
58
+
59
+ # @param [Symbol] property_name
60
+ # @param [Hash{Symbol => Object}] options
61
+ def property(property_name, options = {})
62
+ property_definitions << JsonWorld::PropertyDefinition.new(
63
+ options.merge(
64
+ parent: self,
65
+ property_name: property_name,
66
+ )
67
+ )
68
+ end
69
+
70
+ # @return [Array<JsonWorld::PropertyDefinition>]
71
+ def property_definitions
72
+ @property_definitions ||= []
73
+ end
74
+
75
+ # @return [Array<Symbol>]
76
+ def property_names
77
+ property_definitions.map(&:property_name)
78
+ end
79
+
80
+ # @return [Array<Symbol>]
81
+ def required_property_names
82
+ property_definitions.reject(&:optional?).map(&:property_name)
83
+ end
84
+
85
+ # @note .as_json_schema wrappter
86
+ # @return [String]
87
+ def to_json_schema
88
+ JSON.pretty_generate(as_json_schema)
89
+ end
90
+
91
+ private
92
+
93
+ # @return [String, nil]
94
+ def description(value = nil)
95
+ @description ||= value
96
+ end
97
+
98
+ # @return [Array<Hash>]
99
+ def links_as_json_schema
100
+ link_definitions.map(&:as_json_schema)
101
+ end
102
+
103
+ # @return [Hash]
104
+ def properties_as_json_schema
105
+ property_definitions.inject({}) do |result, property_definition|
106
+ result.merge(
107
+ property_definition.property_name => property_definition.as_json_schema,
108
+ )
109
+ end
110
+ end
111
+
112
+ # @return [String, nil]
113
+ def title(value = nil)
114
+ @title ||= value
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,142 @@
1
+ module JsonWorld
2
+ class PropertyDefinition
3
+ # @return [Symbol]
4
+ attr_reader :property_name
5
+
6
+ # @param [Symbol, nil] property_name Note that unnamed property can be passed
7
+ # @param [Hash{Symbol => Object}] options
8
+ def initialize(property_name: nil, **options)
9
+ @options = options
10
+ @property_name = property_name
11
+ end
12
+
13
+ # @return [Hash{Symbol => Object}]
14
+ def as_nested_json_schema
15
+ { property_name => as_json_schema }
16
+ end
17
+
18
+ # @return [Hash{Symbol => Object}]
19
+ def as_json_schema
20
+ {
21
+ description: description,
22
+ example: example,
23
+ format: format_type,
24
+ items: items_as_json_schema,
25
+ pattern: pattern_in_string,
26
+ properties: properties_as_json_schema,
27
+ required: required_property_names,
28
+ type: type,
29
+ uniqueItems: unique_flag,
30
+ }.reject do |_key, value|
31
+ value.nil? || value.respond_to?(:empty?) && value.empty?
32
+ end
33
+ end
34
+
35
+ # @return [false, true] True if explicitly this property is defined as optional
36
+ def optional?
37
+ !!@options[:optional]
38
+ end
39
+
40
+ private
41
+
42
+ # @return [String, nil]
43
+ def description
44
+ @options[:description]
45
+ end
46
+
47
+ # @return [Object]
48
+ def example
49
+ @options[:example]
50
+ end
51
+
52
+ # @note format is preserved by Kernel.#format ;(
53
+ # @return [String, nil]
54
+ def format_type
55
+ if types.include?(Time)
56
+ "date-time"
57
+ end
58
+ end
59
+
60
+ # @note Tuple validation is not supported yet
61
+ # @return [Array<Hash>, nil]
62
+ def items_as_json_schema
63
+ if @options[:items]
64
+ JsonWorld::PropertyDefinition.new(@options[:items]).as_json_schema
65
+ end
66
+ end
67
+
68
+ # @note pattern can be used only when type is String or not specified
69
+ # @return [Regexp, nil]
70
+ def pattern
71
+ @options[:pattern]
72
+ end
73
+
74
+ # @return [String, nil]
75
+ def pattern_in_string
76
+ if pattern
77
+ pattern.inspect[1..-2]
78
+ end
79
+ end
80
+
81
+ # @return [Hash, nil]
82
+ def properties_as_json_schema
83
+ if @options[:properties]
84
+ property_definitions.map(&:as_nested_json_schema).inject({}, :merge)
85
+ end
86
+ end
87
+
88
+ # @return [Array, nil]
89
+ def property_definitions
90
+ if @options[:properties]
91
+ @options[:properties].map do |property_name, options|
92
+ JsonWorld::PropertyDefinition.new(
93
+ options.merge(
94
+ property_name: property_name,
95
+ )
96
+ )
97
+ end
98
+ end
99
+ end
100
+
101
+ # @return [Array<Symbol>, nil]
102
+ def required_property_names
103
+ if @options[:properties]
104
+ property_definitions.reject(&:optional?).map(&:property_name)
105
+ end
106
+ end
107
+
108
+ # @return [Array<String>, String, nil]
109
+ def type
110
+ strings = types.map do |this_type|
111
+ case
112
+ when this_type == Array
113
+ "array"
114
+ when this_type == Float
115
+ "float"
116
+ when this_type == Hash
117
+ "object"
118
+ when this_type == Integer
119
+ "integer"
120
+ when this_type == NilClass
121
+ "null"
122
+ when this_type == String || this_type == Time
123
+ "string"
124
+ end
125
+ end.compact
126
+ strings.length >= 2 ? strings : strings.first
127
+ end
128
+
129
+ # @note type can be an Array
130
+ # @return [Array<String>]
131
+ def types
132
+ Array(@options[:type])
133
+ end
134
+
135
+ # @return [false, nil, true]
136
+ def unique_flag
137
+ if @options.key?(:unique)
138
+ !!@options[:unique]
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,3 @@
1
+ module JsonWorld
2
+ VERSION = "0.0.1"
3
+ end
data/lib/json_world.rb ADDED
@@ -0,0 +1,2 @@
1
+ require "json_world/property_definable"
2
+ require "json_world/version"
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: json_world
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ryo Nakamura
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-06-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: json
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.9'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.9'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '='
74
+ - !ruby/object:Gem::Version
75
+ version: 3.2.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '='
81
+ - !ruby/object:Gem::Version
82
+ version: 3.2.0
83
+ description:
84
+ email:
85
+ - r7kamura@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".travis.yml"
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - json_world.gemspec
98
+ - lib/json_world.rb
99
+ - lib/json_world/json_encodable.rb
100
+ - lib/json_world/link_definition.rb
101
+ - lib/json_world/property_definable.rb
102
+ - lib/json_world/property_definition.rb
103
+ - lib/json_world/version.rb
104
+ homepage: https://github.com/r7kamura/json_world
105
+ licenses:
106
+ - MIT
107
+ metadata: {}
108
+ post_install_message:
109
+ rdoc_options: []
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ requirements: []
123
+ rubyforge_project:
124
+ rubygems_version: 2.4.5
125
+ signing_key:
126
+ specification_version: 4
127
+ summary: Provides DSL to define JSON Schema representation of your class.
128
+ test_files: []
129
+ has_rdoc: