praxis 2.0.pre.31 → 2.0.pre.33

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.travis.yml +4 -1
  4. data/Appraisals +11 -0
  5. data/CHANGELOG.md +144 -104
  6. data/Gemfile +6 -6
  7. data/bin/praxis +24 -1
  8. data/gemfiles/active_6.gemfile +16 -0
  9. data/gemfiles/active_6.gemfile.lock +199 -0
  10. data/gemfiles/active_7.gemfile +16 -0
  11. data/gemfiles/active_7.gemfile.lock +197 -0
  12. data/lib/praxis/action_definition/headers_dsl_compiler.rb +1 -1
  13. data/lib/praxis/application.rb +1 -1
  14. data/lib/praxis/blueprint.rb +25 -18
  15. data/lib/praxis/blueprint_attribute_group.rb +0 -2
  16. data/lib/praxis/controller.rb +4 -0
  17. data/lib/praxis/docs/open_api/operation_object.rb +9 -0
  18. data/lib/praxis/docs/open_api/paths_object.rb +2 -2
  19. data/lib/praxis/docs/open_api_generator.rb +51 -21
  20. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +4 -4
  21. data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +6 -12
  22. data/lib/praxis/extensions/pagination/active_record_pagination_handler.rb +2 -0
  23. data/lib/praxis/extensions/pagination/pagination_params.rb +2 -2
  24. data/lib/praxis/field_expander.rb +1 -1
  25. data/lib/praxis/mapper/active_model_compat.rb +4 -6
  26. data/lib/praxis/mapper/selector_generator.rb +1 -1
  27. data/lib/praxis/media_type_identifier.rb +4 -4
  28. data/lib/praxis/request.rb +1 -1
  29. data/lib/praxis/tasks/console.rb +3 -0
  30. data/lib/praxis/types/multipart_array.rb +3 -3
  31. data/lib/praxis/version.rb +1 -1
  32. data/praxis.gemspec +2 -4
  33. data/spec/praxis/application_spec.rb +11 -0
  34. data/spec/praxis/blueprint_spec.rb +307 -17
  35. data/spec/praxis/controller_spec.rb +9 -0
  36. data/spec/praxis/extensions/pagination/active_record_pagination_handler_spec.rb +28 -0
  37. data/spec/praxis/request_spec.rb +10 -0
  38. data/spec/support/spec_blueprints.rb +6 -4
  39. data/tasks/thor/model.rb +3 -1
  40. data/tasks/thor/scaffold.rb +35 -3
  41. data/tasks/thor/templates/generator/scaffold/design/endpoints/collection.rb +1 -0
  42. data/tasks/thor/templates/generator/scaffold/design/media_types/item.rb +1 -1
  43. data/tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb +11 -14
  44. data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +3 -7
  45. metadata +23 -38
