praxis 2.0.pre.33 → 2.0.pre.34
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.coveralls.yml +2 -0
- data/.gitignore +0 -5
- data/.travis.yml +2 -3
- data/Appraisals +6 -4
- data/CHANGELOG.md +10 -0
- data/Gemfile +6 -6
- data/gemfiles/active_6.gemfile +11 -9
- data/gemfiles/active_6.gemfile.lock +21 -15
- data/gemfiles/active_7.gemfile +11 -9
- data/gemfiles/active_7.gemfile.lock +21 -15
- data/lib/praxis/application.rb +3 -0
- data/lib/praxis/blueprint.rb +2 -1
- data/lib/praxis/docs/open_api/paths_object.rb +1 -1
- data/lib/praxis/docs/open_api/schema_object.rb +48 -27
- data/lib/praxis/docs/open_api_generator.rb +6 -6
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +0 -1
- data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +1 -1
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +5 -0
- data/lib/praxis/extensions/field_expansion.rb +7 -4
- data/lib/praxis/extensions/pagination/ordering_params.rb +0 -4
- data/lib/praxis/field_expander.rb +18 -3
- data/lib/praxis/handlers/xml_sample.rb +1 -1
- data/lib/praxis/mapper/resource.rb +1 -1
- data/lib/praxis/mapper/selector_generator.rb +1 -1
- data/lib/praxis/multipart/parser.rb +1 -1
- data/lib/praxis/types/splattable_string_array.rb +13 -0
- data/lib/praxis/version.rb +1 -1
- data/lib/praxis.rb +1 -1
- data/praxis.gemspec +3 -3
- data/spec/functional_library_spec.rb +91 -28
- data/spec/praxis/application_spec.rb +7 -2
- data/spec/praxis/blueprint_spec.rb +53 -55
- data/spec/praxis/controller_spec.rb +4 -1
- data/spec/praxis/extensions/field_expansion_spec.rb +2 -2
- data/spec/praxis/extensions/pagination/ordering_params_spec.rb +0 -2
- data/spec/praxis/field_expander_spec.rb +46 -0
- data/spec/spec_app/app/controllers/base_class.rb +12 -0
- data/spec/spec_app/app/resources/book.rb +9 -0
- data/spec/spec_app/config.ru +0 -1
- data/spec/spec_app/design/media_types/book.rb +2 -0
- data/spec/spec_app/design/resources/authors.rb +3 -7
- data/spec/support/spec_blueprints.rb +15 -0
- data/tasks/thor/templates/generator/example_app/Gemfile +1 -1
- data/tasks/thor/templates/generator/example_app/app/models/user.rb +0 -1
- data/tasks/thor/templates/generator/example_app/app/v1/concerns/controller_base.rb +0 -1
- data/tasks/thor/templates/generator/example_app/app/v1/concerns/href.rb +10 -4
- data/tasks/thor/templates/generator/example_app/app/v1/resources/user.rb +24 -0
- data/tasks/thor/templates/generator/example_app/design/api.rb +12 -1
- data/tasks/thor/templates/generator/scaffold/design/media_types/item.rb +1 -1
- data/tasks/thor/templates/generator/scaffold/implementation/controllers/collection.rb +1 -1
- data/tasks/thor/templates/generator/scaffold/implementation/resources/item.rb +1 -1
- metadata +18 -17
- data/.simplecov +0 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 907548a79ff385c164356d493d6ed808933534a37037d55fd3ffa446317c2e16
|
4
|
+
data.tar.gz: a1bc7931b86f65d6091a5ec835cc1b7ea7dafa21ac13a5cf8a0fcafdfd00ba3f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: df103d23ba77341af3586865c294e4681ab4d3d8dcb89f5b78dc2f29c56e4edc84b755195e35965a0193635b9726cbfcfe96e1d13ee26597b10bd7fdc13011f4
|
7
|
+
data.tar.gz: 927034f7434cd8a9e3f710d151d5fd04b92db0939d116c4951888c2c8837e8029db723e3c1aadd40efdb9930f05aada870c0c1291a07c126630c3d4c1784bcb4
|
data/.coveralls.yml
ADDED
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/Appraisals
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
appraise 'active-6' do
|
2
4
|
# Just for the query selector/filtering/ordering extensions etc...
|
3
5
|
gem 'activerecord', '> 4', '< 7'
|
4
6
|
gem 'sequel', '~> 5'
|
5
7
|
end
|
6
|
-
|
7
|
-
appraise
|
8
|
+
|
9
|
+
appraise 'active-7' do
|
8
10
|
# Just for the query selector/filtering/ordering extensions etc...
|
9
11
|
gem 'activerecord', '>=7'
|
10
12
|
gem 'sequel', '~> 5'
|
11
|
-
end
|
13
|
+
end
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,16 @@
|
|
1
1
|
# Praxis Changelog
|
2
2
|
|
3
3
|
## next
|
4
|
+
|
5
|
+
## 2.0.pre.34
|
6
|
+
- Allow filtering, ordering and pagination to freely use any attributes (potentially deep ones) when no block for the definition is provided. When continuing to define the allowed fields from within the block, those would still be enforced.
|
7
|
+
- Added the ability to easily control the hiding/displayability of MediaType attributes in responses.
|
8
|
+
- it introduces a :displayable custom attribute that accepts an array of opaque strings, which can represent some sort of 'privileges' required to have for it to be renderable.
|
9
|
+
- the expander tooling now, will use such `display_attribute?` method to decide if a given attribute needs to be expanded/displayed or not.
|
10
|
+
- The glue to all this requires defining a `display_attribute?` method in the controllers (with takes an array of string opaque privileges), which needs to return true/false to decide if a given attribute is displayable or now. This method will commonly be connected to the `authz` pieces of your app, and would look at the assigned 'privileges' of the currently logged in 'principal', and simply lookup if the required set of privileges received in the method, fall within the currently assigned ones.
|
11
|
+
- Cleanup in the OpenAPI generation for schemas, to more properly surface existing custom attributes into x-{name} attributes as well.
|
12
|
+
|
13
|
+
## 2.0.pre.33
|
4
14
|
- Better support for ordefing in newer versions of MySQL:
|
5
15
|
* Some versions will complain on an invalid query if you use an ORDER BY component that does not have the corresponding SELECT field
|
6
16
|
- Use the newest Attributor gem, which revamps and hardnens the struct/collection definion DSL, and provides much better error messages and safety. It also
|
data/Gemfile
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
source
|
3
|
+
source 'https://rubygems.org'
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
gem
|
6
|
+
gem 'rubocop'
|
7
7
|
|
8
8
|
group :test do
|
9
|
-
gem
|
9
|
+
gem 'builder'
|
10
10
|
|
11
|
-
gem
|
12
|
-
gem
|
13
|
-
gem
|
11
|
+
gem 'link_header'
|
12
|
+
gem 'oj'
|
13
|
+
gem 'parslet'
|
14
14
|
end
|
data/gemfiles/active_6.gemfile
CHANGED
@@ -1,16 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This file was generated by Appraisal
|
2
4
|
|
3
|
-
source
|
5
|
+
source 'https://rubygems.org'
|
4
6
|
|
5
|
-
gem
|
6
|
-
gem
|
7
|
-
gem
|
7
|
+
gem 'activerecord', '> 4', '< 7'
|
8
|
+
gem 'rubocop'
|
9
|
+
gem 'sequel', '~> 5'
|
8
10
|
|
9
11
|
group :test do
|
10
|
-
gem
|
11
|
-
gem
|
12
|
-
gem
|
13
|
-
gem
|
12
|
+
gem 'builder'
|
13
|
+
gem 'link_header'
|
14
|
+
gem 'oj'
|
15
|
+
gem 'parslet'
|
14
16
|
end
|
15
17
|
|
16
|
-
gemspec path:
|
18
|
+
gemspec path: '../'
|
@@ -1,9 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ..
|
3
3
|
specs:
|
4
|
-
praxis (2.0.pre.
|
4
|
+
praxis (2.0.pre.34)
|
5
5
|
activesupport (>= 3)
|
6
|
-
attributor (>=
|
6
|
+
attributor (>= 7.1)
|
7
7
|
mime (~> 0)
|
8
8
|
mustermann (>= 1.1)
|
9
9
|
rack (>= 1)
|
@@ -29,7 +29,7 @@ GEM
|
|
29
29
|
rake
|
30
30
|
thor (>= 0.14.0)
|
31
31
|
ast (2.4.2)
|
32
|
-
attributor (
|
32
|
+
attributor (7.1)
|
33
33
|
activesupport (>= 3)
|
34
34
|
hashie (~> 3)
|
35
35
|
randexp (~> 0)
|
@@ -39,12 +39,11 @@ GEM
|
|
39
39
|
byebug (11.1.3)
|
40
40
|
coderay (1.1.3)
|
41
41
|
concurrent-ruby (1.2.2)
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
tins (~> 1.6)
|
42
|
+
coveralls_reborn (0.27.0)
|
43
|
+
simplecov (~> 0.22.0)
|
44
|
+
term-ansicolor (~> 1.7)
|
45
|
+
thor (~> 1.2)
|
46
|
+
tins (~> 1.32)
|
48
47
|
debug_inspector (1.1.0)
|
49
48
|
diff-lcs (1.5.0)
|
50
49
|
docile (1.4.0)
|
@@ -82,6 +81,7 @@ GEM
|
|
82
81
|
lumberjack (1.2.8)
|
83
82
|
method_source (1.0.0)
|
84
83
|
mime (0.4.4)
|
84
|
+
mini_portile2 (2.8.2)
|
85
85
|
minitest (5.18.0)
|
86
86
|
mustermann (3.0.0)
|
87
87
|
ruby2_keywords (~> 0.0.1)
|
@@ -148,18 +148,22 @@ GEM
|
|
148
148
|
ruby2_keywords (0.0.5)
|
149
149
|
sequel (5.55.0)
|
150
150
|
shellany (0.0.1)
|
151
|
-
simplecov (0.
|
151
|
+
simplecov (0.22.0)
|
152
152
|
docile (~> 1.1)
|
153
|
-
|
154
|
-
|
155
|
-
simplecov-html (0.
|
153
|
+
simplecov-html (~> 0.11)
|
154
|
+
simplecov_json_formatter (~> 0.1)
|
155
|
+
simplecov-html (0.12.3)
|
156
|
+
simplecov_json_formatter (0.1.4)
|
157
|
+
sqlite3 (1.6.1)
|
158
|
+
mini_portile2 (~> 2.8.0)
|
156
159
|
sqlite3 (1.6.1-x86_64-darwin)
|
160
|
+
sqlite3 (1.6.1-x86_64-linux)
|
157
161
|
sync (0.5.0)
|
158
162
|
term-ansicolor (1.7.1)
|
159
163
|
tins (~> 1.0)
|
160
164
|
terminal-table (1.6.0)
|
161
165
|
thor (1.2.2)
|
162
|
-
tins (1.
|
166
|
+
tins (1.32.1)
|
163
167
|
sync
|
164
168
|
tzinfo (2.0.6)
|
165
169
|
concurrent-ruby (~> 1.0)
|
@@ -167,14 +171,16 @@ GEM
|
|
167
171
|
zeitwerk (2.6.8)
|
168
172
|
|
169
173
|
PLATFORMS
|
174
|
+
ruby
|
170
175
|
x86_64-darwin-20
|
176
|
+
x86_64-linux
|
171
177
|
|
172
178
|
DEPENDENCIES
|
173
179
|
activerecord (> 4, < 7)
|
174
180
|
appraisal
|
175
181
|
builder
|
176
182
|
bundler
|
177
|
-
|
183
|
+
coveralls_reborn (~> 0.27.0)
|
178
184
|
fuubar (~> 2)
|
179
185
|
guard (~> 2)
|
180
186
|
guard-bundler (~> 2)
|
data/gemfiles/active_7.gemfile
CHANGED
@@ -1,16 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# This file was generated by Appraisal
|
2
4
|
|
3
|
-
source
|
5
|
+
source 'https://rubygems.org'
|
4
6
|
|
5
|
-
gem
|
6
|
-
gem
|
7
|
-
gem
|
7
|
+
gem 'activerecord', '>=7'
|
8
|
+
gem 'rubocop'
|
9
|
+
gem 'sequel', '~> 5'
|
8
10
|
|
9
11
|
group :test do
|
10
|
-
gem
|
11
|
-
gem
|
12
|
-
gem
|
13
|
-
gem
|
12
|
+
gem 'builder'
|
13
|
+
gem 'link_header'
|
14
|
+
gem 'oj'
|
15
|
+
gem 'parslet'
|
14
16
|
end
|
15
17
|
|
16
|
-
gemspec path:
|
18
|
+
gemspec path: '../'
|
@@ -1,9 +1,9 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ..
|
3
3
|
specs:
|
4
|
-
praxis (2.0.pre.
|
4
|
+
praxis (2.0.pre.34)
|
5
5
|
activesupport (>= 3)
|
6
|
-
attributor (>=
|
6
|
+
attributor (>= 7.1)
|
7
7
|
mime (~> 0)
|
8
8
|
mustermann (>= 1.1)
|
9
9
|
rack (>= 1)
|
@@ -28,7 +28,7 @@ GEM
|
|
28
28
|
rake
|
29
29
|
thor (>= 0.14.0)
|
30
30
|
ast (2.4.2)
|
31
|
-
attributor (
|
31
|
+
attributor (7.1)
|
32
32
|
activesupport (>= 3)
|
33
33
|
hashie (~> 3)
|
34
34
|
randexp (~> 0)
|
@@ -38,12 +38,11 @@ GEM
|
|
38
38
|
byebug (11.1.3)
|
39
39
|
coderay (1.1.3)
|
40
40
|
concurrent-ruby (1.2.2)
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
tins (~> 1.6)
|
41
|
+
coveralls_reborn (0.27.0)
|
42
|
+
simplecov (~> 0.22.0)
|
43
|
+
term-ansicolor (~> 1.7)
|
44
|
+
thor (~> 1.2)
|
45
|
+
tins (~> 1.32)
|
47
46
|
debug_inspector (1.1.0)
|
48
47
|
diff-lcs (1.5.0)
|
49
48
|
docile (1.4.0)
|
@@ -81,6 +80,7 @@ GEM
|
|
81
80
|
lumberjack (1.2.8)
|
82
81
|
method_source (1.0.0)
|
83
82
|
mime (0.4.4)
|
83
|
+
mini_portile2 (2.8.2)
|
84
84
|
minitest (5.18.0)
|
85
85
|
mustermann (3.0.0)
|
86
86
|
ruby2_keywords (~> 0.0.1)
|
@@ -147,32 +147,38 @@ GEM
|
|
147
147
|
ruby2_keywords (0.0.5)
|
148
148
|
sequel (5.55.0)
|
149
149
|
shellany (0.0.1)
|
150
|
-
simplecov (0.
|
150
|
+
simplecov (0.22.0)
|
151
151
|
docile (~> 1.1)
|
152
|
-
|
153
|
-
|
154
|
-
simplecov-html (0.
|
152
|
+
simplecov-html (~> 0.11)
|
153
|
+
simplecov_json_formatter (~> 0.1)
|
154
|
+
simplecov-html (0.12.3)
|
155
|
+
simplecov_json_formatter (0.1.4)
|
156
|
+
sqlite3 (1.6.1)
|
157
|
+
mini_portile2 (~> 2.8.0)
|
155
158
|
sqlite3 (1.6.1-x86_64-darwin)
|
159
|
+
sqlite3 (1.6.1-x86_64-linux)
|
156
160
|
sync (0.5.0)
|
157
161
|
term-ansicolor (1.7.1)
|
158
162
|
tins (~> 1.0)
|
159
163
|
terminal-table (1.6.0)
|
160
164
|
thor (1.2.2)
|
161
|
-
tins (1.
|
165
|
+
tins (1.32.1)
|
162
166
|
sync
|
163
167
|
tzinfo (2.0.6)
|
164
168
|
concurrent-ruby (~> 1.0)
|
165
169
|
unicode-display_width (2.4.2)
|
166
170
|
|
167
171
|
PLATFORMS
|
172
|
+
ruby
|
168
173
|
x86_64-darwin-20
|
174
|
+
x86_64-linux
|
169
175
|
|
170
176
|
DEPENDENCIES
|
171
177
|
activerecord (>= 7)
|
172
178
|
appraisal
|
173
179
|
builder
|
174
180
|
bundler
|
175
|
-
|
181
|
+
coveralls_reborn (~> 0.27.0)
|
176
182
|
fuubar (~> 2)
|
177
183
|
guard (~> 2)
|
178
184
|
guard-bundler (~> 2)
|
data/lib/praxis/application.rb
CHANGED
@@ -5,6 +5,9 @@ require 'mustermann'
|
|
5
5
|
require 'logger'
|
6
6
|
|
7
7
|
module Praxis
|
8
|
+
# Setup the option early, since things like MediaTypes and EndpointDefinitions might need it
|
9
|
+
Attributor::Attribute.custom_option :displayable, Types::SplattableStringArray.of(String)
|
10
|
+
|
8
11
|
class Application
|
9
12
|
include Singleton
|
10
13
|
|
data/lib/praxis/blueprint.rb
CHANGED
@@ -11,7 +11,7 @@ module Praxis
|
|
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
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
|
+
attribute(name, Praxis::BlueprintAttributeGroup.for(target), **{ reference: target }.merge(options), &block)
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
@@ -284,6 +284,7 @@ module Praxis
|
|
284
284
|
attributes.each do |name, attr|
|
285
285
|
the_type = attr.type < Attributor::Collection ? attr.type.member_type : attr.type
|
286
286
|
next if the_type < Blueprint
|
287
|
+
|
287
288
|
# TODO: Allow groups in the default fieldset?? or perhaps better to make people explicitly define them?
|
288
289
|
# next if (the_type < Blueprint && !(the_type < BlueprintAttributeGroup))
|
289
290
|
|
@@ -32,7 +32,7 @@ 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
|
35
|
+
params_example = action.params&.example(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
|
@@ -5,68 +5,79 @@ module Praxis
|
|
5
5
|
module OpenApi
|
6
6
|
class SchemaObject
|
7
7
|
# https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#schema-object
|
8
|
-
attr_reader :type
|
8
|
+
attr_reader :type, :attribute
|
9
9
|
|
10
10
|
def initialize(info:)
|
11
|
-
@attribute_options = {}
|
12
|
-
|
13
11
|
# info could be an attribute ... or a type
|
12
|
+
# We will always try to work with the attribute if it is there, otherwise, we'll use type underneath
|
14
13
|
if info.is_a?(Attributor::Attribute)
|
15
14
|
@type = info.type
|
16
|
-
|
17
|
-
@attribute_options = info.options
|
15
|
+
@attribute = info
|
18
16
|
else
|
19
17
|
@type = info
|
20
18
|
end
|
21
19
|
|
22
|
-
# Mediatypes have the description method, lower types don't
|
23
|
-
@attribute_options[:description] = @type.description if @type.respond_to?(:description)
|
24
20
|
@collection = type.respond_to?(:member_type)
|
25
21
|
end
|
26
22
|
|
27
23
|
def dump_example
|
28
|
-
ex = type.example
|
24
|
+
ex = (attribute || type).example
|
29
25
|
ex.respond_to?(:dump) ? ex.dump : ex
|
30
26
|
end
|
31
27
|
|
32
28
|
def dump_schema(shallow: false, allow_ref: false)
|
29
|
+
important_options = %i[description null]
|
30
|
+
# Compile all options from the underlying tye and attribute (if any), translating them
|
31
|
+
# to OpenAPI schema options with the x- prefix for our custom ones
|
32
|
+
base_options = _slice_options(type, important_options)
|
33
|
+
base_options.merge! _slice_options(type, Attributor::Attribute.custom_options, prefix: 'x')
|
34
|
+
if attribute
|
35
|
+
base_options.merge! _slice_options(attribute, important_options, prefix: 'x')
|
36
|
+
base_options.merge! _slice_options(attribute, Attributor::Attribute.custom_options, prefix: 'x')
|
37
|
+
end
|
38
|
+
# Tag on OpenAPI specific requirements that aren't already added in the underlying JSON schema model
|
39
|
+
# Nullable: (it seems we need to ensure there is a null option to the enum, if there is one)
|
40
|
+
base_options[:nullable] = !base_options.delete(:null).nil? if base_options.key?(:null)
|
41
|
+
|
33
42
|
# We will dump schemas for mediatypes by simply creating a reference to the components' section
|
34
43
|
if type < Attributor::Container && !(type < Praxis::Types::MultipartArray)
|
35
44
|
if (type < Praxis::Blueprint || type < Attributor::Model) && allow_ref && !type.anonymous?
|
36
|
-
#
|
45
|
+
# NOTE: Technically OpenAPI/JSON schema support passing a description when pointing to a $ref (to override it)
|
37
46
|
# However, it seems that UI browsers like redoc or elements have bugs where if that's done, they get into a loop and crash
|
38
47
|
# so for now, we're gonna avoid overriding the description until that is solved
|
39
|
-
|
40
|
-
h = {}
|
48
|
+
base_options.delete(:description)
|
41
49
|
Praxis::Docs::OpenApiGenerator.instance.register_seen_component(type)
|
42
|
-
|
50
|
+
base_options.merge!('$ref' => "#/components/schemas/#{type.id}")
|
43
51
|
elsif @collection
|
44
52
|
items = OpenApi::SchemaObject.new(info: type.member_type).dump_schema(allow_ref: allow_ref, shallow: false)
|
45
|
-
|
46
|
-
h.merge!(type: 'array', items: items)
|
53
|
+
base_options.merge!(type: 'array', items: items)
|
47
54
|
else # Attributor::Struct, etc
|
48
|
-
|
49
|
-
|
50
|
-
|
55
|
+
# Requirements are reported at the outter schema layer, we we need to gather them from the description here
|
56
|
+
reqs = type < Praxis::Blueprint ? type.attribute.type.requirements : type.requirements
|
57
|
+
# Full requirements specified at the struct level that apply to all are considered required attributes
|
58
|
+
required_attributes = (reqs || []).filter { |r| r.type == :all }.map(&:attr_names).flatten.compact
|
59
|
+
# Also, if any inner attribute has the required: true option, that, obviously means required as well
|
60
|
+
sub_attributes = (attribute || type).attributes
|
61
|
+
direct_required = sub_attributes ? sub_attributes.select { |_, a| a.options[:required] == true }.keys : []
|
62
|
+
required_attributes.concat(direct_required)
|
63
|
+
required_attributes.uniq!
|
64
|
+
props = sub_attributes.transform_values do |definition|
|
51
65
|
OpenApi::SchemaObject.new(info: definition).dump_schema(allow_ref: true, shallow: shallow)
|
52
66
|
end
|
53
|
-
|
54
|
-
|
55
|
-
|
67
|
+
|
68
|
+
base_options.merge!(type: :object)
|
69
|
+
base_options[:properties] = props if props.presence
|
70
|
+
base_options[:required] = required_attributes unless required_attributes.empty?
|
56
71
|
end
|
57
72
|
else
|
73
|
+
desc = (attribute || type).as_json_schema(shallow: shallow, example: nil)
|
58
74
|
# OpenApi::SchemaObject.new(info:target).dump_schema(allow_ref: allow_ref, shallow: shallow)
|
59
75
|
# TODO...we need to make sure we can use refs in the underlying components after the first level...
|
60
76
|
# ... maybe we need to loop over the attributes if it's an object/struct?...
|
61
|
-
|
77
|
+
base_options.merge!(desc)
|
62
78
|
end
|
63
79
|
|
64
|
-
|
65
|
-
# Nullable: (it seems we need to ensure there is a null option to the enum, if there is one)
|
66
|
-
is_nullable = @attribute_options[:null]
|
67
|
-
h[:nullable] = true if is_nullable
|
68
|
-
h
|
69
|
-
|
80
|
+
base_options
|
70
81
|
# # TODO: FIXME: return a generic object type if the passed info was weird.
|
71
82
|
# return { type: :object } unless info
|
72
83
|
|
@@ -116,6 +127,16 @@ module Praxis
|
|
116
127
|
raise "Unknown praxis family type: #{praxis_type[:family]}"
|
117
128
|
end
|
118
129
|
end
|
130
|
+
|
131
|
+
def _slice_options(object, names, prefix: nil)
|
132
|
+
subset = object.options.slice(*names)
|
133
|
+
return subset if prefix.nil?
|
134
|
+
|
135
|
+
subset.transform_keys do |key|
|
136
|
+
"#{prefix}-#{key}".to_sym
|
137
|
+
end
|
138
|
+
subset
|
139
|
+
end
|
119
140
|
end
|
120
141
|
end
|
121
142
|
end
|
@@ -110,13 +110,13 @@ module Praxis
|
|
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
112
|
server_params = {}
|
113
|
-
if(server_info = @infos[version].server)
|
113
|
+
if (server_info = @infos[version].server)
|
114
114
|
server_params[:url] = server_info[:url]
|
115
115
|
server_params[:variables] = server_info[:variables] if server_info[:variables]
|
116
|
+
server_params[:description] = server_info[:description] if server_info[:description]
|
116
117
|
else
|
117
118
|
server_params[:url] = @infos[version].base_path
|
118
119
|
end
|
119
|
-
server_params[:description] = server_info[:description] if server_info[:description]
|
120
120
|
server_object = OpenApi::ServerObject.new(**server_params)
|
121
121
|
|
122
122
|
paths_object = OpenApi::PathsObject.new(resources: resources_by_version[version])
|
@@ -163,18 +163,18 @@ module Praxis
|
|
163
163
|
if (version_with = @infos[version].version_with)
|
164
164
|
common_params = {}
|
165
165
|
if version_with.include?(:header)
|
166
|
-
common_params['ApiVersionHeader'] = {
|
166
|
+
common_params['ApiVersionHeader'] = {
|
167
167
|
in: 'header',
|
168
168
|
name: 'X-Api-Version',
|
169
|
-
schema: { type: 'string', enum: [version]},
|
169
|
+
schema: { type: 'string', enum: [version] },
|
170
170
|
required: version_with.size == 1
|
171
171
|
}
|
172
172
|
end
|
173
173
|
if version_with.include?(:params)
|
174
|
-
common_params['ApiVersionParam'] = {
|
174
|
+
common_params['ApiVersionParam'] = {
|
175
175
|
in: :query,
|
176
176
|
name: 'api_version',
|
177
|
-
schema: { type: 'string', enum: [version]},
|
177
|
+
schema: { type: 'string', enum: [version] },
|
178
178
|
required: version_with.size == 1
|
179
179
|
}
|
180
180
|
end
|
@@ -229,6 +229,11 @@ module Praxis
|
|
229
229
|
end
|
230
230
|
|
231
231
|
def validate(_context = Attributor::DEFAULT_ROOT_CONTEXT)
|
232
|
+
# Treat a blank block definition for the filters, as a way to allow any valid filter, on any operator and fuz
|
233
|
+
# Obviously, the filter names need to be valid, but that's checked below.
|
234
|
+
# Also, in some circumstances, you'd need to make sure there is a filters_map entry for the ones that aren't directly translatable to query associations/columns
|
235
|
+
return [] if allowed_filters.blank? && allowed_leaves.blank?
|
236
|
+
|
232
237
|
parsed_array.each_with_object([]) do |item, errors|
|
233
238
|
attr_name = item[:name]
|
234
239
|
attr_filters = allowed_filters[attr_name]
|
@@ -10,17 +10,20 @@ module Praxis
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def expanded_fields
|
13
|
-
@expanded_fields ||=
|
13
|
+
@expanded_fields ||=
|
14
|
+
begin
|
15
|
+
expansion_filter = respond_to?(:display_attribute?) ? method(:display_attribute?) : nil
|
16
|
+
request.action.expanded_fields(request, media_type, expansion_filter)
|
17
|
+
end
|
14
18
|
end
|
15
19
|
|
16
20
|
module ActionDefinitionExtension
|
17
21
|
extend ActiveSupport::Concern
|
18
22
|
|
19
|
-
def expanded_fields(request, media_type)
|
23
|
+
def expanded_fields(request, media_type, expansion_filter)
|
20
24
|
uses_fields = params&.attributes&.key?(:fields)
|
21
25
|
fields = uses_fields ? request.params.fields.fields : true
|
22
|
-
|
23
|
-
Praxis::FieldExpander.expand(media_type, fields)
|
26
|
+
Praxis::FieldExpander.expand(media_type, fields, expansion_filter)
|
24
27
|
end
|
25
28
|
end
|
26
29
|
end
|
@@ -91,10 +91,6 @@ module Praxis
|
|
91
91
|
|
92
92
|
::Class.new(self) do
|
93
93
|
@media_type = media_type
|
94
|
-
if media_type
|
95
|
-
# By default all fields in the mediatype are allowed (but defining a DSL block will override it to more specific ones)
|
96
|
-
@fields_allowed = media_type.attributes.keys
|
97
|
-
end
|
98
94
|
# Default is to only enforce the allowed fields in the first ordering position (the one typicall uses an index if there)
|
99
95
|
@enforce_all = OrderingParams.enforce_all_fields
|
100
96
|
end
|
@@ -2,19 +2,27 @@
|
|
2
2
|
|
3
3
|
module Praxis
|
4
4
|
class FieldExpander
|
5
|
-
def self.expand(object, fields = true)
|
6
|
-
|
5
|
+
def self.expand(object, fields = true, displayable_filter = nil)
|
6
|
+
# check the displayability of the whole type/attr object at the top as well, not just the inner ones
|
7
|
+
if (privs = object.options[:displayable])
|
8
|
+
raise 'Attempting to expand fields for a type that uses :displayable, but the system (or at least this controller) does not have a displayable_filter setup.' unless privs && displayable_filter
|
9
|
+
return {} unless displayable_filter.call(Array(privs))
|
10
|
+
|
11
|
+
end
|
12
|
+
new(displayable_filter: displayable_filter).expand(object, fields)
|
7
13
|
end
|
8
14
|
|
9
15
|
attr_reader :stack, :history
|
10
16
|
|
11
|
-
|
17
|
+
# displayable_filter is a proc that takes a set of strings (representing privileges of sort) and returns true if it should be displayed
|
18
|
+
def initialize(displayable_filter: nil)
|
12
19
|
@stack = Hash.new do |hash, key|
|
13
20
|
hash[key] = Set.new
|
14
21
|
end
|
15
22
|
@history = Hash.new do |hash, key|
|
16
23
|
hash[key] = {}
|
17
24
|
end
|
25
|
+
@displayable_filter = displayable_filter
|
18
26
|
end
|
19
27
|
|
20
28
|
def expand(object, fields = true)
|
@@ -50,6 +58,13 @@ module Praxis
|
|
50
58
|
end
|
51
59
|
|
52
60
|
attributes.each_with_object({}) do |(name, dumpable), hash|
|
61
|
+
# Filter out attributes that are not displayable (if a filter is provided and there is a displayable option)
|
62
|
+
if (privs = dumpable.options[:displayable])
|
63
|
+
raise "Found field named #{name} using :displayable, but the system (or at least this controller) does not have a displayable_filter setup." unless privs && @displayable_filter
|
64
|
+
next unless @displayable_filter.call(Array(privs))
|
65
|
+
|
66
|
+
end
|
67
|
+
|
53
68
|
sub_fields = case fields
|
54
69
|
when true
|
55
70
|
true
|