praxis 2.0.pre.27 → 2.0.pre.29

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71228b4dd69f45eb28f8fec8782eef94fc24bdf6bb164a707d19cfade5091ac7
4
- data.tar.gz: 33e2fce254db25935827d67ccc394aa7b54039212e97eaa7aae89d9006e4d5fd
3
+ metadata.gz: e8f0514dd2a155733de0efe5586dd4b0409c70a9b385822b15e39934a7b83ea2
4
+ data.tar.gz: ff66561cdaadb84a9c97b83c044c423561b6506e10a31cf72d032dce73dcb50c
5
5
  SHA512:
6
- metadata.gz: 4f1bb58917fe5070e9433acdc2c2b1cbc0341eb832331f399a08bc2138fc5080397326ec2755f7e9452ec4774e931842699c4a4fa9455e0ef1231a6532f2b2d9
7
- data.tar.gz: 346dbe116bab1da666e6e38b5d8a7f3863c0dcd2c97736a762fc107eaf77d879b6e9f52e5569e5ee4e4e56207b14c87b21a0cd4e4e10a2f33a34177720c682e7
6
+ metadata.gz: 9bcb89e0426fb2e9e8ea51e7794a42f0a1ec6969fce436196da8c9d32697b232a02bddb0008400d51c6dc8b2d475e1122283da8644ed78bb448d759fbf4f2363
7
+ data.tar.gz: 5c0a71be2bb1043ccc4269ccfa258d01e93b66d4dfa2f69938f5a495d8a296a305d2b8051c84a6683343672a69cd01e20f717dc87f4bf5ca4049b587438a3194
data/CHANGELOG.md CHANGED
@@ -2,7 +2,14 @@
2
2
 
3
3
  ## next
4
4
 
5
- ## 2.0.pre.26
5
+ ## 2.0.pre.29
6
+ * Assorted set of fixes to generate cleaner and more compliant OpenApi documents.
7
+ * Mostly in the area of multipart generation, and requirements and nullability for OpenApi 3.0
8
+ ## 2.0.pre.28
9
+ * Enhance the mapper's Resource property to allow for a couple more powerful options using the `as:` keyword:
10
+ * `as: :self` will provide a way to map any further incoming fields on top of the already existing object. This is useful when we want to expose some properties for a resource, grouped within a sub structure, but that in reality they exist directly in the resource's underlying model (i.e., to organize the information of the model in a more structured/groupable way).
11
+ * `as: 'association1.association2'` allows us to traverse more than 1 association, and continue applying the incoming fields under that. This is commonly used when we want to expose a relationship on a resource, which is really coming from more than a single association level depth.
12
+ ## 2.0.pre.27
6
13
  * Introduce a new `as:` option for resource's `property`, to indicate that the underlying association method it is connected to, has a different name.
7
14
  * This also will create a delegation function for the property name, that instead of calling the underlying association on the record, and wrapping the result with a resource instance, it will simply call the aliased method name (which is likely gonna hit the autogenerated code for that properyty, unless we have overriden it)
