apia-open_api 0.1.9 → 0.1.11

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: ab4263122239dfce8e477f4279cbe4a03743f0e6fa4c1236c0480082d517ec8c
4
- data.tar.gz: 8542223956ac7fc5fc4817cc627c0d5ec96876cd1f671a4e26422deae3818387
3
+ metadata.gz: 9105b1350ceecf58bdf2037f3052a50f38ec76be818c67016c46381a68fb2f4b
4
+ data.tar.gz: 433da5d349c9aabd1f71277e5b41885b8bcc514cda52ca0f8c659470307c01c7
5
5
  SHA512:
6
- metadata.gz: 05131ea1ca348bce2d637314eba336e07bb4553f71ef3fd107c23ee854f107f09b26d24c0cdfdebb259ef450421bbcdc8864b6ac8fc057e32ac8570d590f24eb
7
- data.tar.gz: 95cad90b7979f85e4cc085bb25ea7f33583766f8b05ef94a8ac91a93701e27e6b757bc3e63141211a9fab5dad16f57872b98dd3ad1c5a77fefa4c25c9b4f3bb1
6
+ metadata.gz: a6bc4e0c62791abe13bcc5742270e0faefc30cbed76a140a1fed6afa766dd3635f344fbb3b322a7f5f1fec02c2c4d69cc90ec0866a661a971e619fe76662a543
7
+ data.tar.gz: dbb0d647cea2e6f208fee6426dd5b7bbb3270a8bcbbdbb7a84ca2bc9a1975cecfe98caa7a729e859357493085a1639e9b8c2be24662b96c3c4cf69e238fed0ca
@@ -52,6 +52,20 @@ module Apia
52
52
  schema
53
53
  end
54
54
 
55
+ def generate_array_schema(definition)
56
+ type = definition.type
57
+ schema = {
58
+ type: "array",
59
+ items: {
60
+ type: convert_type_to_open_api_data_type(type)
61
+ }
62
+ }
63
+ schema[:description] = definition.description if definition.description.present?
64
+ schema[:items][:format] = "float" if type.klass == Apia::Scalars::Decimal
65
+ schema[:items][:format] = "date" if type.klass == Apia::Scalars::Date
66
+ schema
67
+ end
68
+
55
69
  def generate_schema_ref(definition, id: nil, sibling_props: false, **schema_opts)
56
70
  id ||= generate_id_from_definition(definition.type.klass.definition)
57
71
  success = add_to_components_schemas(definition, id, **schema_opts)
@@ -69,6 +69,9 @@ module Apia
69
69
  schema: generate_scalar_schema(@argument)
70
70
  }
71
71
  param[:description] = @argument.description if @argument.description.present?
72
+
73
+ add_pagination_params(param)
74
+
72
75
  param[:required] = true if @argument.required?
73
76
  add_to_parameters(param)
74
77
  end
@@ -76,6 +79,19 @@ module Apia
76
79
 
77
80
  private
78
81
 
82
+ def add_pagination_params(param)
83
+ if param[:name] == "page"
84
+ param[:description] = "The page number to request. If not provided, the first page will be returned."
85
+ param[:schema][:default] = 1
86
+ param[:schema][:minimum] = 1
87
+ elsif param[:name] == "per_page"
88
+ param[:description] =
89
+ "The number of items to return per page. If not provided, the default value will be used."
90
+ param[:schema][:default] = @argument.default
91
+ param[:schema][:minimum] = 1
92
+ end
93
+ end
94
+
79
95
  # Complex argument sets are not supported in query params (e.g. nested objects)
80
96
  # For any LookupArgumentSet only one argument is expected to be provided.
81
97
  # However, OpenAPI does not currently support describing mutually exclusive query params.
@@ -87,19 +103,55 @@ module Apia
87
103
  next if child_arg.type.argument_set?
88
104
 
89
105
  param = {
90
- name: "#{@argument.name}[#{child_arg.name}]",
91
- in: "query",
92
- schema: generate_scalar_schema(child_arg)
106
+ in: "query"
93
107
  }
108
+
94
109
  description = []
95
110
  description << formatted_description(@argument.description) if @argument.description.present?
96
111
  description << formatted_description(child_arg.description) if child_arg.description.present?
97
- description << "All '#{@argument.name}[]' params are mutually exclusive, only one can be provided."
112
+
113
+ add_lookup_description(description)
114
+
115
+ if @argument.array
116
+ param[:name] = "#{@argument.name}[][#{child_arg.name}]"
117
+ param[:schema] = generate_array_schema(child_arg)
118
+
119
+ add_description_section(
120
+ description,
121
+ "All `#{@argument.name}[]` params should have the same amount of elements."
122
+ )
123
+
124
+ else
125
+ param[:name] = "#{@argument.name}[#{child_arg.name}]"
126
+ param[:schema] = generate_scalar_schema(child_arg)
127
+ end
128
+
98
129
  param[:description] = description.join(" ")