@@ -0,0 +1,199 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ praxis (2.0.pre.32)
5
+ activesupport (>= 3)
6
+ attributor (>= 6.5)
7
+ mime (~> 0)
8
+ mustermann (>= 1.1)
9
+ rack (>= 1)
10
+ terminal-table (~> 1.4)
11
+ thor
12
+
13
+ GEM
14
+ remote: https://rubygems.org/
15
+ specs:
16
+ activemodel (6.1.7.3)
17
+ activesupport (= 6.1.7.3)
18
+ activerecord (6.1.7.3)
19
+ activemodel (= 6.1.7.3)
20
+ activesupport (= 6.1.7.3)
21
+ activesupport (6.1.7.3)
22
+ concurrent-ruby (~> 1.0, >= 1.0.2)
23
+ i18n (>= 1.6, < 2)
24
+ minitest (>= 5.1)
25
+ tzinfo (~> 2.0)
26
+ zeitwerk (~> 2.3)
27
+ appraisal (2.4.1)
28
+ bundler
29
+ rake
30
+ thor (>= 0.14.0)
31
+ ast (2.4.2)
32
+ attributor (6.5)
33
+ activesupport (>= 3)
34
+ hashie (~> 3)
35
+ randexp (~> 0)
36
+ binding_of_caller (1.0.0)
37
+ debug_inspector (>= 0.0.1)
38
+ builder (3.2.4)
39
+ byebug (11.1.3)
40
+ coderay (1.1.3)
41
+ concurrent-ruby (1.2.2)
42
+ coveralls (0.8.23)
43
+ json (>= 1.8, < 3)
44
+ simplecov (~> 0.16.1)
45
+ term-ansicolor (~> 1.3)
46
+ thor (>= 0.19.4, < 2.0)
47
+ tins (~> 1.6)
48
+ debug_inspector (1.1.0)
49
+ diff-lcs (1.5.0)
50
+ docile (1.4.0)
51
+ ffi (1.15.5)
52
+ formatador (1.1.0)
53
+ fuubar (2.5.1)
54
+ rspec-core (~> 3.0)
55
+ ruby-progressbar (~> 1.4)
56
+ guard (2.18.0)
57
+ formatador (>= 0.2.4)
58
+ listen (>= 2.7, < 4.0)
59
+ lumberjack (>= 1.0.12, < 2.0)
60
+ nenv (~> 0.1)
61
+ notiffany (~> 0.0)
62
+ pry (>= 0.13.0)
63
+ shellany (~> 0.0)
64
+ thor (>= 0.18.1)
65
+ guard-bundler (2.2.1)
66
+ bundler (>= 1.3.0, < 3)
67
+ guard (~> 2.2)
68
+ guard-compat (~> 1.1)
69
+ guard-compat (1.2.1)
70
+ guard-rspec (4.7.3)
71
+ guard (~> 2.1)
72
+ guard-compat (~> 1.1)
73
+ rspec (>= 2.99.0, < 4.0)
74
+ hashie (3.6.0)
75
+ i18n (1.13.0)
76
+ concurrent-ruby (~> 1.0)
77
+ json (2.6.3)
78
+ link_header (0.0.8)
79
+ listen (3.8.0)
80
+ rb-fsevent (~> 0.10, >= 0.10.3)
81
+ rb-inotify (~> 0.9, >= 0.9.10)
82
+ lumberjack (1.2.8)
83
+ method_source (1.0.0)
84
+ mime (0.4.4)
85
+ minitest (5.18.0)
86
+ mustermann (3.0.0)
87
+ ruby2_keywords (~> 0.0.1)
88
+ nenv (0.3.0)
89
+ notiffany (0.1.3)
90
+ nenv (~> 0.1)
91
+ shellany (~> 0.0)
92
+ oj (3.14.2)
93
+ parallel (1.23.0)
94
+ parser (3.2.2.1)
95
+ ast (~> 2.4.1)
96
+ parslet (2.0.0)
97
+ pry (0.14.2)
98
+ coderay (~> 1.1)
99
+ method_source (~> 1.0)
100
+ pry-byebug (3.10.1)
101
+ byebug (~> 11.0)
102
+ pry (>= 0.13, < 0.15)
103
+ pry-stack_explorer (0.6.1)
104
+ binding_of_caller (~> 1.0)
105
+ pry (~> 0.13)
106
+ rack (2.2.7)
107
+ rack-test (0.8.3)
108
+ rack (>= 1.0, < 3)
109
+ rainbow (3.1.1)
110
+ rake (13.0.6)
111
+ randexp (0.1.7)
112
+ rb-fsevent (0.11.2)
113
+ rb-inotify (0.10.1)
114
+ ffi (~> 1.0)
115
+ regexp_parser (2.8.0)
116
+ rexml (3.2.5)
117
+ rspec (3.12.0)
118
+ rspec-core (~> 3.12.0)
119
+ rspec-expectations (~> 3.12.0)
120
+ rspec-mocks (~> 3.12.0)
121
+ rspec-collection_matchers (1.2.0)
122
+ rspec-expectations (>= 2.99.0.beta1)
123
+ rspec-core (3.12.1)
124
+ rspec-support (~> 3.12.0)
125
+ rspec-expectations (3.12.2)
126
+ diff-lcs (>= 1.2.0, < 2.0)
127
+ rspec-support (~> 3.12.0)
128
+ rspec-its (1.3.0)
129
+ rspec-core (>= 3.0.0)
130
+ rspec-expectations (>= 3.0.0)
131
+ rspec-mocks (3.12.3)
132
+ diff-lcs (>= 1.2.0, < 2.0)
133
+ rspec-support (~> 3.12.0)
134
+ rspec-support (3.12.0)
135
+ rubocop (1.48.1)
136
+ json (~> 2.3)
137
+ parallel (~> 1.10)
138
+ parser (>= 3.2.0.0)
139
+ rainbow (>= 2.2.2, < 4.0)
140
+ regexp_parser (>= 1.8, < 3.0)
141
+ rexml (>= 3.2.5, < 4.0)
142
+ rubocop-ast (>= 1.26.0, < 2.0)
143
+ ruby-progressbar (~> 1.7)
144
+ unicode-display_width (>= 2.4.0, < 3.0)
145
+ rubocop-ast (1.28.0)
146
+ parser (>= 3.2.1.0)
147
+ ruby-progressbar (1.13.0)
148
+ ruby2_keywords (0.0.5)
149
+ sequel (5.55.0)
150
+ shellany (0.0.1)
151
+ simplecov (0.16.1)
152
+ docile (~> 1.1)
153
+ json (>= 1.8, < 3)
154
+ simplecov-html (~> 0.10.0)
155
+ simplecov-html (0.10.2)
156
+ sqlite3 (1.6.1-x86_64-darwin)
157
+ sync (0.5.0)
158
+ term-ansicolor (1.7.1)
159
+ tins (~> 1.0)
160
+ terminal-table (1.6.0)
161
+ thor (1.2.2)
162
+ tins (1.31.0)
163
+ sync
164
+ tzinfo (2.0.6)
165
+ concurrent-ruby (~> 1.0)
166
+ unicode-display_width (2.4.2)
167
+ zeitwerk (2.6.8)
168
+
169
+ PLATFORMS
170
+ x86_64-darwin-20
171
+
172
+ DEPENDENCIES
173
+ activerecord (> 4, < 7)
174
+ appraisal
175
+ builder
176
+ bundler
177
+ coveralls
178
+ fuubar (~> 2)
179
+ guard (~> 2)
180
+ guard-bundler (~> 2)
181
+ guard-rspec (~> 4)
182
+ link_header
183
+ oj
184
+ parslet
185
+ praxis!
186
+ pry
187
+ pry-byebug
188
+ pry-stack_explorer
189
+ rack-test (~> 0)
190
+ rake (>= 12.3.3)
191
+ rspec (~> 3)
192
+ rspec-collection_matchers (~> 1)
193
+ rspec-its (~> 1)
194
+ rubocop
195
+ sequel (~> 5)
196
+ sqlite3 (~> 1)
197
+
198
+ BUNDLED WITH
199
+ 2.4.12
@@ -0,0 +1,16 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "rubocop"
6
+ gem "activerecord", ">=7"
7
+ gem "sequel", "~> 5"
8
+
9
+ group :test do
10
+ gem "builder"
11
+ gem "link_header"
12
+ gem "oj"
13
+ gem "parslet"
14
+ end
15
+
16
+ gemspec path: "../"
@@ -0,0 +1,197 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ praxis (2.0.pre.32)
5
+ activesupport (>= 3)
6
+ attributor (>= 6.5)
7
+ mime (~> 0)
8
+ mustermann (>= 1.1)
9
+ rack (>= 1)
10
+ terminal-table (~> 1.4)
11
+ thor
12
+
13
+ GEM
14
+ remote: https://rubygems.org/
15
+ specs:
16
+ activemodel (7.0.4.2)
17
+ activesupport (= 7.0.4.2)
18
+ activerecord (7.0.4.2)
19
+ activemodel (= 7.0.4.2)
20
+ activesupport (= 7.0.4.2)
21
+ activesupport (7.0.4.2)
22
+ concurrent-ruby (~> 1.0, >= 1.0.2)
23
+ i18n (>= 1.6, < 2)
24
+ minitest (>= 5.1)
25
+ tzinfo (~> 2.0)
26
+ appraisal (2.4.1)
27
+ bundler
28
+ rake
29
+ thor (>= 0.14.0)
30
+ ast (2.4.2)
31
+ attributor (6.5)
32
+ activesupport (>= 3)
33
+ hashie (~> 3)
34
+ randexp (~> 0)
35
+ binding_of_caller (1.0.0)
36
+ debug_inspector (>= 0.0.1)
37
+ builder (3.2.4)
38
+ byebug (11.1.3)
39
+ coderay (1.1.3)
40
+ concurrent-ruby (1.2.2)
41
+ coveralls (0.8.23)
42
+ json (>= 1.8, < 3)
43
+ simplecov (~> 0.16.1)
44
+ term-ansicolor (~> 1.3)
45
+ thor (>= 0.19.4, < 2.0)
46
+ tins (~> 1.6)
47
+ debug_inspector (1.1.0)
48
+ diff-lcs (1.5.0)
49
+ docile (1.4.0)
50
+ ffi (1.15.5)
51
+ formatador (1.1.0)
52
+ fuubar (2.5.1)
53
+ rspec-core (~> 3.0)
54
+ ruby-progressbar (~> 1.4)
55
+ guard (2.18.0)
56
+ formatador (>= 0.2.4)
57
+ listen (>= 2.7, < 4.0)
58
+ lumberjack (>= 1.0.12, < 2.0)
59
+ nenv (~> 0.1)
60
+ notiffany (~> 0.0)
61
+ pry (>= 0.13.0)
62
+ shellany (~> 0.0)
63
+ thor (>= 0.18.1)
64
+ guard-bundler (2.2.1)
65
+ bundler (>= 1.3.0, < 3)
66
+ guard (~> 2.2)
67
+ guard-compat (~> 1.1)
68
+ guard-compat (1.2.1)
69
+ guard-rspec (4.7.3)
70
+ guard (~> 2.1)
71
+ guard-compat (~> 1.1)
72
+ rspec (>= 2.99.0, < 4.0)
73
+ hashie (3.6.0)
74
+ i18n (1.13.0)
75
+ concurrent-ruby (~> 1.0)
76
+ json (2.6.3)
77
+ link_header (0.0.8)
78
+ listen (3.8.0)
79
+ rb-fsevent (~> 0.10, >= 0.10.3)
80
+ rb-inotify (~> 0.9, >= 0.9.10)
81
+ lumberjack (1.2.8)
82
+ method_source (1.0.0)
83
+ mime (0.4.4)
84
+ minitest (5.18.0)
85
+ mustermann (3.0.0)
86
+ ruby2_keywords (~> 0.0.1)
87
+ nenv (0.3.0)
88
+ notiffany (0.1.3)
89
+ nenv (~> 0.1)
90
+ shellany (~> 0.0)
91
+ oj (3.14.2)
92
+ parallel (1.23.0)
93
+ parser (3.2.2.1)
94
+ ast (~> 2.4.1)
95
+ parslet (2.0.0)
96
+ pry (0.14.2)
97
+ coderay (~> 1.1)
98
+ method_source (~> 1.0)
99
+ pry-byebug (3.10.1)
100
+ byebug (~> 11.0)
101
+ pry (>= 0.13, < 0.15)
102
+ pry-stack_explorer (0.6.1)
103
+ binding_of_caller (~> 1.0)
104
+ pry (~> 0.13)
105
+ rack (2.2.7)
106
+ rack-test (0.8.3)
107
+ rack (>= 1.0, < 3)
108
+ rainbow (3.1.1)
109
+ rake (13.0.6)
110
+ randexp (0.1.7)
111
+ rb-fsevent (0.11.2)
112
+ rb-inotify (0.10.1)
113
+ ffi (~> 1.0)
114
+ regexp_parser (2.8.0)
115
+ rexml (3.2.5)
116
+ rspec (3.12.0)
117
+ rspec-core (~> 3.12.0)
118
+ rspec-expectations (~> 3.12.0)
119
+ rspec-mocks (~> 3.12.0)
120
+ rspec-collection_matchers (1.2.0)
121
+ rspec-expectations (>= 2.99.0.beta1)
122
+ rspec-core (3.12.1)
123
+ rspec-support (~> 3.12.0)
124
+ rspec-expectations (3.12.2)
125
+ diff-lcs (>= 1.2.0, < 2.0)
126
+ rspec-support (~> 3.12.0)
127
+ rspec-its (1.3.0)
128
+ rspec-core (>= 3.0.0)
129
+ rspec-expectations (>= 3.0.0)
130
+ rspec-mocks (3.12.3)
131
+ diff-lcs (>= 1.2.0, < 2.0)
132
+ rspec-support (~> 3.12.0)
133
+ rspec-support (3.12.0)
134
+ rubocop (1.48.1)
135
+ json (~> 2.3)
136
+ parallel (~> 1.10)
137
+ parser (>= 3.2.0.0)
138
+ rainbow (>= 2.2.2, < 4.0)
139
+ regexp_parser (>= 1.8, < 3.0)
140
+ rexml (>= 3.2.5, < 4.0)
141
+ rubocop-ast (>= 1.26.0, < 2.0)
142
+ ruby-progressbar (~> 1.7)
143
+ unicode-display_width (>= 2.4.0, < 3.0)
144
+ rubocop-ast (1.28.0)
145
+ parser (>= 3.2.1.0)
146
+ ruby-progressbar (1.13.0)
147
+ ruby2_keywords (0.0.5)
148
+ sequel (5.55.0)
149
+ shellany (0.0.1)
150
+ simplecov (0.16.1)
151
+ docile (~> 1.1)
152
+ json (>= 1.8, < 3)
153
+ simplecov-html (~> 0.10.0)
154
+ simplecov-html (0.10.2)
155
+ sqlite3 (1.6.1-x86_64-darwin)
156
+ sync (0.5.0)
157
+ term-ansicolor (1.7.1)
158
+ tins (~> 1.0)
159
+ terminal-table (1.6.0)
160
+ thor (1.2.2)
161
+ tins (1.31.0)
162
+ sync
163
+ tzinfo (2.0.6)
164
+ concurrent-ruby (~> 1.0)
165
+ unicode-display_width (2.4.2)
166
+
167
+ PLATFORMS
168
+ x86_64-darwin-20
169
+
170
+ DEPENDENCIES
171
+ activerecord (>= 7)
172
+ appraisal
173
+ builder
174
+ bundler
175
+ coveralls
176
+ fuubar (~> 2)
177
+ guard (~> 2)
178
+ guard-bundler (~> 2)
179
+ guard-rspec (~> 4)
180
+ link_header
181
+ oj
182
+ parslet
183
+ praxis!
184
+ pry
185
+ pry-byebug
186
+ pry-stack_explorer
187
+ rack-test (~> 0)
188
+ rake (>= 12.3.3)
189
+ rspec (~> 3)
190
+ rspec-collection_matchers (~> 1)
191
+ rspec-its (~> 1)
192
+ rubocop
193
+ sequel (~> 5)
194
+ sqlite3 (~> 1)
195
+
196
+ BUNDLED WITH
197
+ 2.4.12
@@ -18,7 +18,7 @@ module Praxis
18
18
  case val
