jsapi 1.4 → 2.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.
Files changed (113) hide show
  1. checksums.yaml +4 -4
  2. data/lib/jsapi/controller/actions/class_methods.rb +61 -0
  3. data/lib/jsapi/controller/actions.rb +13 -0
  4. data/lib/jsapi/controller/authentication/class_methods.rb +65 -0
  5. data/lib/jsapi/controller/authentication/credentials/api_key.rb +24 -0
  6. data/lib/jsapi/controller/authentication/credentials/http/base.rb +25 -0
  7. data/lib/jsapi/controller/authentication/credentials/http/basic.rb +34 -0
  8. data/lib/jsapi/controller/authentication/credentials/http/bearer.rb +30 -0
  9. data/lib/jsapi/controller/authentication/credentials/http.rb +5 -0
  10. data/lib/jsapi/controller/authentication/credentials.rb +38 -0
  11. data/lib/jsapi/controller/authentication.rb +70 -0
  12. data/lib/jsapi/controller/base.rb +5 -4
  13. data/lib/jsapi/controller/methods/callbacks/callback.rb +80 -0
  14. data/lib/jsapi/controller/methods/callbacks/class_methods.rb +62 -0
  15. data/lib/jsapi/controller/methods/callbacks.rb +54 -0
  16. data/lib/jsapi/controller/methods.rb +209 -116
  17. data/lib/jsapi/controller/parameters.rb +24 -20
  18. data/lib/jsapi/controller/response.rb +71 -39
  19. data/lib/jsapi/controller.rb +2 -1
  20. data/lib/jsapi/dsl/base.rb +38 -5
  21. data/lib/jsapi/dsl/class_methods.rb +2 -2
  22. data/lib/jsapi/dsl/definitions.rb +41 -27
  23. data/lib/jsapi/dsl/operation.rb +10 -109
  24. data/lib/jsapi/dsl/parameter.rb +1 -1
  25. data/lib/jsapi/dsl/path.rb +41 -18
  26. data/lib/jsapi/dsl/request_body.rb +1 -1
  27. data/lib/jsapi/dsl/response.rb +9 -6
  28. data/lib/jsapi/dsl/schema.rb +11 -5
  29. data/lib/jsapi/dsl/shared_operation_methods.rb +140 -0
  30. data/lib/jsapi/dsl.rb +1 -2
  31. data/lib/jsapi/json/array.rb +2 -2
  32. data/lib/jsapi/json/object.rb +6 -6
  33. data/lib/jsapi/json.rb +4 -6
  34. data/lib/jsapi/media/range.rb +102 -0
  35. data/lib/jsapi/media/type.rb +70 -0
  36. data/lib/jsapi/media/type_and_subtype.rb +38 -0
  37. data/lib/jsapi/media.rb +9 -0
  38. data/lib/jsapi/messages.rb +19 -0
  39. data/lib/jsapi/meta/callback/base.rb +63 -8
  40. data/lib/jsapi/meta/content.rb +59 -0
  41. data/lib/jsapi/meta/definitions.rb +299 -153
  42. data/lib/jsapi/meta/example/base.rb +41 -8
  43. data/lib/jsapi/meta/existence.rb +4 -2
  44. data/lib/jsapi/meta/header/base.rb +4 -2
  45. data/lib/jsapi/meta/info.rb +3 -1
  46. data/lib/jsapi/meta/license.rb +11 -5
  47. data/lib/jsapi/meta/model/attributes/class_methods.rb +150 -0
  48. data/lib/jsapi/meta/model/attributes/frozen_error.rb +16 -0
  49. data/lib/jsapi/meta/model/attributes/type_caster.rb +56 -0
  50. data/lib/jsapi/meta/model/attributes.rb +24 -118
  51. data/lib/jsapi/meta/model/base.rb +2 -5
  52. data/lib/jsapi/meta/model/reference.rb +46 -10
  53. data/lib/jsapi/meta/model/wrappable.rb +23 -0
  54. data/lib/jsapi/meta/model/wrapper.rb +26 -0
  55. data/lib/jsapi/meta/model.rb +2 -1
  56. data/lib/jsapi/meta/oauth_flow.rb +1 -1
  57. data/lib/jsapi/meta/openapi/extensions.rb +5 -6
  58. data/lib/jsapi/meta/openapi/version.rb +16 -4
  59. data/lib/jsapi/meta/operation.rb +177 -71
  60. data/lib/jsapi/meta/parameter/base.rb +10 -6
  61. data/lib/jsapi/meta/parameter/wrapper.rb +13 -0
  62. data/lib/jsapi/meta/parameter.rb +3 -0
  63. data/lib/jsapi/meta/path.rb +59 -13
  64. data/lib/jsapi/meta/pathname.rb +6 -3
  65. data/lib/jsapi/meta/property.rb +10 -0
  66. data/lib/jsapi/meta/request_body/base.rb +69 -32
  67. data/lib/jsapi/meta/request_body/wrapper.rb +13 -0
  68. data/lib/jsapi/meta/request_body.rb +3 -0
  69. data/lib/jsapi/meta/rescue_handler.rb +18 -17
  70. data/lib/jsapi/meta/response/base.rb +82 -58
  71. data/lib/jsapi/meta/response/reference.rb +11 -1
  72. data/lib/jsapi/meta/response/wrapper.rb +26 -0
  73. data/lib/jsapi/meta/response.rb +3 -0
  74. data/lib/jsapi/meta/schema/additional_properties.rb +8 -0
  75. data/lib/jsapi/meta/schema/array.rb +20 -8
  76. data/lib/jsapi/meta/schema/base.rb +10 -9
  77. data/lib/jsapi/meta/schema/boundary.rb +1 -0
  78. data/lib/jsapi/meta/schema/numeric.rb +26 -20
  79. data/lib/jsapi/meta/schema/object.rb +60 -44
  80. data/lib/jsapi/meta/schema/reference.rb +1 -8
  81. data/lib/jsapi/meta/schema/string.rb +12 -6
  82. data/lib/jsapi/meta/schema/wrapper.rb +31 -0
  83. data/lib/jsapi/meta/schema.rb +22 -9
  84. data/lib/jsapi/meta/security_requirement.rb +2 -2
  85. data/lib/jsapi/meta/security_scheme/api_key.rb +5 -2
  86. data/lib/jsapi/meta/security_scheme/base.rb +7 -5
  87. data/lib/jsapi/meta/security_scheme/http/basic.rb +5 -7
  88. data/lib/jsapi/meta/security_scheme/http/bearer.rb +5 -5
  89. data/lib/jsapi/meta/security_scheme/http/other.rb +1 -3
  90. data/lib/jsapi/meta/security_scheme/mutual_tls.rb +1 -3
  91. data/lib/jsapi/meta/security_scheme/oauth2.rb +18 -13
  92. data/lib/jsapi/meta/security_scheme/open_id_connect.rb +4 -4
  93. data/lib/jsapi/meta/security_scheme.rb +4 -4
  94. data/lib/jsapi/meta/server.rb +4 -2
  95. data/lib/jsapi/meta/tag.rb +9 -3
  96. data/lib/jsapi/meta.rb +2 -1
  97. data/lib/jsapi/model/base.rb +1 -1
  98. data/lib/jsapi/status/base.rb +35 -0
  99. data/lib/jsapi/status/code.rb +113 -0
  100. data/lib/jsapi/status/default.rb +16 -0
  101. data/lib/jsapi/status/range.rb +35 -0
  102. data/lib/jsapi/status.rb +37 -0
  103. data/lib/jsapi/version.rb +1 -1
  104. data/lib/jsapi.rb +3 -3
  105. metadata +36 -10
  106. data/lib/jsapi/controller/parameters_invalid.rb +0 -27
  107. data/lib/jsapi/dsl/callback.rb +0 -21
  108. data/lib/jsapi/dsl/error.rb +0 -36
  109. data/lib/jsapi/invalid_argument_error.rb +0 -12
  110. data/lib/jsapi/invalid_value_error.rb +0 -12
  111. data/lib/jsapi/invalid_value_helper.rb +0 -17
  112. data/lib/jsapi/meta/model/type_caster.rb +0 -50
  113. data/lib/jsapi/meta/schema/delegator.rb +0 -26
