oas_core 0.0.1
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 +7 -0
- data/LICENSE +674 -0
- data/README.md +70 -0
- data/Rakefile +12 -0
- data/lib/oas_core/builders/content_builder.rb +44 -0
- data/lib/oas_core/builders/operation_builder.rb +94 -0
- data/lib/oas_core/builders/parameter_builder.rb +31 -0
- data/lib/oas_core/builders/parameters_builder.rb +46 -0
- data/lib/oas_core/builders/path_item_builder.rb +26 -0
- data/lib/oas_core/builders/request_body_builder.rb +45 -0
- data/lib/oas_core/builders/response_builder.rb +42 -0
- data/lib/oas_core/builders/responses_builder.rb +92 -0
- data/lib/oas_core/builders/specification_builder.rb +24 -0
- data/lib/oas_core/configuration.rb +143 -0
- data/lib/oas_core/json_schema_generator.rb +134 -0
- data/lib/oas_core/oas_route.rb +24 -0
- data/lib/oas_core/spec/components.rb +99 -0
- data/lib/oas_core/spec/contact.rb +20 -0
- data/lib/oas_core/spec/hashable.rb +41 -0
- data/lib/oas_core/spec/info.rb +68 -0
- data/lib/oas_core/spec/license.rb +20 -0
- data/lib/oas_core/spec/media_type.rb +26 -0
- data/lib/oas_core/spec/operation.rb +28 -0
- data/lib/oas_core/spec/parameter.rb +39 -0
- data/lib/oas_core/spec/path_item.rb +27 -0
- data/lib/oas_core/spec/paths.rb +28 -0
- data/lib/oas_core/spec/reference.rb +18 -0
- data/lib/oas_core/spec/request_body.rb +23 -0
- data/lib/oas_core/spec/response.rb +22 -0
- data/lib/oas_core/spec/responses.rb +27 -0
- data/lib/oas_core/spec/server.rb +19 -0
- data/lib/oas_core/spec/specable.rb +56 -0
- data/lib/oas_core/spec/specification.rb +34 -0
- data/lib/oas_core/spec/tag.rb +20 -0
- data/lib/oas_core/string.rb +13 -0
- data/lib/oas_core/utils.rb +94 -0
- data/lib/oas_core/version.rb +5 -0
- data/lib/oas_core/yard/example_tag.rb +14 -0
- data/lib/oas_core/yard/oas_core_factory.rb +166 -0
- data/lib/oas_core/yard/parameter_tag.rb +16 -0
- data/lib/oas_core/yard/request_body_example_tag.rb +13 -0
- data/lib/oas_core/yard/request_body_tag.rb +17 -0
- data/lib/oas_core/yard/response_example_tag.rb +14 -0
- data/lib/oas_core/yard/response_tag.rb +15 -0
- data/lib/oas_core.rb +88 -0
- metadata +120 -0
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+

|
2
|
+

|
3
|
+

|
4
|
+

|
5
|
+

|
6
|
+