19
19
  when Regexp
20
20
  options[:regexp] = val
21
- when String
21
+ when ::String
22
22
  options[:values] = [val]
23
23
  when nil
24
24
  # Defining the existence without any other options can only mean that it is required (otherwise it is a useless definition)
@@ -40,7 +40,7 @@ module Praxis
40
40
  end
41
41
 
42
42
  def inspect
43
- "<#{self.class}##{object_id} root: #{@root}>"
43
+ "#<#{self.class}##{object_id} @root=#{@root}>"
44
44
  end
45
45
 
46
46
  def setup(root: '.')
@@ -10,7 +10,8 @@ module Praxis
10
10
  # aren't invoked by just merely loading, and only really invoked when we've asked to render them
11
11
  # It takes the name of the group, and passes the attributes block that needs to be a subset of the MediaType where the group resides
12
12
  def group(name, **options, &block)
13
- attribute(name, Praxis::BlueprintAttributeGroup.for(target.options[:reference]), **options, &block)
13
+ # Pass the reference to the target type by default. But allow overriding it if needed
14
+ attribute(name, Praxis::BlueprintAttributeGroup.for(target), **{reference: target}.merge(options), &block)
14
15
  end
15
16
  end
16
17
 
@@ -62,7 +63,6 @@ module Praxis
62
63
 
