apia-open_api 0.1.4 → 0.1.6

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: f046cce05d4154ba36e9f52227306129ef092e75061db7a522b44ade60b252c0
4
- data.tar.gz: 2c56c6cedf8281819ca4c6382159183cdcc0c550d27375410f7f39cfe4bb842a
3
+ metadata.gz: 6621197f4a9535afae13e2a83de1fc5993290f379660e347afe989e94f3179ba
4
+ data.tar.gz: a932546d15d8302931a4c55d1fa4fa1e088b4f74ee7b227d0817de5f7b3f6dbb
5
5
  SHA512:
6
- metadata.gz: 9c60e070b4a683379b689fff7c95ba8a7aa6ea9f2902e13b68474190b2aac234feec2428c73871591b467af2ba17be21c2dcb6d8b2297014a88e1ec783311472
7
- data.tar.gz: afce3695675df681249591575c3704161caa3dd66b91a67e89bc67e58b07d45d8d6445e0080f7264c2a0fb234f1a03f87eec97aeeba22e39bf873f991b38452b
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
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,21 +134,26 @@ 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
 
153
+ if nullable
154
+ schema[:properties][child.name.to_s][:nullable] = true
155
+ end
156
+
133
157
  if child.try(:required?)
134
158
  schema[:required] ||= []
135
159
  schema[:required] << child.name.to_s
@@ -137,11 +161,11 @@ module Apia
137
161
  schema
138
162
  end
139
163
 
140
- def generate_properties_for_object(schema, child, all_properties_included)
164
+ def generate_properties_for_object(schema, child, all_properties_included, nullable)
141
165
  schema[:type] = "object"
142
166
  schema[:properties] ||= {}
143
167
  if all_properties_included
144
- schema[:properties][child.name.to_s] = generate_schema_ref(child)
168
+ ref = generate_schema_ref(child, sibling_props: nullable)
145
169
  else
146
170
  child_path = @path.nil? ? nil : @path + [child]
147
171
 
@@ -149,13 +173,16 @@ module Apia
149
173
  # very long IDs, so we truncate them to avoid hitting the 100 character
150
174
  # filename limit imposed by the rubygems gem builder.
151
175
  truncated_id = @id.match(/^(.*?)\d*?(Response|Part).*$/)[1]
152
- schema[:properties][child.name.to_s] = generate_schema_ref(
176
+ ref = generate_schema_ref(
153
177
  child,
154
178
  id: "#{truncated_id}Part_#{child.name}".camelize,
179
+ sibling_props: nullable,
155
180
  endpoint: @endpoint,
156
181
  path: child_path
157
182
  )
158
183
  end
184
+
185
+ schema[:properties][child.name.to_s] = ref
159
186
  end
160
187
 
161
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.4"
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.4
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: 2023-12-14 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
@@ -70,7 +70,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
70
70
  - !ruby/object:Gem::Version
71
71
  version: '0'
72
72
  requirements: []
73
- rubygems_version: 3.4.10
73
+ rubygems_version: 3.4.19
74
74
  signing_key:
75
75
  specification_version: 4
76
76
  summary: Apia OpenAPI spec generator