@@ -13,13 +13,18 @@ module Jsapi
13
13
  attribute :description, String
14
14
 
15
15
  ##
16
- # :attr: external
17
- # If true, +value+ is interpreted as a URI pointing to an external sample value.
18
- attribute :external, values: [true, false]
16
+ # :attr: external_value
17
+ # The URI of an external sample value.
18
+ attribute :external_value, String, accessors: %i[reader]
19
+
20
+ ##
21
+ # :attr: serialized_value
22
+ # The serialized form of the sample value.
23
+ attribute :serialized_value, accessors: %i[reader]
19
24
 
20
25
  ##
21
26
  # :attr: summary
22
- # The summary of the example.
27
+ # The short summary of the example.
23
28
  attribute :summary, String
24
29
 
25
30
  ##
@@ -27,12 +32,40 @@ module Jsapi
27
32
  # The sample value.
28
33
  attribute :value
29
34
 
35
+ def external_value=(value) # :nodoc:
36
+ try_modify_attribute!(:external_value) do
37
+ raise 'external value and serialized value are mutually exclusive' \
38
+ unless serialized_value.nil?
39
+
40
+ @external_value = value
41
+ end
42
+ end
43
+
44
+ def serialized_value=(value) # :nodoc:
45
+ try_modify_attribute!(:serialized_value) do
46
+ raise 'external value and serialized value are mutually exclusive' \
47
+ unless external_value.nil?
48
+
49
+ @serialized_value = value
50
+ end
51
+ end
52
+
30
53
  # Returns a hash representing the \OpenAPI example object.