63
64
  class << self
64
65
  attr_reader :attribute, :options
65
- attr_accessor :reference
66
66
  end
67
67
 
68
68
  def self.inherited(klass)
@@ -97,11 +97,7 @@ module Praxis
97
97
 
98
98
  def self.attributes(opts = {}, &block)
99
99
  if block_given?
100
- raise 'Redefining Blueprint attributes is not currently supported' if const_defined?(:Struct, false)
101
-
102
- raise "Reference mismatch in #{inspect}. Given :reference option #{opts[:reference].inspect}, while using #{reference.inspect}" if opts.key?(:reference) && opts[:reference] != reference
103
-
104
- opts[:reference] = (reference || self)
100
+ raise 'Redefining Blueprint attributes is not currently supported' if const_defined?(:InnerStruct, false)
105
101
 
106
102
  @options.merge!(opts.merge(dsl_compiler: DSLCompiler))
107
103
  @block = block
@@ -133,7 +129,7 @@ module Praxis
133
129
  new(value)
134
130
  end
135
131
  else
136
- if value.is_a?(domain_model) || value.is_a?(self::Struct)
132
+ if value.is_a?(domain_model) || value.is_a?(self::InnerStruct)
137
133
  # Wrap the value directly
138
134
  new(value)
139
135
  else