|
7
|
+
|
8
|
+
# πOpen API Specification For Rails
|
9
|
+
|
10
|
+
OasCore is a Rails engine for generating **automatic interactive documentation for your Rails APIs**. It generates an **OAS 3.1** document and displays it using **[RapiDoc](https://rapidocweb.com)**.
|
11
|
+
|
12
|
+
### π Demo App
|
13
|
+
|
14
|
+
Explore the interactive documentation live:
|
15
|
+
|
16
|
+
π **[Open Demo App](https://paso.fly.dev/api/docs)**
|
17
|
+
π€ **Username**: `oasrails`
|
18
|
+
π **Password**: `oasrails`
|
19
|
+
|
20
|
+
π¬ A Demo Installation/Usage Video:
|
21
|
+
<https://vimeo.com/1013687332>
|
22
|
+
π¬
|
23
|
+
|
24
|
+

|
25
|
+
|
26
|
+
## Related Projects
|
27
|
+
|
28
|
+
- **[ApiPie](https://github.com/Apipie/apipie-rails)**: Doesn't support OAS 3.1, requires learning a DSL, lacks a nice UI
|
29
|
+
- **[swagger_yard-rails](https://github.com/livingsocial/swagger_yard-rails)**: Seems abandoned, but serves as inspiration
|
30
|
+
- **[Rswag](https://github.com/rswag/rswag)**: Not automatic, depends on RSpec; Many developers now use Minitest as it's the default test framework
|
31
|
+
- **[grape-swagger](https://github.com/ruby-grape/grape-swagger)**: Requires Grape
|
32
|
+
- **[rspec_api_documentation](https://github.com/zipmark/rspec_api_documentation)**: Requires RSpec and a command to generate the docs
|
33
|
+
|
34
|
+
## What Sets OasCore Apart?
|
35
|
+
|
36
|
+
- **Dynamic**: No command required to generate docs
|
37
|
+
- **Simple**: Complement default documentation with a few comments; no need to learn a complex DSL
|
38
|
+
- **Pure Ruby on Rails APIs**: No additional frameworks needed (e.g., Grape, RSpec)
|
39
|
+
|
40
|
+
## π½οΈ Motivation
|
41
|
+
|
42
|
+
After experiencing the interactive documentation in Python's fast-api framework, I sought similar functionality in Ruby on Rails. Unable to find a suitable solution, I [asked on Stack Overflow](https://stackoverflow.com/questions/71947018/is-there-a-way-to-generate-an-interactive-documentation-for-rails-apis) years ago. Now, with some free time while freelancing as an API developer, I decided to build my own tool.
|
43
|
+
|
44
|
+
**Note: This is not yet a production-ready solution. The code may be rough and behave unexpectedly, but I am actively working on improving it. If you like the idea, please consider contributing to its development.**
|
45
|
+
|
46
|
+
The goal is to minimize the effort required to create comprehensive documentation. By following REST principles in Rails, we believe this is achievable. You can enhance the documentation using [Yard](https://yardoc.org/) tags.
|
47
|
+
|
48
|
+
## Documentation
|
49
|
+
|
50
|
+
For see how to install, configure and use OasCore please refere to the [OasCoreBook](http://a-chacon.com/oas_core)
|
51
|
+
|
52
|
+
## Contributing
|
53
|
+
|
54
|
+
Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". Don't forget to give the project a starβ! Thanks again!
|
55
|
+
|
56
|
+
If you plan a big feature, first open an issue to discuss it before any development.
|
57
|
+
|
58
|
+
1. Fork the Project
|
59
|
+
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
|
60
|
+
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
|
61
|
+
4. Push to the Branch (`git push origin feature/AmazingFeature`)
|
62
|
+
5. Open a Pull Request
|
63
|
+
|
64
|
+
## License
|
65
|
+
|
66
|
+
The gem is available as open source under the terms of the [GPL-3.0](https://www.gnu.org/licenses/gpl-3.0.en.html#license-text).
|
67
|
+
|
68
|
+
## Star History
|
69
|
+
|
70
|
+
[](https://www.star-history.com/#a-chacon/oas_core&Date)
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OasCore
|
4
|
+
module Builders
|
5
|
+
class ContentBuilder
|
6
|
+
def initialize(specification, context)
|
7
|
+
@context = context || :incoming
|
8
|
+
@specification = specification
|
9
|
+
@media_type = Spec::MediaType.new(specification)
|
10
|
+
end
|
11
|
+
|
12
|
+
def with_schema(schema)
|
13
|
+
@media_type.schema = @specification.components.add_schema(schema)
|
14
|
+
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def with_examples(examples)
|
19
|
+
@media_type.examples = @specification.components.add_example(examples)
|
20
|
+
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def with_examples_from_tags(tags)
|
25
|
+
@media_type.examples = @media_type.examples.merge(tags.each_with_object({}).with_index(1) do |(example, result), _index|
|
26
|
+
key = example.text.downcase.gsub(' ', '_')
|
27
|
+
value = {
|
28
|
+
'summary' => example.text,
|
29
|
+
'value' => example.content
|
30
|
+
}
|
31
|
+
result[key] = @specification.components.add_example(value)
|
32
|
+
end)
|
33
|
+
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def build
|
38
|
+
{
|
39
|
+
'application/json': @media_type
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OasCore
|
4
|
+
module Builders
|
5
|
+
class OperationBuilder
|
6
|
+
def initialize(specification)
|
7
|
+
@specification = specification
|
8
|
+
@operation = Spec::Operation.new(specification)
|
9
|
+
end
|
10
|
+
|
11
|
+
def from_oas_route(oas_route)
|
12
|
+
@operation.summary = extract_summary(oas_route:)
|
13
|
+
@operation.operation_id = extract_operation_id(oas_route:)
|
14
|
+
@operation.description = oas_route.docstring
|
15
|
+
@operation.tags = extract_tags(oas_route:)
|
16
|
+
@operation.security = extract_security(oas_route:)
|
17
|
+
@operation.parameters = ParametersBuilder.new(@specification).from_oas_route(oas_route).build
|
18
|
+
@operation.request_body = RequestBodyBuilder.new(@specification).from_oas_route(oas_route).reference
|
19
|
+
@operation.responses = ResponsesBuilder.new(@specification)
|
20
|
+
.from_oas_route(oas_route)
|
21
|
+
.add_default_responses(oas_route, !@operation.security.empty?).build
|
22
|
+
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def build
|
27
|
+
@operation
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def extract_summary(oas_route:)
|
33
|
+
oas_route.tags(:summary).first.try(:text) || generate_crud_name(oas_route.method_name,
|
34
|
+
oas_route.controller.downcase) || "#{oas_route.verb} #{oas_route.path}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def extract_operation_id(oas_route:)
|
38
|
+
"#{oas_route.method_name}#{oas_route.path.gsub('/', '_').gsub(/[{}]/, '')}"
|
39
|
+
end
|
40
|
+
|
41
|
+
def extract_tags(oas_route:)
|
42
|
+
tags = oas_route.tags(:tags).first
|
43
|
+
if tags.nil?
|
44
|
+
default_tags(oas_route:)
|
45
|
+
else
|
46
|
+
tags.text.split(',').map(&:strip).map(&:titleize)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def default_tags(oas_route:)
|
51
|
+
tags = []
|
52
|
+
if OasCore.config.default_tags_from == :namespace
|
53
|
+
tag = oas_route.path.split('/').reject(&:empty?).first.try(:titleize)
|
54
|
+
tags << tag unless tag.nil?
|
55
|
+
else
|
56
|
+
tags << oas_route.controller.gsub('/', ' ').titleize
|
57
|
+
end
|
58
|
+
|
59
|
+
tags
|
60
|
+
end
|
61
|
+
|
62
|
+
def extract_security(oas_route:)
|
63
|
+
return [] if oas_route.tags(:no_auth).any?
|
64
|
+
|
65
|
+
if (methods = oas_route.tags(:auth).first)
|
66
|
+
OasCore.config.security_schemas.keys.map { |key| { key => [] } }.select do |schema|
|
67
|
+
methods.types.include?(schema.keys.first.to_s)
|
68
|
+
end
|
69
|
+
elsif OasCore.config.authenticate_all_routes_by_default
|
70
|
+
OasCore.config.security_schemas.keys.map { |key| { key => [] } }
|
71
|
+
else
|
72
|
+
[]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def generate_crud_name(method, controller)
|
77
|
+
controller_name = controller.to_s.underscore.humanize.downcase.pluralize
|
78
|
+
|
79
|
+
case method.to_sym
|
80
|
+
when :index
|
81
|
+
"List #{controller_name}"
|
82
|
+
when :show
|
83
|
+
"View #{controller_name.singularize}"
|
84
|
+
when :create
|
85
|
+
"Create new #{controller_name.singularize}"
|
86
|
+
when :update
|
87
|
+
"Update #{controller_name.singularize}"
|
88
|
+
when :destroy
|
89
|
+
"Delete #{controller_name.singularize}"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OasCore
|
4
|
+
module Builders
|
5
|
+
class ParameterBuilder
|
6
|
+
def initialize(specification)
|
7
|
+
@specification = specification
|
8
|
+
@parameter = Spec::Parameter.new(specification)
|
9
|
+
end
|
10
|
+
|
11
|
+
def from_path(path, param)
|
12
|
+
@parameter.name = param
|
13
|
+
@parameter.in = 'path'
|
14
|
+
@parameter.description = "#{param.split('_')[-1].titleize} of existing #{extract_word_before(path,
|
15
|
+
param).singularize}."
|
16
|
+
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def extract_word_before(string, param)
|
21
|
+
regex = %r{/([\w-]+)/\{#{param}\}}
|
22
|
+
match = string.match(regex)
|
23
|
+
match ? match[1] : ''
|
24
|
+
end
|
25
|
+
|
26
|
+
def build
|
27
|
+
@parameter
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OasCore
|
4
|
+
module Builders
|
5
|
+
class ParametersBuilder
|
6
|
+
def initialize(specification)
|
7
|
+
@specification = specification
|
8
|
+
@parameters = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def from_oas_route(oas_route)
|
12
|
+
parameters_from_tags(tags: oas_route.tags(:parameter))
|
13
|
+
oas_route.path_params.try(:map) do |p|
|
14
|
+
unless @parameters.any? do |param|
|
15
|
+
param.name.to_s == p.to_s
|
16
|
+
end
|
17
|
+
@parameters << ParameterBuilder.new(@specification).from_path(oas_route.path,
|
18
|
+
p).build
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def parameters_from_tags(tags:)
|
26
|
+
tags.each do |t|
|
27
|
+
parameter = Spec::Parameter.new(@specification)
|
28
|
+
parameter.name = t.name
|
29
|
+
parameter.in = t.location
|
30
|
+
parameter.required = t.required
|
31
|
+
parameter.schema = t.schema
|
32
|
+
parameter.description = t.text
|
33
|
+
@parameters << parameter
|
34
|
+
end
|
35
|
+
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def build
|
40
|
+
@parameters.map do |p|
|
41
|
+
@specification.components.add_parameter(p)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OasCore
|
4
|
+
module Builders
|
5
|
+
class PathItemBuilder
|
6
|
+
def initialize(specification)
|
7
|
+
@specification = specification
|
8
|
+
@path_item = Spec::PathItem.new(specification)
|
9
|
+
end
|
10
|
+
|
11
|
+
def with_oas_routes(oas_routes)
|
12
|
+
oas_routes.each do |oas_route|
|
13
|
+
oas_route.verb.downcase.split('|').each do |v|
|
14
|
+
@path_item.add_operation(v, OperationBuilder.new(@specification).from_oas_route(oas_route).build)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def build
|
22
|
+
@path_item
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OasCore
|
4
|
+
module Builders
|
5
|
+
class RequestBodyBuilder
|
6
|
+
def initialize(specification)
|
7
|
+
@specification = specification
|
8
|
+
@request_body = Spec::RequestBody.new(specification)
|
9
|
+
end
|
10
|
+
|
11
|
+
def from_oas_route(oas_route)
|
12
|
+
tag_request_body = oas_route.tags(:request_body).first
|
13
|
+
# TODO: This is for frameowkr specific.
|
14
|
+
# if tag_request_body.nil? && OasCore.config.autodiscover_request_body
|
15
|
+
# detect_request_body(oas_route) if %w[create update].include? oas_route.method_name
|
16
|
+
return self if tag_request_body.nil?
|
17
|
+
|
18
|
+
from_tags(tag: tag_request_body, examples_tags: oas_route.tags(:request_body_example))
|
19
|
+
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def from_tags(tag:, examples_tags: [])
|
24
|
+
@request_body.description = tag.text
|
25
|
+
@request_body.content = ContentBuilder.new(@specification,
|
26
|
+
:incoming).with_schema(tag.schema).with_examples_from_tags(examples_tags).build
|
27
|
+
@request_body.required = tag.required
|
28
|
+
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
def build
|
33
|
+
return {} if @request_body.content == {}
|
34
|
+
|
35
|
+
@request_body
|
36
|
+
end
|
37
|
+
|
38
|
+
def reference
|
39
|
+
return {} if @request_body.content == {}
|
40
|
+
|
41
|
+
@specification.components.add_request_body(@request_body)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OasCore
|
4
|
+
module Builders
|
5
|
+
class ResponseBuilder
|
6
|
+
def initialize(specification)
|
7
|
+
@specification = specification
|
8
|
+
@response = Spec::Response.new(specification)
|
9
|
+
end
|
10
|
+
|
11
|
+
def with_description(description)
|
12
|
+
@response.description = description
|
13
|
+
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def with_content(content)
|
18
|
+
@response.content = content
|
19
|
+
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def with_code(code)
|
24
|
+
@response.code = code
|
25
|
+
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
def from_tag(tag)
|
30
|
+
@response.code = tag.name.to_i
|
31
|
+
@response.description = tag.text
|
32
|
+
@response.content = ContentBuilder.new(@specification, :outgoing).with_schema(tag.schema).build
|
33
|
+
|
34
|
+
self
|
35
|
+
end
|
36
|
+
|
37
|
+
def build
|
38
|
+
@response
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OasCore
|
4
|
+
module Builders
|
5
|
+
class ResponsesBuilder
|
6
|
+
def initialize(specification, content_builder: ContentBuilder, response_builder: ResponseBuilder,
|
7
|
+
json_schema_generator: JsonSchemaGenerator, utils: Utils)
|
8
|
+
@specification = specification
|
9
|
+
@content_builder = content_builder
|
10
|
+
@response_builder = response_builder
|
11
|
+
@json_schema_generator = json_schema_generator
|
12
|
+
@utils = utils
|
13
|
+
@responses = Spec::Responses.new(specification)
|
14
|
+
end
|
15
|
+
|
16
|
+
def from_oas_route(oas_route)
|
17
|
+
oas_route.tags(:response).each do |tag|
|
18
|
+
content = @content_builder.new(@specification,
|
19
|
+
:outgoing).with_schema(tag.schema).with_examples_from_tags(oas_route.tags(:response_example).filter do |re|
|
20
|
+
re.code == tag.name
|
21
|
+
end).build
|
22
|
+
response = @response_builder.new(@specification).with_code(tag.name.to_i).with_description(tag.text).with_content(content).build
|
23
|
+
|
24
|
+
@responses.add_response(response)
|
25
|
+
end
|
26
|
+
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
# def add_autodiscovered_responses(oas_route)
|
31
|
+
# return self if !OasCore.config.autodiscover_responses || oas_route.tags(:response).any?
|
32
|
+
#
|
33
|
+
# new_responses = Extractors::RenderResponseExtractor.extract_responses_from_source(@specification, source: oas_route.source_string)
|
34
|
+
#
|
35
|
+
# new_responses.each do |new_response|
|
36
|
+
# @responses.add_response(new_response) if @responses.responses[new_response.code].blank?
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# self
|
40
|
+
# end
|
41
|
+
|
42
|
+
def add_default_responses(oas_route, security)
|
43
|
+
return self unless OasCore.config.set_default_responses
|
44
|
+
|
45
|
+
common_errors = determine_common_errors(oas_route, security)
|
46
|
+
add_responses_for_errors(common_errors)
|
47
|
+
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
def build
|
52
|
+
@responses
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def determine_common_errors(oas_route, security)
|
58
|
+
common_errors = []
|
59
|
+
common_errors.push(:unauthorized, :forbidden, :internal_server_error) if security
|
60
|
+
|
61
|
+
case oas_route.method_name
|
62
|
+
when 'show', 'destroy'
|
63
|
+
common_errors.push(:not_found)
|
64
|
+
when 'create'
|
65
|
+
common_errors.push(:unprocessable_entity)
|
66
|
+
when 'update'
|
67
|
+
common_errors.push(:not_found, :unprocessable_entity)
|
68
|
+
end
|
69
|
+
|
70
|
+
OasCore.config.possible_default_responses & common_errors
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_responses_for_errors(errors)
|
74
|
+
errors.each do |error|
|
75
|
+
response_body = resolve_response_body(error)
|
76
|
+
content = @content_builder.new(@specification,
|
77
|
+
:outgoing).with_schema(@json_schema_generator.process_string(response_body)[:json_schema]).build
|
78
|
+
code = @utils.status_to_integer(error)
|
79
|
+
response = @response_builder.new(@specification).with_code(code).with_description(@utils.get_definition(code)).with_content(content).build
|
80
|
+
|
81
|
+
@responses.add_response(response) if @responses.responses[response.code].nil?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def resolve_response_body(error)
|
86
|
+
OasCore.config.public_send("response_body_of_#{error}")
|
87
|
+
rescue StandardError
|
88
|
+
OasCore.config.response_body_of_default
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OasCore
|
4
|
+
module Builders
|
5
|
+
class SpecificationBuilder
|
6
|
+
def initialize
|
7
|
+
@specification = Spec::Specification.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def with_oas_routes(oas_routes)
|
11
|
+
grouped_routes = oas_routes.group_by(&:path)
|
12
|
+
grouped_routes.each do |path, routes|
|
13
|
+
@specification.paths.add_path(path, routes)
|
14
|
+
end
|
15
|
+
|
16
|
+
self
|
17
|
+
end
|
18
|
+
|
19
|
+
def build
|
20
|
+
@specification
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OasCore
|
4
|
+
class Configuration
|
5
|
+
attr_accessor :info,
|
6
|
+
:layout,
|
7
|
+
:default_tags_from,
|
8
|
+
:autodiscover_request_body,
|
9
|
+
:autodiscover_responses,
|
10
|
+
:api_path,
|
11
|
+
:ignored_actions,
|
12
|
+
:security_schemas,
|
13
|
+
:authenticate_all_routes_by_default,
|
14
|
+
:set_default_responses,
|
15
|
+
:possible_default_responses,
|
16
|
+
:http_verbs,
|
17
|
+
:use_model_names,
|
18
|
+
:rapidoc_theme
|
19
|
+
|
20
|
+
attr_reader :servers, :tags, :security_schema, :include_mode, :response_body_of_default
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@info = Spec::Info.new
|
24
|
+
@layout = false
|
25
|
+
@servers = default_servers
|
26
|
+
@tags = []
|
27
|
+
@swagger_version = '3.1.0'
|
28
|
+
@default_tags_from = :namespace
|
29
|
+
@autodiscover_request_body = true
|
30
|
+
@autodiscover_responses = true
|
31
|
+
@api_path = '/'
|
32
|
+
@ignored_actions = []
|
33
|
+
@authenticate_all_routes_by_default = true
|
34
|
+
@security_schema = nil
|
35
|
+
@security_schemas = {}
|
36
|
+
@set_default_responses = true
|
37
|
+
@possible_default_responses = %i[not_found unauthorized forbidden internal_server_error
|
38
|
+
unprocessable_entity]
|
39
|
+
@http_verbs = %i[get post put patch delete]
|
40
|
+
@response_body_of_default = 'Hash{ status: !Integer, error: String }'
|
41
|
+
@use_model_names = false
|
42
|
+
@rapidoc_theme = :rails
|
43
|
+
@include_mode = :all
|
44
|
+
|
45
|
+
@possible_default_responses.each do |response|
|
46
|
+
method_name = "response_body_of_#{response}="
|
47
|
+
variable_name = "@response_body_of_#{response}"
|
48
|
+
|
49
|
+
define_singleton_method(method_name) do |value|
|
50
|
+
raise ArgumentError, "#{method_name} must be a String With a valid object" unless value.is_a?(String)
|
51
|
+
|
52
|
+
OasCore::JsonSchemaGenerator.parse_type(value)
|
53
|
+
instance_variable_set(variable_name, value)
|
54
|
+
end
|
55
|
+
|
56
|
+
define_singleton_method("response_body_of_#{response}") do
|
57
|
+
instance_variable_get(variable_name) || @response_body_of_default
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def security_schema=(value)
|
63
|
+
return unless (security_schema = DEFAULT_SECURITY_SCHEMES[value])
|
64
|
+
|
65
|
+
@security_schemas = { value => security_schema }
|
66
|
+
end
|
67
|
+
|
68
|
+
def default_servers
|
69
|
+
[Spec::Server.new(url: 'http://localhost:3000', description: 'Rails Default Development Server')]
|
70
|
+
end
|
71
|
+
|
72
|
+
def servers=(value)
|
73
|
+
@servers = value.map { |s| Spec::Server.new(url: s[:url], description: s[:description]) }
|
74
|
+
end
|
75
|
+
|
76
|
+
def tags=(value)
|
77
|
+
@tags = value.map { |t| Spec::Tag.new(name: t[:name], description: t[:description]) }
|
78
|
+
end
|
79
|
+
|
80
|
+
def excluded_columns_incoming
|
81
|
+
%i[id created_at updated_at deleted_at]
|
82
|
+
end
|
83
|
+
|
84
|
+
def excluded_columns_outgoing
|
85
|
+
[]
|
86
|
+
end
|
87
|
+
|
88
|
+
def include_mode=(value)
|
89
|
+
valid_modes = %i[all with_tags explicit]
|
90
|
+
raise ArgumentError, "include_mode must be one of #{valid_modes}" unless valid_modes.include?(value)
|
91
|
+
|
92
|
+
@include_mode = value
|
93
|
+
end
|
94
|
+
|
95
|
+
def response_body_of_default=(value)
|
96
|
+
raise ArgumentError, 'response_body_of_default must be a String With a valid object' unless value.is_a?(String)
|
97
|
+
|
98
|
+
OasCore::JsonSchemaGenerator.parse_type(value)
|
99
|
+
@response_body_of_default = value
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
DEFAULT_SECURITY_SCHEMES = {
|
104
|
+
api_key_cookie: {
|
105
|
+
type: 'apiKey',
|
106
|
+
in: 'cookie',
|
107
|
+
name: 'api_key',
|
108
|
+
description: 'An API key that will be supplied in a named cookie.'
|
109
|
+
},
|
110
|
+
api_key_header: {
|
111
|
+
type: 'apiKey',
|
112
|
+
in: 'header',
|
113
|
+
name: 'X-API-Key',
|
114
|
+
description: 'An API key that will be supplied in a named header.'
|
115
|
+
},
|
116
|
+
api_key_query: {
|
117
|
+
type: 'apiKey',
|
118
|
+
in: 'query',
|
119
|
+
name: 'apiKey',
|
120
|
+
description: 'An API key that will be supplied in a named query parameter.'
|
121
|
+
},
|
122
|
+
basic: {
|
123
|
+
type: 'http',
|
124
|
+
scheme: 'basic',
|
125
|
+
description: "Basic auth that takes a base64'd combination of `user:password`."
|
126
|
+
},
|
127
|
+
bearer: {
|
128
|
+
type: 'http',
|
129
|
+
scheme: 'bearer',
|
130
|
+
description: 'A bearer token that will be supplied within an `Authorization` header as `bearer <token>`.'
|
131
|
+
},
|
132
|
+
bearer_jwt: {
|
133
|
+
type: 'http',
|
134
|
+
scheme: 'bearer',
|
135
|
+
bearerFormat: 'JWT',
|
136
|
+
description: 'A bearer token that will be supplied within an `Authorization` header as `bearer <token>`. In this case, the format of the token is specified as JWT.'
|
137
|
+
},
|
138
|
+
mutual_tls: {
|
139
|
+
type: 'mutualTLS',
|
140
|
+
description: 'Requires a specific mutual TLS certificate to use when making an HTTP request.'
|
141
|
+
}
|
142
|
+
}.freeze
|
143
|
+
end
|