31
- def to_openapi(*)
54
+ def to_openapi(version, *)
55
+ version = OpenAPI::Version.from(version)
56
+
32
57
  with_openapi_extensions(
33
- { summary: summary, description: description }.tap do |result|
34
- result[external? ? :externalValue : :value] = value
35
- end
58
+ summary: summary,
59
+ description: description,
60
+ **if version < OpenAPI::V3_2
61
+ { value: value }
62
+ else
63
+ {
64
+ dataValue: value,
65
+ serializedValue: serialized_value
66
+ }
67
+ end,
68
+ externalValue: external_value
36
69
  )
37
70
  end
38
71
  end
@@ -30,7 +30,9 @@ module Jsapi
30
30
  # or must be +false+.
31
31
  PRESENT = new(4)
32
32
 
33
- # Creates a new instance from +value+.
33
+ # Transforms +value+ to an instance of this class.
34
+ #
35
+ # Raises an +ArgumentError+ if +value+ could not be transformed.
34
36
  def self.from(value)
35
37
  return value if value.is_a?(Existence)
36
38
 
@@ -53,7 +55,7 @@ module Jsapi
53
55
  end
54
56
 
55
57
  def <=>(other) # :nodoc:
56
- level <=> other.level
58
+ level <=> other.level if other.is_a?(Existence)
57
59
  end
58
60
 
59
61
  def inspect # :nodoc:
@@ -23,7 +23,7 @@ module Jsapi
23
23
 
24
24
  ##
25
25
  # :attr: deprecated
26
- # Specifies whether or not the header is deprecated.
26
+ # Specifies whether the header is marked as deprecated.
27
27
  attribute :deprecated, values: [true, false]
28
28
 
29
29
  ##
@@ -70,7 +70,9 @@ module Jsapi
70
70
  description: description,
71
71
  deprecated: deprecated?.presence,
72
72
  schema: schema.to_openapi(version),
73
- examples: examples.transform_values(&:to_openapi).presence
73
+ examples: examples.transform_values do |example|
74
+ example.to_openapi(version)
75
+ end.presence
74
76
  }
75
77
  end
76
78
  )
@@ -23,7 +23,9 @@ module Jsapi
23
23
 
24
24
  ##
25
25
  # :attr: summary
26
- # The short summary of the API. Applies to \OpenAPI 3.1 and higher.
26
+ # The short summary of the API.
27
+ #
28
+ # Applies to \OpenAPI 3.1 and higher.
27
29
  attribute :summary, String
28
30
 
29
31
  ##
@@ -8,7 +8,9 @@ module Jsapi
8
8
 
9
9
  ##
10
10
  # :attr: identifier
11
- # The SDPX identifier of the license. Applies to \OpenAPI 3.1 and higher.
11
+ # The SDPX identifier of the license.
12
+ #
13
+ # Applies to \OpenAPI 3.1 and higher.
12
14
  attribute :identifier, String, accessors: %i[reader]
13
15
 
14
16
  ##
@@ -22,15 +24,19 @@ module Jsapi
22
24
  attribute :url, String, accessors: %i[reader]
23
25
 
24
26
  def identifier=(identifier) # :nodoc:
25
- raise 'identifier and url are mutually exclusive' if url.present?
27
+ try_modify_attribute!(:identifier) do
28
+ raise 'identifier and url are mutually exclusive' unless url.nil?
26
29
 
27
- @identifier = identifier
30
+ @identifier = identifier
31
+ end
28
32
  end
29
33
 
30
34
  def url=(url) # :nodoc:
31
- raise 'identifier and url are mutually exclusive' if identifier.present?
35
+ try_modify_attribute!(:url) do
36
+ raise 'identifier and url are mutually exclusive' unless identifier.nil?
32
37
 
33
- @url = url
38
+ @url = url
39
+ end
34
40
  end
35
41
 
36
42
  # Returns a hash representing the \OpenAPI license object.
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module Meta
5
+ module Model
6
+ module Attributes
7
+ module ClassMethods
8
+ DEFAULT_ARRAY = [].freeze
9
+ DEFAULT_HASH = {}.freeze
10
+
11
+ # Defines an attribute.
12
+ def attribute(name, type = Object,
13
+ accessors: %i[add reader writer],
14
+ default: nil,
15
+ default_key: nil,
16
+ keys: nil,
17
+ values: nil)
18
+
19
+ (@attribute_names ||= []) << name.to_sym
20
+
21
+ instance_variable_name = "@#{name}"
22
+
23
+ case type
24
+ when Array
25
+ # General default
26
+ default ||= DEFAULT_ARRAY
27
+
28
+ if accessors.include?(:add) || accessors.include?(:writer)
29
+ singular_name = name.to_s.singularize
30
+ add_method = "add_#{singular_name}"
31
+ type_caster = TypeCaster.new(type.first, values: values, name: singular_name)
32
+
33
+ # Attribute writer
34
+ if accessors.include?(:writer)
35
+ define_method("#{name}=") do |argument|
36
+ instance_variable_set(instance_variable_name, []).tap do
37
+ Array.wrap(argument).each { |element| send(add_method, element) }
38
+ end
39
+ end
40
+ end
41
+
42
+ # Add method
43
+ if accessors.include?(:add)
44
+ define_method(add_method) do |argument = nil|
45
+ try_modify_attribute!(name) do
46
+ type_caster.cast(argument).tap do |casted_argument|
47
+ if instance_variable_defined?(instance_variable_name)
48
+ instance_variable_get(instance_variable_name)
49
+ else
50
+ instance_variable_set(instance_variable_name, [])
51
+ end << casted_argument
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ when Hash
58
+ # General default
59
+ default ||= DEFAULT_HASH
60
+
61
+ singular_name = name.to_s.singularize
62
+ key_type, value_type = type.first
63
+ key_type_caster = TypeCaster.new(key_type, values: keys, name: 'key')
64
+
65
+ # Lookup method
66
+ if accessors.include?(:reader)
67
+ define_method(singular_name) do |key = nil|
68
+ key = default_key if key.to_s.empty?
69
+ send(name)[key_type_caster.cast(key)]
70
+ end
71
+ end
72
+
73
+ if accessors.include?(:add) || accessors.include?(:writer)
74
+ add_method = "add_#{singular_name}"
75
+ value_type_caster = TypeCaster.new(value_type, values: values)
76
+
77
+ # Attribute writer
78
+ if accessors.include?(:writer)
79
+ define_method("#{name}=") do |argument|
80
+ instance_variable_set(instance_variable_name, {}).tap do
81
+ Hash(argument).each { |key, value| send(add_method, key, value) }
82
+ end
83
+ end
84
+ end
85
+
86
+ # Add method
87
+ if accessors.include?(:add)
88
+ define_method(add_method) do |key_or_value, value = nil|
89
+ try_modify_attribute!(name) do
90
+ if value.nil? && default_key
91
+ key = default_key
92
+ value = key_or_value
93
+ else
94
+ key = key_or_value
95
+ key = default_key if key.to_s.empty?
96
+ end
97
+ raise ArgumentError, "key can't be blank" if key.to_s.empty?
98
+
99
+ if instance_variable_defined?(instance_variable_name)
100
+ instance_variable_get(instance_variable_name)
101
+ else
102
+ instance_variable_set(instance_variable_name, {})
103
+ end[key_type_caster.cast(key)] = value_type_caster.cast(value)
104
+ end
105
+ end
106
+ end
107
+ end
108
+ else
109
+ # Predicate method
110
+ if values == [true, false] && accessors.include?(:reader)
111
+ define_method("#{name}?") do
112
+ value = instance_variable_get(instance_variable_name)
113
+ value.nil? ? (default == true) || false : value
114
+ end
115
+ end
116
+
117
+ # Attribute writer
118
+ if accessors.include?(:writer)
119
+ type_caster = TypeCaster.new(type, values: values, name: name)
120
+
121
+ define_method("#{name}=") do |argument = nil|
122
+ try_modify_attribute!(name) do
123
+ instance_variable_set(instance_variable_name, type_caster.cast(argument))
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ # Attribute reader
130
+ if accessors.include?(:reader)
131
+ default = nil if default == :nil
132
+
133
+ define_method(name) do
134
+ value = instance_variable_get(instance_variable_name)
135
+ value.nil? ? default : value
136
+ end
137
+ end
138
+ end
139
+
140
+ def attribute_names
141
+ names = @attribute_names || []
142
+ return names unless superclass.respond_to?(:attribute_names)
143
+
144
+ superclass.attribute_names + names
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module Meta
5
+ module Model
6
+ module Attributes
7
+ # Raised when trying to modify a frozen attribute.
8
+ class FrozenError < StandardError
9
+ def initialize(target)
10
+ super("can't modify frozen #{target.class}")
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module Meta
5
+ module Model
6
+ module Attributes
7
+ class TypeCaster
8
+ STRING_CASTER = ->(arg) { arg&.to_s } # :nodoc:
9
+
10
+ SYMBOL_CASTER = ->(arg) { # :nodoc:
11
+ return if arg.nil?
12
+
13
+ arg = arg.to_s unless arg.respond_to?(:to_sym)
14
+ arg.to_sym
15
+ }
16
+
17
+ # Creates a new type caster for +klass+.
18
+ def initialize(klass = nil, name: 'value', values: nil)
19
+ klass = Object if klass.nil?
20
+ @caster =
21
+ case klass.name
22
+ when 'String'
23
+ STRING_CASTER
24
+ when 'Symbol'
25
+ SYMBOL_CASTER
26
+ else
27
+ ->(arg) {
28
+ return arg if arg.is_a?(klass)
29
+ return klass.from(arg) if klass.respond_to?(:from)
30
+ return klass.new if arg.nil?
31
+
32
+ klass.new(arg)
33
+ }
34
+ end
35
+ @values = values
36
+ @name = name
37
+ end
38
+
39
+ # Casts +value+.
40
+ #
41
+ # Raises an ArgumentError if the (casted) value is invalid.
42
+ def cast(value)
43
+ casted_value = @caster.call(value)
44
+ return casted_value unless @values&.exclude?(casted_value)
45
+
46
+ raise ArgumentError, Messages.invalid_value(
47
+ name: @name,
48
+ value: casted_value,
49
+ valid_values: @values
50
+ )
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,134 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'attributes/frozen_error'
4
+ require_relative 'attributes/type_caster'
5
+ require_relative 'attributes/class_methods'
6
+
3
7
  module Jsapi