@@ -253,7 +249,7 @@ module Praxis
253
249
  @attribute = Attributor::Attribute.new(Attributor::Struct, @options, &@block)
254
250
  @block = nil
255
251
  @attribute.type.anonymous_type true
256
- const_set(:Struct, @attribute.type)
252
+ const_set(:InnerStruct, @attribute.type)
257
253
  end
258
254
 
259
255
  def self.define_readers!
@@ -288,6 +284,8 @@ module Praxis
288
284
  attributes.each do |name, attr|
289
285
  the_type = attr.type < Attributor::Collection ? attr.type.member_type : attr.type
290
286
  next if the_type < Blueprint
287
+ # TODO: Allow groups in the default fieldset?? or perhaps better to make people explicitly define them?
288
+ # next if (the_type < Blueprint && !(the_type < BlueprintAttributeGroup))
291
289
 
292
290
  # NOTE: we won't try to expand fields here, as we want to be lazy (and we're expanding)
293
291
  # every time a request comes in anyway. This could be an optimization we do at some point
@@ -333,23 +331,32 @@ module Praxis
333
331
  errors = []
334
332
  keys_provided = []
335
333
 
336
- self.class.attributes.each do |key, attribute|
337
- sub_context = self.class.generate_subcontext(context, key)
338
- value = _get_attr(key)
339
- keys_provided << key if @object.key?(key)
334
+ keys_provided = object.contents.keys
340
335
 
