json_world 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: