apia-open_api 0.1.5 → 0.1.6

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d7c3d54619c699cf9edca73ebc76e68819c2656f0259adbd6d5e411d4e415ca6
4
- data.tar.gz: f31e758e511dd57af96896cd9ca956e6455c29747fc8389d5bda59a6c5938f3d
3
+ metadata.gz: 6621197f4a9535afae13e2a83de1fc5993290f379660e347afe989e94f3179ba
4
+ data.tar.gz: a932546d15d8302931a4c55d1fa4fa1e088b4f74ee7b227d0817de5f7b3f6dbb
5
5
  SHA512:
6
- metadata.gz: 1f96b6eca24cc4d97d4e248892adf62bce141c4d4cce4697c8141cd1b844465d7ce9ef33036daddee81bf908dd41b4cc1bcad2b4436bc2cd1aef431e8fba8602
7
- data.tar.gz: 1754bd390af23a0c88658c9a58b7c367f1743df4021a2f47c186720b3b3ff8235be743adaf62d0a3613502ae22267264f6aea71ae44ab716ba5ca8b86257345d
6
+ metadata.gz: d5bb084664b9a2729f8b0fb8408c8cd66eb6b8a637255582aba4c01cf5867eb2b88b5de5d1baba1eb4ebbece5f8d7353a7bedcbe68abe75036c7be9578a1a5c3
7
+ data.tar.gz: b99e2cc62ea871c657cbe523e1680d78f3341919b0a726d0fd6548635173a2064027c5c927d1a246be3120a508366e4627e4351c0040951d30a84694316fda3f
data/Gemfile CHANGED
@@ -11,6 +11,7 @@ gem "rspec", "~> 3.0"
11
11
  gem "rubocop", "~> 1.21"
12
12
 
13
13
  group :test do
14
+ gem "openapi3_parser"
14
15
  gem "pry"
15
16
  gem "simplecov", require: false
16
17
  end
@@ -52,17 +52,26 @@ module Apia
52
52
  schema
53
53
  end
54
54
 
55
- def generate_schema_ref(definition, id: nil, **schema_opts)
55
+ def generate_schema_ref(definition, id: nil, sibling_props: false, **schema_opts)
56
56
  id ||= generate_id_from_definition(definition.type.klass.definition)
57
57
  success = add_to_components_schemas(definition, id, **schema_opts)
58
58
 
59
- if success
59
+ # sibling_props indicates we want to allow sibling properties (typically setting nullable: true)
60
+ # In OpenAPI 3.0 sibling properties are not allowed for $refs (but are allowed in 3.1)
61
+ # Using allOf is a workaround to allow us to set a ref as `nullable` in OpenAPI 3.0
62
+ if success && sibling_props
63
+ {
64
+ allOf: [{ "$ref": "#/components/schemas/#{id}" }]
65
+ }
66
+ elsif success
60
67
  { "$ref": "#/components/schemas/#{id}" }
61
68
  else # no properties were defined, so just declare an object with unknown properties
62
69
  { type: "object" }
63
70
  end
64
71
  end
65
72
 
73
+ # Converts the definition id to a short version:
74
+ # e.g. CoreAPI/Objects/TimeZone => TimeZone
66
75
  def generate_id_from_definition(definition)
67
76
  definition.id.split("/").last
68
77
  end
@@ -98,15 +98,15 @@ module Apia
98
98
  else
99
99
  # We assume the partially selected attributes must be present in all of the polymorph options
100
100
  # and that each option returns the same data type for that attribute.
101
- # The same 'allOf workaround' is used here as for objects and enums below.
102
101
  ref = generate_schema_ref(
103
102
  field.type.klass.definition.options.values.first,
104
103
  id: generate_field_id(field_name),
104
+ sibling_props: field.description.present? || field.null?,
105
105
  endpoint: @endpoint,
106
106
  path: [field]
107
107
  )
108
108
 
109
- properties[field_name] = { allOf: [ref] }
109
+ properties[field_name] = ref
110
110
  end
111
111
  properties[field_name][:description] = field.description if field.description.present?
112
112
  end
@@ -135,28 +135,32 @@ module Apia
135
135
  properties[field_name][:description] = field.description if field.description.present?
136
136
  end
137
137
 
138
- # Using allOf is a 'workaround' so that we can include a description for the field
139
- # In OpenAPI 3.0 sibling properties are not allowed for $refs (but are allowed in 3.1)
140
138
  # We don't want to put the description on the $ref itself because the description is
141
139
  # specific to the endpoint and not necessarily applicable to all uses of the $ref.
142
140
  def build_properties_for_object_or_enum(field_name, field, properties)
143
- properties[field_name] = {}
144
- properties[field_name][:description] = field.description if field.description.present?
141
+ sibling_props = field.description.present? || field.null?
145
142
  if field_includes_all_properties?(field)
146
- ref = generate_schema_ref(field)
143
+ ref = generate_schema_ref(field, sibling_props: sibling_props)
147
144
  else
148
145
  ref = generate_schema_ref(
149
146
  field,
150
147
  id: generate_field_id(field_name),
148
+ sibling_props: sibling_props,
151
149
  endpoint: @endpoint,
152
150
  path: [field]
153
151
  )
154
152
  end
155
- properties[field_name][:allOf] = [ref]
153
+
154
+ properties[field_name] = {
155
+ description: (field.description if field.description.present?),
156
+ **ref
157
+ }.compact
156
158
  end
157
159
 
160
+ # We used to check if field.include was nil, but explicitly checking for a string is more robust.
161
+ # As some apia definitions declare `include: true`, which is not the correct way to use the option.
158
162
  def field_includes_all_properties?(field)
159
- field.include.nil?
163
+ !field.include.is_a?(String)
160
164
  end
161
165
 
162
166
  def generate_field_id(field_name)
@@ -173,7 +177,11 @@ module Apia
173
177
  d.http_status_code.to_s.to_sym
174
178
  end
175
179
 
176
- sorted_grouped_potential_errors = grouped_potential_errors.sort_by do |http_status_code, _|
180
+ deduplicated_potential_errors = grouped_potential_errors.map do |http_status_code, potential_errors|
181
+ [http_status_code, potential_errors.uniq { |error| error.code }]
182
+ end
183
+
184
+ sorted_grouped_potential_errors = deduplicated_potential_errors.sort_by do |http_status_code, _|
177
185
  http_status_code.to_s.to_i
178
186
  end
179
187
 
@@ -270,6 +278,7 @@ module Apia
270
278
  oneOf: definitions.map { |d| generate_ref("schemas", http_status_code, [d]) }
271
279
  }
272
280
 
281
+ # we don't need the ref allOf workaround here because these responses are not nullable
273
282
  schema = { "$ref": "#/components/schemas/#{one_of_id}" }
274
283
  end
275
284
 
@@ -40,7 +40,7 @@ module Apia
40
40
  @definition = definition
41
41
  @schema = schema
42
42
  @id = id
43
- @endpoint = endpoint # endpoint gets specified when we are dealing with a partial response
43
+ @endpoint = endpoint # endpoint gets specified when we are dealing with a partial response (see below)
44
44
  @path = path
45
45
  @children = []
46
46
  end
@@ -90,10 +90,10 @@ module Apia
90
90
 
91
91
  return if @children.empty?
92
92
 
93
- all_properties_included = error_definition? || enum_definition? || @endpoint.nil?
94
93
  @children.each do |child|
95
94
  next unless @endpoint.nil? || (!enum_definition? && @endpoint.include_field?(@path + [child]))
96
95
 
96
+ all_properties_included = all_properties_included_for_child?(child)
97
97
  if child.respond_to?(:array?) && child.array?
98
98
  generate_schema_for_child_array(@schema, child, all_properties_included)
99
99
  else
@@ -102,6 +102,25 @@ module Apia
102
102
  end
103
103
  end
104
104
 
105
+ # We need to check if all properties are included, because if they aren't we cannot use
106
+ # an existing schema $ref. We need to generate a new schema only for the endpoint.
107
+ # A field is considered 'partial' when declared in Apia with the `include` option, e.g.:
108
+ # ```
109
+ # field :zones,
110
+ # type: [Objects::Zone],
111
+ # include: 'id,name,permalink,data_center[reference]'
112
+ # ```
113
+ # the above example means we only want to include the id, name and permalink fields for the zone
114
+ # and data_center is a nested object in zone, which should only include reference.
115
+ # so the parent zone is a partial object and the child data_center is also a partial object.
116
+ def all_properties_included_for_child?(child)
117
+ return true if error_definition? || enum_definition? || @endpoint.nil? || !child.type.object?
118
+
119
+ child.type.klass.definition.fields.values.all? do |child_field|
120
+ @endpoint.include_field?(@path + [child, child_field])
121
+ end
122
+ end
123
+
105
124
  def generate_schema_for_child_array(schema, child, all_properties_included)