341
- next if value.respond_to?(:validating) && value.validating # really, it's a thing with sub-attributes
336
+ keys_provided.each do |key|
337
+ sub_context = self.class.generate_subcontext(context, key)
338
+ attribute = self.class.attributes[key]
342
339
 
343
- # Isn't this handled by the requirements validation? NO! we might want to combine
344
- errors.concat ["Attribute #{Attributor.humanize_context(sub_context)} is required."] if attribute.options[:required] && !@object.key?(key)
345
- if @object[key].nil?
346
- errors.concat ["Attribute #{Attributor.humanize_context(sub_context)} is not nullable."] if !Attributor::Attribute.nullable_attribute?(attribute.options) && @object.key?(key) # It is only nullable if there's an explicite null: true (undefined defaults to false)
340
+ if object.contents[key].nil?
341
+ errors.concat ["Attribute #{Attributor.humanize_context(sub_context)} is not nullable."] if !Attributor::Attribute.nullable_attribute?(attribute.options) && object.contents.key?(key) # It is only nullable if there's an explicite null: true (undefined defaults to false)
347
342
  # No need to validate the attribute further if the key wasn't passed...(or we would get nullable errors etc..cause the attribute has no
348
343
  # context if its containing key was even passed (and there might not be a containing key for a top level attribute anyways))
