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 +4 -4
- data/lib/apia/open_api/helpers.rb +14 -0
- data/lib/apia/open_api/objects/parameters.rb +56 -4
- data/lib/apia/open_api/objects/path.rb +51 -1
- data/lib/apia/open_api/rack.rb +18 -1
- data/lib/apia/open_api/specification.rb +34 -14
- data/lib/apia/open_api/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9105b1350ceecf58bdf2037f3052a50f38ec76be818c67016c46381a68fb2f4b
|
4
|
+
data.tar.gz: 433da5d349c9aabd1f71277e5b41885b8bcc514cda52ca0f8c659470307c01c7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
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
|
data/lib/apia/open_api/rack.rb
CHANGED
@@ -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
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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]
|
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
|
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
|
+
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-
|
11
|
+
date: 2024-09-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|