8
15
  * With this change, the selector generator (i.e., the thing that looks at the incoming `fields` parameters and calculates which select and includes are necessary to query all the data we need), will be able to understand this aliasing cases, and properly pass along, and continue expanding any nested fields that are under the property name (before this, and further inner fields would be not included as soon as we hit a property that didn't have that direct association underneath).
@@ -45,15 +45,22 @@ module Praxis
45
45
 
46
46
  if example_payload
47
47
  examples_by_content_type = {}
48
- rendered_payload = example_payload.dump
48
+ # We don't need to provide top-level examples for a multipart payload...each part
49
+ # will provide its own example (and assembling a proper structured example for the whole
50
+ # body isn't necessarily trivial based on the spec)
51
+ # If we need to, someday, we can create the rendered_paylod by calling MultipartArray.self.dump_for_openapi
52
+ # and properly complete that code to generate the appropriate thing, including the encoding etc...
53
+ unless type < Praxis::Types::MultipartArray
54
+ rendered_payload = example_payload.dump
49
55
 
50
- example_handlers.each do |spec|
51
- content_type, handler_name = spec.first
52
- handler = Praxis::Application.instance.handlers[handler_name]
53
- # ReDoc is not happy to display json generated outputs when served as JSON...wtf?
54
- generated = handler.generate(rendered_payload)
55
- final = handler_name == 'json' ? JSON.parse(generated) : generated
56
- examples_by_content_type[content_type] = final
56
+ example_handlers.each do |spec|
57
+ content_type, handler_name = spec.first
58
+ handler = Praxis::Application.instance.handlers[handler_name]
59
+ # ReDoc is not happy to display json generated outputs when served as JSON...wtf?
60
+ generated = handler.generate(rendered_payload)
61
+ final = handler_name == 'json' ? JSON.parse(generated) : generated
62
+ examples_by_content_type[content_type] = final
63
+ end
57
64
  end
58
65
  end
59
66
 
@@ -23,13 +23,14 @@ module Praxis
23
23
  # so we'll show all the supported MTs...but repeating the schema
24
24
  # dumped_schema = SchemaObject.new(info: attribute).dump_schema
25
25
 
26
- example_handlers = if attribute.type < Praxis::Types::MultipartArray
27
- ident = MediaTypeIdentifier.load('multipart/form-data')
28
- [{ ident.to_s => 'plain' }] # Multipart content type, but with the plain renderer (so there's no modification)
29
- else
30
- # TODO: We could run it through other handlers I guess...if they're registered
31
- [{ 'application/json' => 'json' }]
32
- end
26
+ example_handlers = \
27
+ if attribute.type < Praxis::Types::MultipartArray
28
+ ident = MediaTypeIdentifier.load('multipart/form-data')
29
+ [{ ident.to_s => 'json' }] # Multipart content type
30
+ else
31
+ # TODO: We could run it through other handlers I guess...if they're registered
32
+ [{ 'application/json' => 'json' }]
33
+ end
33
34
 
34
35
  h[:content] = MediaTypeObject.create_content_attribute_helper(type: attribute.type,
35
36
  example_payload: attribute.example(nil),
@@ -31,7 +31,7 @@ module Praxis
31
31
 
32
32
  def dump_schema(shallow: false, allow_ref: false)
33
33
  # We will dump schemas for mediatypes by simply creating a reference to the components' section
34
- if type < Attributor::Container
34
+ if type < Attributor::Container && ! (type < Praxis::Types::MultipartArray)
35
35
  if (type < Praxis::Blueprint || type < Attributor::Model) && allow_ref && !type.anonymous?
36
36
  # TODO: Do we even need a description?
37
37
  h = @attribute_options[:description] ? { 'description' => @attribute_options[:description] } : {}
@@ -47,10 +47,11 @@ module Praxis
47
47
  props = type.attributes.transform_values.with_index do |definition, index|
48
48
  # if type has an attribute in its requirements all, then it should be marked as required here
49
49
  field_name = type.attributes.keys[index]
50
- definition.options.merge!(required: true) if required_attributes.include?(field_name)
51
50
  OpenApi::SchemaObject.new(info: definition).dump_schema(allow_ref: true, shallow: shallow)
52
51
  end
53
- h = { type: :object, properties: props } # TODO: Example?
52
+ h = { type: :object}
53
+ h[:properties] = props if props.presence
54
+ h[:required] = required_attributes unless required_attributes.empty?
54
55
  end
55
56
  else
56
57
  # OpenApi::SchemaObject.new(info:target).dump_schema(allow_ref: allow_ref, shallow: shallow)
@@ -61,12 +62,8 @@ module Praxis
61
62
 
62
63
  # Tag on OpenAPI specific requirements that aren't already added in the underlying JSON schema model
63
64
  # Nullable: (it seems we need to ensure there is a null option to the enum, if there is one)
64
- if @attribute_options[:null]
65
- h[:nullable] = @attribute_options[:null]
66
- h[:enum] = h[:enum] + [nil] if h[:enum] && !h[:enum].include?(nil)
67
- end
68
- # Required: Mostly for request bodies
69
- h[:required] = true if @attribute_options[:required]
65
+ is_nullable = @attribute_options[:null]
66
+ h[:nullable] = true if is_nullable
70
67
  h
71
68
 
72
69
  # # TODO: FIXME: return a generic object type if the passed info was weird.
@@ -117,10 +117,10 @@ module Praxis
117
117
  openapi: '3.0.2',
118
118
  info: info_object.dump,
119
119
  servers: [server_object.dump],
120
- paths: paths_object.dump
120
+ paths: paths_object.dump,
121
+ security: [] # NOTE: No security definitions in Praxis. Leave it empty, to not anger linters
121
122
  # responses: {}, #TODO!! what do we get here? the templates?...need to transform to "Responses Definitions Object"
122
123
  # securityDefinitions: {}, # NOTE: No security definitions in Praxis
123
- # security: [], # NOTE: No security definitions in Praxis
124
124
  }
125
125
 
126
126
  # Create the top level tags by:
@@ -68,7 +68,24 @@ module Praxis
68
68
  # Always add the underlying association if we're overriding the name...
69
69
  if (praxis_compat_model = resource.model&.respond_to?(:_praxis_associations))
70
70
  aliased_as = resource.properties[name][:as]
71
- add_association(aliased_as, fields) if resource.model._praxis_associations[aliased_as]
71
+ if aliased_as == :self
72
+ # Special keyword to add itself as the association, but still continue procesing the fields
73
+ # This is useful when we expose resource fields tucked inside another sub-struct, this way
74
+ # we can make sure that if the fields necessary to compute things inside the struct, they are preloaded
75
+ add(fields)
76
+ else
77
+ first, *rest = aliased_as.to_s.split('.').map(&:to_sym)
78
+
79
+ extended_fields = \
80
+ if rest.empty?
81
+ fields
82
+ else
83
+ rest.reverse.inject(fields) do |accum, prop|
84
+ { prop => accum }
85
+ end
86
+ end
87
+ add_association(first, extended_fields) if resource.model._praxis_associations[first]
88
+ end
72
89
  end
73
90
  dependencies&.each do |dependency|
74
91
  # To detect recursion, let's allow mapping depending fields to the same name of the property
@@ -254,5 +254,10 @@ module Praxis
254
254
  ensure
255
255
  self.content_type = original_content_type
256
256
  end
257
+
258
+ def self.dump_for_openapi(example_part)
259
+ # TODO: This needs to be structured as OpenAPI requires it
260
+ raise "dumping a part for open api not implemented yet"
261
+ end
257
262
  end
258
263
  end
@@ -197,7 +197,7 @@ module Praxis
197
197
  encoding[key_to_use]['headers'] = headers_attribute.as_json_schema(example: part_example.headers)
198
198
  end
199
199
 
200
- hash[:properties] = props
200
+ hash[:properties] = props if props.presence
201
201
  hash[:encoding] = encoding unless encoding.empty?
202
202
  end
203
203
  hash
@@ -370,6 +370,10 @@ module Praxis
370
370
  all_entities = parts.join("\r\n--#{boundary}\r\n")
371
371
  "--#{boundary}\r\n#{all_entities}\r\n--#{boundary}--\r\n"
372
372
  end
373
+
374
+ def self.dump_for_openapi(example)
375
+ example.map {|part| MultipartPart.dump_for_openapi(part)}
376
+ end
373
377
  end
374
378
  end
375
379
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Praxis
4
- VERSION = '2.0.pre.27'
4
+ VERSION = '2.0.pre.29'
5
5
  end
data/praxis.gemspec CHANGED
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.executables << 'praxis'
24
24
 
25
25
  spec.add_dependency 'activesupport', '>= 3'
26
- spec.add_dependency 'attributor', '>= 6.4'
26
+ spec.add_dependency 'attributor', '>= 6.5'
27
27
  spec.add_dependency 'mime', '~> 0'
28
28
  spec.add_dependency 'mustermann', '>=1.1'
29
29
  spec.add_dependency 'rack', '>= 1'
@@ -236,6 +236,55 @@ describe Praxis::Mapper::SelectorGenerator do
236
236
  end
237
237
  it_behaves_like 'a proper selector'
238
238
  end
239
+ context 'Deep aliased underlying associations also follows any nested fields at the end of the chain...' do
240
+ let(:fields) do
241
+ {
242
+ parent_id: true,
243
+ deep_aliased_association: {
244
+ name: true
245
+ }
246
+ }
247
+ end
248
+ let(:selectors) do
249
+ {
250
+ model: SimpleModel,
251
+ columns: %i[parent_id simple_name], # No added_column, as it does not follow the dotted reference as properties, just associations
252
+ tracks: {
253
+ parent: {
254
+ model: ParentModel,
255
+ columns: %i[id],
256
+ tracks: {
257
+ simple_children: {
258
+ model: SimpleModel,
259
+ columns: %i[parent_id simple_name]
260
+ }
261
+ }
262
+ }
263
+ }
264
+ }
265
+ end
266
+ it_behaves_like 'a proper selector'
267
+ end
268
+ context 'Using self for the underlying association: follows any nested fields skipping the association name and still applies dependencies' do
269
+ let(:fields) do
270
+ {
271
+ parent_id: true,
272
+ sub_struct: {
273
+ display_name: true
274
+ }
275
+ }
276
+ end
277
+ let(:selectors) do
278
+ {
279
+ model: SimpleModel,
280
+ # Parent_id is because we asked for it at the top
281
+ # display_name because we asked for it under sub_struct, but it is marked as :self
282
+ # alway_necessary_attribute because it is a dependency of sub_struct
283
+ columns: %i[parent_id display_name alway_necessary_attribute]
284
+ }
285
+ end
286
+ it_behaves_like 'a proper selector'
287
+ end
239
288
  end
240
289
 
241
290
  context 'string associations' do
@@ -154,6 +154,9 @@ class SimpleResource < BaseResource
154
154
  property :deep_nested_deps, dependencies: ['parent.simple_children.other_model.parent.display_name']
155
155
  property :aliased_association, as: :other_model, dependencies: [:name]
156
156
  property :overriden_aliased_association, as: :other_model, dependencies: [:name]
157
+ property :deep_aliased_association, as: 'parent.simple_children' , dependencies: [:name]
158
+
159
+ property :sub_struct, as: :self, dependencies: [:alway_necessary_attribute]
157
160
 
158
161
  before(:update!, :do_before_update)
159
162
  around(:update!, :do_around_update_nested)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: praxis
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.pre.27
4
+ version: 2.0.pre.29
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josep M. Blanquer
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-12-21 00:00:00.000000000 Z
12
+ date: 2023-01-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -31,14 +31,14 @@ dependencies:
31
31
  requirements:
32
32
  - - ">="
33
33
  - !ruby/object:Gem::Version
34
- version: '6.4'
34
+ version: '6.5'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - ">="
40
40
  - !ruby/object:Gem::Version
41
- version: '6.4'
41
+ version: '6.5'
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: mime
44
44
  requirement: !ruby/object:Gem::Requirement