349
344
  else
345
+ value = _get_attr(key)
346
+ next if value.respond_to?(:validating) && value.validating # really, it's a thing with sub-attributes
347
+
350
348
  errors.concat attribute.validate(value, sub_context)
351
349
  end
352
350
  end
351
+
352
+ leftover = self.class.attributes.keys - keys_provided
353
+ leftover.each do |key|
354
+ sub_context = self.class.generate_subcontext(context, key)
355
+ attribute = self.class.attributes[key]
356
+
357
+ errors.concat ["Attribute #{Attributor.humanize_context(sub_context)} is required."] if attribute.options[:required]
358
+ end
359
+
353
360
  self.class.attribute.type.requirements.each do |requirement|
354
361
  validation_errors = requirement.validate(keys_provided, context)
355
362
  errors.concat(validation_errors) unless validation_errors.empty?
@@ -10,10 +10,8 @@ module Praxis
10
10
  def self.construct(attribute_definition, options = {})
11
11
  return self if attribute_definition.nil?
12
12
 
13
- reference_type = @media_type
14
13
  # Construct a group-derived class with the given mediatype as the reference
15
14
  ::Class.new(self) do
16
- @reference = reference_type
17
15
  attributes(**options, &attribute_definition)
18
16
  end
19
17
  end
@@ -35,6 +35,10 @@ module Praxis
35
35
  @response = response
36
36
  end
37
37
 
38
+ def inspect
39
+ "#<#{self.class}##{object_id} @request=#{@request.inspect}>"
40
+ end
41
+
38
42
  def definition
39
43
  self.class.definition
40
44
  end
@@ -31,6 +31,15 @@ module Praxis
31
31
  # security: [{}]
32
32
  # servers: [{}]
33
33
  }
34
+
35
+ # Handle versioning header/params for the action in a special way, by linking to the existing component
36
+ # spec that will be generated globally
37
+ api_info = ApiDefinition.instance.infos[action.endpoint_definition.version]
38
+ if (version_with = api_info.version_with)
39
+ all_parameters.push('$ref' => '#/components/parameters/ApiVersionHeader') if version_with.include?(:header)
40
+ all_parameters.push('$ref' => '#/components/parameters/ApiVersionParam') if version_with.include?(:params)
41
+ end
42
+
34
43
  h[:description] = action.description if action.description
35
44
  h[:tags] = all_tags.uniq unless all_tags.empty?
36
45
  h[:parameters] = all_parameters unless all_parameters.empty?
@@ -32,14 +32,14 @@ module Praxis
32
32
  id = resource.id
33
33
  # fill in the paths hash with a key for each path for each action/route
34
34
  resource.actions.each do |action_name, action|
35
- params_example = action.params ? action.params.example(nil) : nil
35
+ params_example = action.params ? action.params.example(nil) : nil
36
36
  url = ActionDefinition.url_description(route: action.route, params: action.params, params_example: params_example)
37
37
 
38
38
  verb = url[:verb].downcase
39
39
  templetized_path = OpenApiGenerator.templatize_url(url[:path])
40
40
  path_entry = paths[templetized_path]
41
41
  # Let's fill in verb stuff within the working hash
42
- raise "VERB #{_verb} already defined for #{id}!?!?!" if path_entry[verb]
42
+ raise "VERB #{verb} already defined for #{id}!?!?!" if path_entry[verb]
43
43
 