99
130
  add_to_parameters(param)
100
131
  end
101
132
  end
102
133
 
134
+ # Adds a section to the description of a parameter.
135
+ # If the description is not empty, a blank line is added before the section.
136
+ #
137
+ # @param description [String] The current description of the parameter.
138
+ # @param addition [String] The section to be added to the description.
139
+ # @return [String] The updated description with the added section.
140
+ def add_description_section(description, addition)
141
+ unless description.empty?
142
+ description << "\n\n"
143
+ end
144
+
145
+ description << addition
146
+ end
147
+
148
+ def add_lookup_description(description)
149
+ add_description_section(
150
+ description,
151
+ "All '#{@argument.name}[]' params are mutually exclusive, only one can be provided."
152
+ )
153
+ end
154
+
103
155
  def add_to_parameters(param)
104
156
  @route_spec[:parameters] << param
105
157
  end
@@ -38,12 +38,14 @@ module Apia
38
38
  operationId: convert_route_to_id,
39
39
  summary: @route.endpoint.definition.name,
40
40
  description: @route.endpoint.definition.description,
41
- tags: route.group ? get_group_tags(route.group) : [name]
41
+ tags: route.group ? get_group_tags(route.group) : [name],
42
+ security: []
42
43
  }
43
44
  end
44
45
 
45
46
  def add_to_spec
46
47
  add_scopes_description
48
+ add_scopes_security
47
49
  path = @route.path
48
50
 
49
51
  if @route.request_method == :get
@@ -104,6 +106,45 @@ module Apia
104
106
  "- `#{scope}`"
105
107
  end.join("\n")}
106
108
  DESCRIPTION
109
+
110
+ @spec[:security].each do |auth|
111
+ auth.each_key do |key|
112
+ scope_prefix = @spec[:components][:securitySchemes][key][:"x-scope-prefix"]
113
+ next unless scope_prefix.present?
114
+
115
+ @route_spec[:description] =
116
+ <<~DESCRIPTION
117
+ #{@route_spec[:description]}
118
+ ### #{key} Scopes
119
+ When using #{key} authentication, scopes are prefixed with `#{scope_prefix}`.
120
+ DESCRIPTION
121
+ end
122
+ end
123
+ end
124
+
125
+ # Adds scopes security to the OpenAPI path specification.
126
+ #
127
+ # This method checks if the route's endpoint definition has any scopes defined.
128
+ # If scopes are present, it iterates over the security schemes in the OpenAPI
129
+ # specification and adds the corresponding scopes to the route's security section.
130
+ #
131
+ # @return [void]
132
+ def add_scopes_security
133
+ unless @route.endpoint.definition.scopes.any?
134
+ @route_spec.delete(:security)
135
+ return
136
+ end
137
+
138
+ @spec[:security].each do |auth|
139
+ auth.each_key do |key|
140
+ scopes = @route.endpoint.definition.scopes
141
+ if scope_prefix = @spec[:components][:securitySchemes][key][:"x-scope-prefix"]
142
+ scopes = scopes.map { |v| "#{scope_prefix}/#{v}" }
143
+ end
144
+
145
+ @route_spec[:security] << { key => scopes }
146
+ end
147
+ end
107
148
  end
108
149
 
109
150
  # It's worth creating a 'nice' operationId for each route, as this is used as the
@@ -162,6 +203,15 @@ module Apia
162
203
  current_group = group
163
204
 
164
205
  while current_group
206
+ # Add tags to the spec global tags if they don't already exist
207
+ # Include a description if the group has one.
208
+ unless @spec[:tags].any? { |t| t[:name] == current_group.name }
209
+ global_tag = { name: current_group.name }
210
+ global_tag[:description] = current_group.description if current_group.description
211
+ @spec[:tags] << global_tag
212
+
213
+ end
214
+
165
215
  tags.unshift(current_group.name)
166
216
  current_group = current_group.parent
167
217
  end
@@ -29,6 +29,18 @@ module Apia
29
29
  @options[:base_url] || "https://api.example.com/api/v1"
30
30
  end
31
31
 
32
+ def security_schemes
33
+ @options[:security_schemes] || {}
34
+ end
35
+
36
+ def external_docs
37
+ @options[:external_docs] || {}
38
+ end
39
+
40
+ def info
41
+ @options[:info] || {}
42
+ end
43
+
32
44
  def call(env)
33
45
  if @options[:hosts]&.none? { |host| host == env["HTTP_HOST"] }
34
46
  return @app.call(env)
@@ -38,7 +50,12 @@ module Apia
38
50
  return @app.call(env)
39
51
  end
40
52
 