4
8
  module Meta
5
9
  module Model
6
10
  module Attributes
7
- DEFAULT_ARRAY = [].freeze
8
- DEFAULT_HASH = {}.freeze
9
-
10
- # Defines an attribute.
11
- def attribute(name, type = Object,
12
- accessors: %i[add reader writer],
13
- default: nil,
14
- default_key: nil,
15
- keys: nil,
16
- values: nil)
17
-
18
- (@attribute_names ||= []) << name.to_sym
19
-
20
- instance_variable_name = "@#{name}"
21
-
22
- case type
23
- when Array
24
- # General default
25
- default ||= DEFAULT_ARRAY
26
-
27
- if accessors.include?(:add) || accessors.include?(:writer)
28
- singular_name = name.to_s.singularize
29
- add_method = "add_#{singular_name}"
30
- type_caster = TypeCaster.new(type.first, values: values, name: singular_name)
31
-
32
- # Attribute writer
33
- define_method("#{name}=") do |argument|
34
- instance_variable_set(instance_variable_name, []).tap do
35
- Array.wrap(argument).each { |element| send(add_method, element) }
36
- end
37
- end if accessors.include?(:writer)
38
-
39
- # Add method
40
- define_method(add_method) do |argument = nil|
41
- type_caster.cast(argument).tap do |casted_argument|
42
- if instance_variable_defined?(instance_variable_name)
43
- instance_variable_get(instance_variable_name)
44
- else
45
- instance_variable_set(instance_variable_name, [])
46
- end << casted_argument
47
- attribute_changed(name)
48
- end
49
- end if accessors.include?(:add)
50
- end
51
- when Hash
52
- # General default
53
- default ||= DEFAULT_HASH
54
-
55
- singular_name = name.to_s.singularize
56
- key_type, value_type = type.first
57
- key_type_caster = TypeCaster.new(key_type, values: keys, name: 'key')
58
-
59
- # Lookup method
60
- define_method(singular_name) do |key = nil|
61
- key = default_key if key.to_s.empty?
62
- send(name)[key_type_caster.cast(key)]
63
- end if accessors.include?(:reader)
64
-
65
- if accessors.include?(:add) || accessors.include?(:writer)
66
- add_method = "add_#{singular_name}"
67
- value_type_caster = TypeCaster.new(value_type, values: values)
68
-
69
- # Attribute writer
70
- define_method("#{name}=") do |argument|
71
- instance_variable_set(instance_variable_name, {}).tap do
72
- Hash(argument).each { |key, value| send(add_method, key, value) }
73
- end
74
- end if accessors.include?(:writer)
75
-
76
- # Add method
77
- define_method(add_method) do |key_or_value, value = nil|
78
- if value.nil? && default_key
79
- key = default_key
80
- value = key_or_value
81
- else
82
- key = key_or_value
83
- key = default_key if key.to_s.empty?
84
- end
85
- raise ArgumentError, "key can't be blank" if key.to_s.empty?
86
-
87
- casted_key = key_type_caster.cast(key)
88
- casted_value = value_type_caster.cast(value)
11
+ def self.included(base) # :nodoc:
12
+ base.extend(ClassMethods)
13
+ end
89
14
 
90
- if instance_variable_defined?(instance_variable_name)
91
- instance_variable_get(instance_variable_name)
92
- else
93
- instance_variable_set(instance_variable_name, {})
94
- end[casted_key] = casted_value
15
+ # Returns true when attributes are frozen.
16
+ def attributes_frozen?
17
+ @attributes_frozen == true
18
+ end
95
19
 
96
- attribute_changed(name)
97
- casted_value
98
- end if accessors.include?(:add)
99
- end
100
- else
101
- # Predicate method
102
- define_method("#{name}?") do
103
- value = instance_variable_get(instance_variable_name)
104
- value.nil? ? default || false : value
105
- end if values == [true, false] && accessors.include?(:reader)
20
+ # Freezes attributes.
21
+ def freeze_attributes
22
+ @attributes_frozen = true
23
+ end
106
24
 
107
- if accessors.include?(:writer)
108
- type_caster = TypeCaster.new(type, values: values, name: name)
25
+ protected
109
26
 
110
- # Attribute writer
111
- define_method("#{name}=") do |argument = nil|
112
- type_caster.cast(argument).tap do |casted_value|
113
- instance_variable_set(instance_variable_name, casted_value)
114
- attribute_changed(name)
115
- end
116
- end
117
- end
118
- end
27
+ # Invoked whenever an attribute has been changed.
28
+ def attribute_changed(name); end
119
29
 
120
- # Attribute reader
121
- define_method(name) do
122
- value = instance_variable_get(instance_variable_name)
123
- value.nil? ? default : value
124
- end if accessors.include?(:reader)
125
- end
30
+ private
126
31
 
127
- def attribute_names
128
- names = @attribute_names || []
129
- return names unless superclass.respond_to?(:attribute_names)
32
+ def try_modify_attribute!(name)
33
+ raise FrozenError.new(self) if attributes_frozen?
130
34
 
131
- superclass.attribute_names + names
35
+ result = yield if block_given?
36
+ attribute_changed(name)
37
+ result
132
38
  end
133
39
  end
134
40
  end
@@ -5,7 +5,7 @@ module Jsapi
5
5
  module Model
6
6
  # The base meta model class.
7
7
  class Base
8
- extend Attributes
8
+ include Attributes
9
9
 
10
10
  # Creates a new meta model.
11
11
  #
@@ -48,10 +48,7 @@ module Jsapi
48
48
  self
49
49
  end
50
50
 
51
- protected
52
-
53
- # Invoked whenever an attribute has been changed.
54
- def attribute_changed(name); end
51
+ alias resolve_lazily resolve
55
52
  end
56
53
  end
57
54
  end
@@ -5,19 +5,47 @@ module Jsapi
5
5
  module Model
6
6
  # The base reference class.
7
7
  class Reference < Base
8
- # Derrives the component type from the inner most module name.
9
- def self.component_type
10
- @component_type ||= name.split('::')[-2].underscore
8
+ class Resolver # :nodoc:
9
+ def initialize(reference, definitions)
10
+ @reference = reference
11
+ @definitions = definitions
12
+ end
13
+
14
+ protected
15
+
16
+ def respond_to_missing?(...)
17
+ @reference.respond_to?(...)
18
+ end
19
+
20
+ private
21
+
22
+ def method_missing(name, ...)
23
+ result = @reference.send(name, ...)
24
+ return result unless result.nil?
25
+
26
+ @reference
27
+ .resolve(@definitions, deep: false)
28
+ .resolve_lazily(@definitions)
29
+ .send(name, ...)
30
+ end
11
31
  end
12
32
 
13
- # Derrives the \OpenAPI component type from the inner most module name.
14
- def self.openapi_component_type
15
- @openapi_component_type ||= name.split('::')[-2].pluralize.camelize(:lower)
33
+ class << self
34
+ # Derrives the component type from the inner most module name.
35
+ def component_type
36
+ @component_type ||= name.split('::')[-2].underscore
37
+ end
38
+
39
+ # Derrives the \OpenAPI component type from the inner most module name.
40
+ def openapi_component_type
41
+ @openapi_component_type ||= name.split('::')[-2].pluralize.camelize(:lower)
42
+ end
16
43
  end
17
44
 
18
45
  ##
19
46
  # :attr: description
20
47
  # The description to be displayed instead of the description of the referred object.
48
+ #
21
49
  # Applies to \OpenAPI 3.1 and higher.
22
50
  attribute :description, String
23
51
 
@@ -29,6 +57,7 @@ module Jsapi
29
57
  ##
30
58
  # :attr: summary
31
59
  # The summary to be displayed instead of the summary of the referred object.
60
+ #
32
61
  # Applies to \OpenAPI 3.1 and higher.
33
62
  attribute :summary, String
34
63
 
@@ -37,14 +66,21 @@ module Jsapi
37
66
  true
38
67
  end
39
68
 
40
- # Resolves +ref+ by looking up the object with that name in +definitions+.
69
+ # Resolves the reference by looking up the referred object in +definitions+.
41
70
  #
42
- # Raises a ReferenceError if +ref+ could not be resolved.
43
- def resolve(definitions)
71
+ # Raises a ReferenceError if the reference could not be resolved.
72
+ def resolve(definitions, deep: true)
44
73
  object = definitions.send("find_#{self.class.component_type}", ref)
45
74
  raise ReferenceError, ref if object.nil?
46
75
 
47
- object.resolve(definitions)
76
+ deep ? object.resolve(definitions, deep: true) : object
77
+ end
78
+
79
+ # Lazily resolves the reference.
80
+ #
81
+ # Raises a ReferenceError if the reference could not be resolved.
82
+ def resolve_lazily(definitions)
83
+ Resolver.new(self, definitions)
48
84
  end
49
85
 
50
86
  # Returns a hash representing the \OpenAPI reference object.
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jsapi
4
+ module Meta
5
+ module Model
6
+ # Defines a +wrap+ class method to wrap an instance of the given class.
7
+ module Wrappable
8
+ def self.included(base) # :nodoc:
9
+ class << base
10
+ define_method(:wrap) do |model, definitions|
11
+ return if model.nil?
12
+
13
+ wrapper_class = "#{name}::Wrapper".constantize
14
+ return model if model.is_a?(wrapper_class)
15
+
16
+ wrapper_class.new(model, definitions)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end