44
44
  action_uid = "action-#{action_name}-#{id}"
45
45
  # Add a tag matching the resource name (hoping all actions of a resource are grouped)
@@ -109,7 +109,15 @@ module Praxis
109
109
 
110
110
  info_object = OpenApi::InfoObject.new(version: version, api_definition_info: @infos[version])
111
111
  # We only support a server in Praxis ... so we'll use the base path
112
- server_object = OpenApi::ServerObject.new(url: @infos[version].base_path)
112
+ server_params = {}
113
+ if(server_info = @infos[version].server)
114
+ server_params[:url] = server_info[:url]
115
+ server_params[:variables] = server_info[:variables] if server_info[:variables]
116
+ else
117
+ server_params[:url] = @infos[version].base_path
118
+ end
119
+ server_params[:description] = server_info[:description] if server_info[:description]
120
+ server_object = OpenApi::ServerObject.new(**server_params)
113
121
 
114
122
  paths_object = OpenApi::PathsObject.new(resources: resources_by_version[version])
115
123
 
@@ -151,6 +159,28 @@ module Praxis
151
159
  schemas: component_schemas
152
160
  }
153
161
 
162
+ # Common params/headers for versioning (actions will link to them when appropriate, by name)
163
+ if (version_with = @infos[version].version_with)
164
+ common_params = {}
165
+ if version_with.include?(:header)
166
+ common_params['ApiVersionHeader'] = {
167
+ in: 'header',
168
+ name: 'X-Api-Version',
169
+ schema: { type: 'string', enum: [version]},
170
+ required: version_with.size == 1
171
+ }
172
+ end
173
+ if version_with.include?(:params)
174
+ common_params['ApiVersionParam'] = {
175
+ in: :query,
176
+ name: 'api_version',
177
+ schema: { type: 'string', enum: [version]},
178
+ required: version_with.size == 1
179
+ }
180
+ end
181
+ full_data[:components][:parameters] = common_params
182
+ end
183
+
154
184
  # REDOC specific grouping of sidebar
155
185
  resource_tags = { name: 'Resources', tags: tags_for_resources.map { |t| t[:name] } }
156
186
  schema_tags = { name: 'Models', tags: tags_for_mts.map { |t| t[:name] } }
@@ -170,26 +200,26 @@ module Praxis
170
200
  converted_full_data = JSON.parse(json_data) # So symbols disappear
171
201
  File.open("#{filename}.yml", 'w') { |f| f.write(YAML.dump(converted_full_data)) }
172
202
 
173
- html = <<-HTML
174
- <!doctype html>
175
- <html lang="en">
176
- <head>
177
- <meta charset="utf-8">
178
- <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
179
- <title>Elements in HTML</title>
180
-
181
- <script src="https://unpkg.com/@stoplight/elements/web-components.min.js"></script>
182
- <link rel="stylesheet" href="https://unpkg.com/@stoplight/elements/styles.min.css">
183
- </head>
184
- <body>
185
-
186
- <elements-api
187
- apiDescriptionUrl="http://localhost:9090/#{version_file}/openapi.json"
188
- router="hash"
189
- />
190
-
191
- </body>
192
- </html>
203
+ html = <<~HTML
204
+ <!doctype html>
205
+ <html lang="en">
206
+ <head>
207
+ <meta charset="utf-8">
208
+ <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
209
+ <title>Elements in HTML</title>
210
+ #{' '}
211
+ <script src="https://unpkg.com/@stoplight/elements/web-components.min.js"></script>
212
+ <link rel="stylesheet" href="https://unpkg.com/@stoplight/elements/styles.min.css">
213
+ </head>
214
+ <body>
215
+
216
+ <elements-api
217
+ apiDescriptionUrl="http://localhost:9090/#{version_file}/openapi.json"
218
+ router="hash"
219
+ />
220
+
221
+ </body>
222
+ </html>
193
223
  HTML
194
224
  html_file = File.join(doc_root_dir, version_file, 'index.html')
195
225
  File.write(html_file, html)