106
125
  child_schema = generate_schema_for_child({}, child, all_properties_included)
107
126
  items = child_schema.dig(:properties, child.name.to_s)
@@ -115,22 +134,23 @@ module Apia
115
134
  end
116
135
 
117
136
  def generate_schema_for_child(schema, child, all_properties_included)
137
+ nullable = child.try(:null?)
118
138
  if enum_definition?
119
139
  schema[:type] = "string"
120
140
  schema[:enum] = @children.map { |c| c[:name] }
121
141
  elsif child.type.argument_set? || child.type.enum? || child.type.polymorph?
122
142
  schema[:type] = "object"
123
143
  schema[:properties] ||= {}
124
- schema[:properties][child.name.to_s] = generate_schema_ref(child)
144
+ schema[:properties][child.name.to_s] = generate_schema_ref(child, sibling_props: nullable)
125
145
  elsif child.type.object?
126
- generate_properties_for_object(schema, child, all_properties_included)
146
+ generate_properties_for_object(schema, child, all_properties_included, nullable)
127
147
  else # scalar
128
148
  schema[:type] = "object"
129
149
  schema[:properties] ||= {}
130
150
  schema[:properties][child.name.to_s] = generate_scalar_schema(child)
131
151
  end
132
152
 
133
- if child.try(:null?)
153
+ if nullable
134
154
  schema[:properties][child.name.to_s][:nullable] = true
135
155
  end
136
156
 
@@ -141,11 +161,11 @@ module Apia
141
161
  schema
142
162
  end
143
163
 
144
- def generate_properties_for_object(schema, child, all_properties_included)
164
+ def generate_properties_for_object(schema, child, all_properties_included, nullable)
145
165
  schema[:type] = "object"
146
166
  schema[:properties] ||= {}
147
167
  if all_properties_included
148
- ref = generate_schema_ref(child)
168
+ ref = generate_schema_ref(child, sibling_props: nullable)
149
169
  else
150
170
  child_path = @path.nil? ? nil : @path + [child]
151
171
 
@@ -156,13 +176,13 @@ module Apia
156
176
  ref = generate_schema_ref(
157
177
  child,
158
178
  id: "#{truncated_id}Part_#{child.name}".camelize,
179
+ sibling_props: nullable,
159
180
  endpoint: @endpoint,
160
181
  path: child_path
161
182
  )
162
183
  end
163
184
 
164
- # Using allOf is a workaround to allow us to set a ref as `nullable` in OpenAPI 3.0
165
- schema[:properties][child.name.to_s] = { allOf: [ref] }
185
+ schema[:properties][child.name.to_s] = ref
166
186
  end
167
187
 
168
188
  end
@@ -28,6 +28,9 @@ module Apia
28
28
  },
29
29
  security: []
30
30
  }
31
+
32
+ # path_ids is used to keep track of all the IDs of all the paths we've generated, to avoid duplicates
33
+ # refer to the Path object for more info
31
34
  @path_ids = []
32
35
  build_spec
33
36
  end
@@ -60,7 +63,9 @@ module Apia
60
63
 
61
64
  def add_paths
62
65
  @api.definition.route_set.routes.each do |route|
63
- next unless route.endpoint.definition.schema? # not all routes should be documented
66
+ # not all routes should be documented
67
+ next unless route.group.nil? || route.group.schema?
68
+ next unless route.endpoint.definition.schema?
64
69
 
65
70
  Objects::Path.new(
66
71
  spec: @spec,
@@ -3,7 +3,7 @@
3
3
  module Apia
4
4
  module OpenApi
5
5
 
6
- VERSION = "0.1.5"
6
+ VERSION = "0.1.6"
7
7
 
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: apia-open_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.5
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Sturgess
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-24 00:00:00.000000000 Z
11
+ date: 2024-02-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport