jsapi 0.1.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.
Files changed (121) hide show
  1. checksums.yaml +7 -0
  2. data/lib/jsapi/controller/base.rb +21 -0
  3. data/lib/jsapi/controller/error_result.rb +21 -0
  4. data/lib/jsapi/controller/methods.rb +144 -0
  5. data/lib/jsapi/controller/parameters.rb +86 -0
  6. data/lib/jsapi/controller/parameters_invalid.rb +25 -0
  7. data/lib/jsapi/controller/response.rb +84 -0
  8. data/lib/jsapi/controller.rb +13 -0
  9. data/lib/jsapi/dsl/callbacks.rb +32 -0
  10. data/lib/jsapi/dsl/class_methods.rb +85 -0
  11. data/lib/jsapi/dsl/definitions.rb +102 -0
  12. data/lib/jsapi/dsl/error.rb +38 -0
  13. data/lib/jsapi/dsl/examples.rb +30 -0
  14. data/lib/jsapi/dsl/node.rb +62 -0
  15. data/lib/jsapi/dsl/openapi/callback.rb +23 -0
  16. data/lib/jsapi/dsl/openapi/root.rb +12 -0
  17. data/lib/jsapi/dsl/openapi.rb +4 -0
  18. data/lib/jsapi/dsl/operation.rb +118 -0
  19. data/lib/jsapi/dsl/parameter.rb +10 -0
  20. data/lib/jsapi/dsl/request_body.rb +10 -0
  21. data/lib/jsapi/dsl/response.rb +33 -0
  22. data/lib/jsapi/dsl/schema.rb +87 -0
  23. data/lib/jsapi/dsl.rb +24 -0
  24. data/lib/jsapi/json/array.rb +35 -0
  25. data/lib/jsapi/json/boolean.rb +17 -0
  26. data/lib/jsapi/json/integer.rb +15 -0
  27. data/lib/jsapi/json/null.rb +27 -0
  28. data/lib/jsapi/json/number.rb +15 -0
  29. data/lib/jsapi/json/object.rb +53 -0
  30. data/lib/jsapi/json/string.rb +29 -0
  31. data/lib/jsapi/json/value.rb +47 -0
  32. data/lib/jsapi/json.rb +41 -0
  33. data/lib/jsapi/meta/attributes/class_methods.rb +112 -0
  34. data/lib/jsapi/meta/attributes/type_caster.rb +48 -0
  35. data/lib/jsapi/meta/attributes.rb +4 -0
  36. data/lib/jsapi/meta/base.rb +41 -0
  37. data/lib/jsapi/meta/base_reference.rb +33 -0
  38. data/lib/jsapi/meta/definitions.rb +226 -0
  39. data/lib/jsapi/meta/example/model.rb +44 -0
  40. data/lib/jsapi/meta/example/reference.rb +15 -0
  41. data/lib/jsapi/meta/example.rb +19 -0
  42. data/lib/jsapi/meta/existence.rb +69 -0
  43. data/lib/jsapi/meta/invalid_argument_error.rb +11 -0
  44. data/lib/jsapi/meta/openapi/callback/model.rb +36 -0
  45. data/lib/jsapi/meta/openapi/callback/reference.rb +16 -0
  46. data/lib/jsapi/meta/openapi/callback.rb +21 -0
  47. data/lib/jsapi/meta/openapi/contact.rb +34 -0
  48. data/lib/jsapi/meta/openapi/external_documentation.rb +28 -0
  49. data/lib/jsapi/meta/openapi/info.rb +52 -0
  50. data/lib/jsapi/meta/openapi/license.rb +28 -0
  51. data/lib/jsapi/meta/openapi/link/model.rb +48 -0
  52. data/lib/jsapi/meta/openapi/link/reference.rb +16 -0
  53. data/lib/jsapi/meta/openapi/link.rb +21 -0
  54. data/lib/jsapi/meta/openapi/oauth_flow.rb +50 -0
  55. data/lib/jsapi/meta/openapi/root.rb +134 -0
  56. data/lib/jsapi/meta/openapi/security_requirement.rb +27 -0
  57. data/lib/jsapi/meta/openapi/security_scheme/api_key.rb +38 -0
  58. data/lib/jsapi/meta/openapi/security_scheme/base.rb +16 -0
  59. data/lib/jsapi/meta/openapi/security_scheme/http/basic.rb +31 -0
  60. data/lib/jsapi/meta/openapi/security_scheme/http/bearer.rb +37 -0
  61. data/lib/jsapi/meta/openapi/security_scheme/http/other.rb +37 -0
  62. data/lib/jsapi/meta/openapi/security_scheme/http.rb +31 -0
  63. data/lib/jsapi/meta/openapi/security_scheme/oauth2.rb +47 -0
  64. data/lib/jsapi/meta/openapi/security_scheme/open_id_connect.rb +33 -0
  65. data/lib/jsapi/meta/openapi/security_scheme.rb +51 -0
  66. data/lib/jsapi/meta/openapi/server.rb +34 -0
  67. data/lib/jsapi/meta/openapi/server_variable.rb +34 -0
  68. data/lib/jsapi/meta/openapi/tag.rb +34 -0
  69. data/lib/jsapi/meta/openapi/version.rb +41 -0
  70. data/lib/jsapi/meta/openapi.rb +16 -0
  71. data/lib/jsapi/meta/operation.rb +186 -0
  72. data/lib/jsapi/meta/parameter/model.rb +170 -0
  73. data/lib/jsapi/meta/parameter/reference.rb +30 -0
  74. data/lib/jsapi/meta/parameter.rb +19 -0
  75. data/lib/jsapi/meta/property.rb +62 -0
  76. data/lib/jsapi/meta/reference_error.rb +12 -0
  77. data/lib/jsapi/meta/request_body/model.rb +65 -0
  78. data/lib/jsapi/meta/request_body/reference.rb +14 -0
  79. data/lib/jsapi/meta/request_body.rb +19 -0
  80. data/lib/jsapi/meta/rescue_handler.rb +26 -0
  81. data/lib/jsapi/meta/response/model.rb +72 -0
  82. data/lib/jsapi/meta/response/reference.rb +17 -0
  83. data/lib/jsapi/meta/response.rb +19 -0
  84. data/lib/jsapi/meta/schema/array.rb +42 -0
  85. data/lib/jsapi/meta/schema/base.rb +146 -0
  86. data/lib/jsapi/meta/schema/boolean.rb +9 -0
  87. data/lib/jsapi/meta/schema/boundary.rb +37 -0
  88. data/lib/jsapi/meta/schema/conversion.rb +28 -0
  89. data/lib/jsapi/meta/schema/delegator.rb +26 -0
  90. data/lib/jsapi/meta/schema/discriminator.rb +36 -0
  91. data/lib/jsapi/meta/schema/integer.rb +9 -0
  92. data/lib/jsapi/meta/schema/number.rb +9 -0
  93. data/lib/jsapi/meta/schema/numeric.rb +56 -0
  94. data/lib/jsapi/meta/schema/object.rb +85 -0
  95. data/lib/jsapi/meta/schema/reference.rb +38 -0
  96. data/lib/jsapi/meta/schema/string.rb +58 -0
  97. data/lib/jsapi/meta/schema/validation/base.rb +29 -0
  98. data/lib/jsapi/meta/schema/validation/enum.rb +26 -0
  99. data/lib/jsapi/meta/schema/validation/max_items.rb +26 -0
  100. data/lib/jsapi/meta/schema/validation/max_length.rb +26 -0
  101. data/lib/jsapi/meta/schema/validation/maximum.rb +51 -0
  102. data/lib/jsapi/meta/schema/validation/min_items.rb +26 -0
  103. data/lib/jsapi/meta/schema/validation/min_length.rb +26 -0
  104. data/lib/jsapi/meta/schema/validation/minimum.rb +51 -0
  105. data/lib/jsapi/meta/schema/validation/multiple_of.rb +24 -0
  106. data/lib/jsapi/meta/schema/validation/pattern.rb +30 -0
  107. data/lib/jsapi/meta/schema/validation.rb +12 -0
  108. data/lib/jsapi/meta/schema.rb +61 -0
  109. data/lib/jsapi/meta.rb +23 -0
  110. data/lib/jsapi/model/attributes.rb +22 -0
  111. data/lib/jsapi/model/base.rb +34 -0
  112. data/lib/jsapi/model/error.rb +15 -0
  113. data/lib/jsapi/model/errors.rb +51 -0
  114. data/lib/jsapi/model/naming.rb +28 -0
  115. data/lib/jsapi/model/nestable.rb +37 -0
  116. data/lib/jsapi/model/nested_error.rb +54 -0
  117. data/lib/jsapi/model/validations.rb +27 -0
  118. data/lib/jsapi/model.rb +15 -0
  119. data/lib/jsapi/version.rb +8 -0
  120. data/lib/jsapi.rb +8 -0
  121. metadata +162 -0
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module DSL
5
+ class Node
6
+ def initialize(meta_model, &block)
7
+ @_meta_model = meta_model
8
+ instance_eval(&block) if block
9
+ end
10
+
11
+ def method_missing(*args, &block) # :nodoc:
12
+ _keyword(*args, &block)
13
+ end
14
+
15
+ def respond_to_missing?(*args) # :nodoc:
16
+ _keyword?(args.first)
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :_meta_model
22
+
23
+ def _define(*args, &block)
24
+ block.call
25
+ rescue Error => e
26
+ raise e.prepend_origin(args.compact.join(' '))
27
+ rescue StandardError => e
28
+ raise Error.new(e, args.compact.join(' ').presence)
29
+ end
30
+
31
+ def _eval(model, klass = Node, &block)
32
+ return unless block
33
+
34
+ if model.reference?
35
+ raise Error, 'reference cannot be specified together with a block'
36
+ end
37
+
38
+ klass.new(model, &block)
39
+ end
40
+
41
+ def _find_method(name)
42
+ ["#{name}=", "add_#{name}"].find do |method|
43
+ _meta_model.respond_to?(method)
44
+ end
45
+ end
46
+
47
+ def _keyword(name, *params, &block)
48
+ method = _find_method(name)
49
+ raise "unsupported method: #{name}" unless method
50
+
51
+ _define(name) do
52
+ value = _meta_model.public_send(method, *params)
53
+ _eval(value, &block)
54
+ end
55
+ end
56
+
57
+ def _keyword?(name)
58
+ _find_method(name).present?
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module DSL
5
+ module OpenAPI
6
+ # Used to specify details of a callback.
7
+ class Callback < Node
8
+
9
+ # Defines a callback operation.
10
+ #
11
+ # operation '{$request.query.foo}' do
12
+ # parameter 'bar', type: 'string'
13
+ # end
14
+ def operation(expression, **keywords, &block)
15
+ _define('operation', expression.inspect) do
16
+ operation_model = _meta_model.add_operation(expression, keywords)
17
+ Operation.new(operation_model, &block) if block
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module DSL
5
+ module OpenAPI
6
+ # Used to specify details of an OpenAPI object.
7
+ class Root < Node
8
+ include Callbacks
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'openapi/callback'
4
+ require_relative 'openapi/root'
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module DSL
5
+ # Used to specify details of an operation.
6
+ class Operation < Node
7
+ include Callbacks
8
+
9
+ # Overrides Object#method to handle +method+ as a keyword.
10
+ def method(method) # :nodoc:
11
+ _keyword(:method, method)
12
+ end
13
+
14
+ # Specifies the model class to access top-level parameters by.
15
+ #
16
+ # model Foo do
17
+ # def bar
18
+ # # ...
19
+ # end
20
+ # end
21
+ #
22
+ # +klass+ can be any subclass of Model::Base. If block is given, an
23
+ # anonymous class is created that inherits either from +klass+ or
24
+ # Model::Base.
25
+ def model(klass = nil, &block)
26
+ if block
27
+ klass = Class.new(klass || Model::Base)
28
+ klass.class_eval(&block)
29
+ end
30
+ _meta_model.model = klass
31
+ end
32
+
33
+ # Defines a parameter or refers a reusable parameter.
34
+ #
35
+ # # define a parameter
36
+ # parameter 'foo', type: 'string'
37
+ #
38
+ # # define a nested parameter
39
+ # parameter 'foo', type: 'object' do
40
+ # property 'bar', type: 'string'
41
+ # end
42
+ #
43
+ # # refer a reusable parameter
44
+ # parameter ref: 'foo'
45
+ #
46
+ # Refers the reusable parameter with the same name if neither any
47
+ # keywords nor a block is specified.
48
+ #
49
+ # parameter 'foo'
50
+ #
51
+ def parameter(name = nil, **keywords, &block)
52
+ _define('parameter', name&.inspect) do
53
+ name = keywords[:ref] if name.nil?
54
+ keywords = { ref: name } unless keywords.any? || block
55
+
56
+ parameter_model = _meta_model.add_parameter(name, keywords)
57
+ _eval(parameter_model, Parameter, &block)
58
+ end
59
+ end
60
+
61
+ # Defines the request body or refers a reusable request body.
62
+ #
63
+ # # define a request body
64
+ # request_body type: 'object' do
65
+ # property 'foo', type: 'string'
66
+ # end
67
+ #
68
+ # # refer a reusable request body
69
+ # request_body ref: 'foo'
70
+ #
71
+ # Refers the reusable request body with the same name if neither any
72
+ # keywords nor a block is specified.
73
+ #
74
+ # request_body 'foo'
75
+ #
76
+ def request_body(**keywords, &block)
77
+ _define('request body') do
78
+ _meta_model.request_body = keywords
79
+ _eval(_meta_model.request_body, RequestBody, &block)
80
+ end
81
+ end
82
+
83
+ # Defines a response or refers a reusable response.
84
+ #
85
+ # # define a response
86
+ # response 200, type: 'object' do
87
+ # property 'foo', type: 'string'
88
+ # end
89
+ #
90
+ # # refer a reusable response
91
+ # response 200, ref: 'foo'
92
+ #
93
+ # The default status is <code>"default"</code>.
94
+ #
95
+ # Refers the reusable response with the same name if neither any keywords
96
+ # nor a block is specified.
97
+ #
98
+ # response 'foo'
99
+ #
100
+ # Raises an Error if name is specified together with keywords or a block.
101
+ def response(status_or_name = nil, name = nil, **keywords, &block)
102
+ _define('response', status_or_name&.inspect) do
103
+ raise Error, 'name cannot be specified together with keywords ' \
104
+ 'or a block' if name && (keywords.any? || block)
105
+
106
+ if keywords.any? || block
107
+ status = status_or_name
108
+ else
109
+ status = status_or_name if name
110
+ keywords = { ref: name || status_or_name }
111
+ end
112
+ response_model = _meta_model.add_response(status, keywords)
113
+ _eval(response_model, Response, &block)
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module DSL
5
+ # Used to specify details of a parameter.
6
+ class Parameter < Schema
7
+ include Examples
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module DSL
5
+ # Used to specify details of a request body.
6
+ class RequestBody < Schema
7
+ include Examples
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module DSL
5
+ # Used to specify details of a response.
6
+ class Response < Schema
7
+ include Examples
8
+
9
+ # Defines a link or refers a reusable link object.
10
+ #
11
+ # # define a link
12
+ # link 'foo', operation_id: 'bar'
13
+ #
14
+ # # refer a reusable link
15
+ # link ref: 'foo'
16
+ #
17
+ # Refers the reusable link object with the same name if neither any
18
+ # keywords nor a block is specified.
19
+ #
20
+ # link 'foo'
21
+ #
22
+ def link(name = nil, **keywords, &block)
23
+ _define('link', name&.inspect) do
24
+ name = keywords[:ref] if name.nil?
25
+ keywords = { ref: name } unless keywords.any? || block
26
+
27
+ link_model = _meta_model.add_link(name, keywords)
28
+ Node.new(link_model, &block) if block
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module DSL
5
+ # Used to specify details of a schema.
6
+ class Schema < Node
7
+
8
+ # Includes all of the properties from +schemas+. Each argument must
9
+ # be the name of a schema defined by ClassMethods#api_schema or
10
+ # Definitions#schema.
11
+ def all_of(*schemas)
12
+ schemas.each { |schema| _meta_model.add_all_of({ schema: schema }) }
13
+ end
14
+
15
+ # Specifies a sample matching the schema.
16
+ def example(example)
17
+ _meta_model.add_example(example)
18
+ end
19
+
20
+ # Overrides Kernel#format to handle +format+ as a keyword.
21
+ def format(format) # :nodoc:
22
+ _keyword(:format, format)
23
+ end
24
+
25
+ # Specifies the kind of items that can be contained in an array.
26
+ #
27
+ # items do
28
+ # property 'foo', type: 'string'
29
+ # end
30
+ #
31
+ # Raises an Error if type is other than <code>"array"</code>.
32
+ def items(**keywords, &block)
33
+ unless _meta_model.respond_to?(:items=)
34
+ raise Error, "items isn't supported for '#{_meta_model.type}'"
35
+ end
36
+
37
+ _meta_model.items = keywords
38
+ Schema.new(_meta_model.items, &block) if block
39
+ end
40
+
41
+ # Specifies the model class to access nested object parameters by.
42
+ #
43
+ # model Foo do
44
+ # def bar
45
+ # # ...
46
+ # end
47
+ # end
48
+ #
49
+ # +klass+ can be any subclass of Model::Base. If block is given, an
50
+ # anonymous class is created that inherits either from +klass+ or
51
+ # Model::Base.
52
+ #
53
+ # Raises an Error if type is other than <code>"object"</code>.
54
+ def model(klass = nil, &block)
55
+ unless _meta_model.respond_to?(:model=)
56
+ raise Error, "model isn't supported for '#{_meta_model.type}'"
57
+ end
58
+
59
+ if block
60
+ klass = Class.new(klass || Model::Base)
61
+ klass.class_eval(&block)
62
+ end
63
+ _meta_model.model = klass
64
+ end
65
+
66
+ # Defines a property.
67
+ #
68
+ # property 'foo', type: 'string'
69
+ #
70
+ # property 'foo' do
71
+ # property 'bar', type: 'string'
72
+ # end
73
+ #
74
+ # Raises an Error if type is other than <code>"object"</code>.
75
+ def property(name, **keywords, &block)
76
+ _define('property', name.inspect) do
77
+ unless _meta_model.respond_to?(:add_property)
78
+ raise Error, "property isn't supported for '#{_meta_model.type}'"
79
+ end
80
+
81
+ property_model = _meta_model.add_property(name, keywords)
82
+ Schema.new(property_model, &block) if block
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
data/lib/jsapi/dsl.rb ADDED
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'dsl/callbacks'
4
+ require_relative 'dsl/examples'
5
+ require_relative 'dsl/error'
6
+ require_relative 'dsl/node'
7
+ require_relative 'dsl/schema'
8
+ require_relative 'dsl/parameter'
9
+ require_relative 'dsl/request_body'
10
+ require_relative 'dsl/response'
11
+ require_relative 'dsl/operation'
12
+ require_relative 'dsl/openapi'
13
+ require_relative 'dsl/definitions'
14
+ require_relative 'dsl/class_methods'
15
+
16
+ module Jsapi
17
+ # Provides class methods to define top-level API components.
18
+ # See ClassMethods for details.
19
+ module DSL
20
+ def self.included(base) # :nodoc:
21
+ base.extend(ClassMethods)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module JSON
5
+ # Represents a JSON array.
6
+ class Array < Value
7
+ def initialize(elements, schema, definitions)
8
+ super(schema)
9
+ @elements = Array(elements).map do |element|
10
+ JSON.wrap(element, schema.items, definitions)
11
+ end
12
+ end
13
+
14
+ # Returns +true+ if it contains no elements, +false+ otherwise.
15
+ def empty?
16
+ @elements.empty?
17
+ end
18
+
19
+ def inspect # :nodoc:
20
+ "#<#{self.class.name} [#{@elements.map(&:inspect).join(', ')}]>"
21
+ end
22
+
23
+ # See Value#validate.
24
+ def validate(errors)
25
+ return false unless super
26
+
27
+ @elements.map { |element| element.validate(errors) }.all?
28
+ end
29
+
30
+ def value
31
+ @value ||= @elements.map(&:value)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module JSON
5
+ # Represents +true+ or +false+.
6
+ class Boolean < Value
7
+ TRUTHY_VALUES = [true, 'True', 'true'].freeze
8
+
9
+ attr_reader :value
10
+
11
+ def initialize(value, schema)
12
+ super(schema)
13
+ @value = value.in?(TRUTHY_VALUES)
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module JSON
5
+ # Represents a JSON integer.
6
+ class Integer < Value
7
+ attr_reader :value
8
+
9
+ def initialize(value, schema)
10
+ super(schema)
11
+ @value = schema.convert(value.to_i)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module JSON
5
+ # Represents +null+.
6
+ class Null < Value
7
+
8
+ # Returns allways +true+.
9
+ def empty?
10
+ true
11
+ end
12
+
13
+ def inspect # :nodoc:
14
+ "#<#{self.class}>"
15
+ end
16
+
17
+ # Returns allways +true+.
18
+ def null?
19
+ true
20
+ end
21
+
22
+ def value
23
+ nil
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module JSON
5
+ # Represents a JSON number.
6
+ class Number < Value
7
+ attr_reader :value
8
+
9
+ def initialize(value, schema)
10
+ super(schema)
11
+ @value = schema.convert(value.to_f)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module JSON
5
+ # Represents a JSON object.
6
+ class Object < Value
7
+ include Model::Nestable
8
+
9
+ attr_reader :raw_attributes
10
+
11
+ def initialize(attributes, schema, definitions)
12
+ # Select inherriting schema on polymorphism
13
+ if (discriminator = schema.discriminator)
14
+ schema = discriminator.resolve(
15
+ attributes[discriminator.property_name],
16
+ definitions
17
+ )
18
+ end
19
+ # Wrap attribute values
20
+ @raw_attributes = schema.resolve_properties(:write, definitions)
21
+ .transform_values do |property|
22
+ JSON.wrap(attributes[property.name], property.schema, definitions)
23
+ end
24
+
25
+ super(schema)
26
+ end
27
+
28
+ # Returns +true+ if all attributes are empty, +false+ otherwise.
29
+ def empty?
30
+ @raw_attributes.values.all?(&:empty?)
31
+ end
32
+
33
+ def inspect # :nodoc:
34
+ "#<#{self.class.name} " \
35
+ "#{@raw_attributes.map { |k, v| "#{k}: #{v.inspect}" }.join(', ')}>"
36
+ end
37
+
38
+ # Returns a model to read attributes by.
39
+ def model
40
+ @model ||= (schema.model || Model::Base).new(self)
41
+ end
42
+
43
+ alias value model
44
+
45
+ # See Value#validate
46
+ def validate(errors)
47
+ return false unless super
48
+
49
+ validate_attributes(errors)
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module JSON
5
+ # Represents a JSON string.
6
+ class String < Value
7
+ attr_reader :value
8
+
9
+ def initialize(value, schema)
10
+ super(schema)
11
+ @value = schema.convert(
12
+ case schema.format
13
+ when 'date'
14
+ value.to_date
15
+ when 'date-time'
16
+ value.to_datetime
17
+ else
18
+ value.to_s
19
+ end
20
+ )
21
+ end
22
+
23
+ # See Value#empty?.
24
+ def empty?
25
+ value.blank?
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module JSON
5
+ # Represents a JSON value.
6
+ #
7
+ # Subclasses of Value must provide a +value+ method returning the outside
8
+ # representation of the JSON value.
9
+ class Value
10
+ attr_reader :schema
11
+
12
+ def initialize(schema)
13
+ @schema = schema
14
+ end
15
+
16
+ # Used by #validate to test whether or not it is empty.
17
+ # Returns +false+ by default.
18
+ def empty?
19
+ false
20
+ end
21
+
22
+ def inspect # :nodoc:
23
+ "#<#{self.class} #{value.inspect}>"
24
+ end
25
+
26
+ # Used by #validate to test whether or not it is +null+.
27
+ # Returns +false+ by default.
28
+ def null?
29
+ false
30
+ end
31
+
32
+ # Validates it against #schema. Returns +true+ if it is valid, +false+
33
+ # otherwise. Detected errors are added to +errors+.
34
+ def validate(errors)
35
+ unless schema.existence.reach?(self)
36
+ errors.add(:base, :blank)
37
+ return false
38
+ end
39
+ return true if null?
40
+
41
+ schema.validations.each_value.map do |validation|
42
+ validation.validate(value, errors)
43
+ end.all?
44
+ end
45
+ end
46
+ end
47
+ end
data/lib/jsapi/json.rb ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'json/value'
4
+ require_relative 'json/array'
5
+ require_relative 'json/boolean'
6
+ require_relative 'json/integer'
7
+ require_relative 'json/null'
8
+ require_relative 'json/number'
9
+ require_relative 'json/object'
10
+ require_relative 'json/string'
11
+
12
+ module Jsapi
13
+ # Provides a DOM for JSON values.
14
+ module JSON
15
+ class << self
16
+ def wrap(object, schema, definitions = nil)
17
+ schema = schema.resolve(definitions) unless definitions.nil?
18
+
19
+ object = schema.default if object.nil?
20
+ return Null.new(schema) if object.nil?
21
+
22
+ case schema.type
23
+ when 'array'
24
+ Array.new(object, schema, definitions)
25
+ when 'boolean'
26
+ Boolean.new(object, schema)
27
+ when 'integer'
28
+ Integer.new(object, schema)
29
+ when 'number'
30
+ Number.new(object, schema)
31
+ when 'object'
32
+ Object.new(object, schema, definitions)
33
+ when 'string'
34
+ String.new(object, schema)
35
+ else
36
+ raise "invalid type: #{schema.type}"
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end