41
- specification = Specification.new(api_class, base_url, @options[:name])
53
+ specification = Specification.new(api_class, base_url, @options[:name],
54
+ {
55
+ info: info,
56
+ external_docs: external_docs,
57
+ security_schemes: security_schemes
58
+ })
42
59
  body = specification.json
43
60
 
44
61
  [200, { "content-type" => "application/json", "content-length" => body.bytesize.to_s }, [body]]
@@ -14,13 +14,17 @@ module Apia
14
14
 
15
15
  OPEN_API_VERSION = "3.0.0" # The Ruby client generator currently only supports v3.0.0 https://openapi-generator.tech/
16
16
 
17
- def initialize(api, base_url, name)
17
+ def initialize(api, base_url, name, additions = {})
18
+ default_additions = { info: {}, external_docs: {}, security_schemes: {} }
19
+ additions = default_additions.merge(additions)
20
+
18
21
  @api = api
19
22
  @base_url = base_url
20
23
  @name = name || "Core" # will be suffixed with 'Api' and used in the client generator
21
24
  @spec = {
22
25
  openapi: OPEN_API_VERSION,
23
- info: {},
26
+ info: additions[:info],
27
+ externalDocs: additions[:external_docs],
24
28
  servers: [],
25
29
  paths: {},
26
30
  components: {
@@ -31,6 +35,12 @@ module Apia
31
35
  "x-tagGroups": []
32
36
  }
33
37
 
38
+ if @spec[:externalDocs].nil? || @spec[:externalDocs].empty?
39
+ @spec.delete(:externalDocs)
40
+ end
41
+
42
+ add_additional_security_schemes(additions[:security_schemes])
43
+
34
44
  # path_ids is used to keep track of all the IDs of all the paths we've generated, to avoid duplicates
35
45
  # refer to the Path object for more info
36
46
  @path_ids = []
@@ -52,8 +62,8 @@ module Apia
52
62
  def build_spec
53
63
  add_info
54
64
  add_servers
55
- add_paths
56
65
  add_security
66
+ add_paths
57
67
  add_tag_groups
58
68
 
59
69
  @spec[:paths] = sort_hash_by_nested_tag(@spec[:paths])
@@ -61,11 +71,13 @@ module Apia
61
71
 
62
72
  def add_info
63
73
  title = @api.definition.name || @api.definition.id
64
- @spec[:info] = {
65
- version: "1.0.0",
66
- title: title
67
- }
68
- @spec[:info][:description] = @api.definition.description || "Welcome to the documentation for the #{title}"
74
+ @spec[:info][:version] = "1.0.0" if @spec[:info][:version].nil?
75
+ @spec[:info][:title] = title if @spec[:info][:title].nil?
76
+
77
+ return unless @spec[:info][:description].nil?
78
+
79
+ @spec[:info][:description] =
80
+ @api.definition.description || "Welcome to the documentation for the #{title}"
69
81
  end
70
82
 
71
83
  def add_servers
@@ -119,15 +131,11 @@ module Apia
119
131
  def add_tag_groups
120
132
  @spec[:paths].each_value do |methods|
121
133
  methods.each_value do |method_spec|
122
- method_spec[:tags].each_with_index do |tag, tag_index|
123
- unless @spec[:tags].any? { |t| t[:name] == tag }
124
- @spec[:tags] << { name: tag }
125
- end
134
+ tags = method_spec[:tags]
126
135
 
136
+ tags.each_with_index do |tag, tag_index|
127
137
  next if tag_index.zero?
128
138
 
129
- tags = method_spec[:tags]
130
-
131
139
  parent_tag = tags[tag_index - 1]
132
140
  parent_index = get_tag_group_index(parent_tag)
133
141
 
@@ -140,6 +148,10 @@ module Apia
140
148
  @spec[:"x-tagGroups"][parent_index][:tags] << tag
141
149
  end
142
150
  end
151
+
152
+ # Set the last tag as the tag for the method
153
+ # After we have built the tag group.
154
+ method_spec[:tags] = [tags.last] if tags.any?
143
155
  end
144
156
  end
145
157
 
@@ -153,6 +165,14 @@ module Apia
153
165
  @spec[:"x-tagGroups"].each { |group| group[:tags].sort! }
154
166
  end
155
167
 
168
+ def add_additional_security_schemes(security_schemes)
169
+ security_schemes.each do |key, value|
170
+ @spec[:components][:securitySchemes] ||= {}
171
+ @spec[:components][:securitySchemes][key] = value.transform_keys(&:to_sym)
172
+ @spec[:security] << { key => [] }
173
+ end
174
+ end
175
+
156
176
  end
157
177
  end
158
178
  end
@@ -3,7 +3,7 @@
3
3
  module Apia
4
4
  module OpenApi
5
5
 
6
- VERSION = "0.1.9"
6
+ VERSION = "0.1.11"
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.9
4
+ version: 0.1.11
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-08-21 00:00:00.000000000 Z
11
+ date: 2024-09-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport