zero-rails_openapi 1.0.0
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/.gitignore +13 -0
- data/.rspec +3 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +35 -0
- data/LICENSE.txt +21 -0
- data/README.md +245 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/examples/examples_controller.rb +36 -0
- data/lib/examples/open_api.rb +87 -0
- data/lib/oas_objs/helpers.rb +41 -0
- data/lib/oas_objs/media_type_obj.rb +86 -0
- data/lib/oas_objs/param_obj.rb +83 -0
- data/lib/oas_objs/ref_obj.rb +29 -0
- data/lib/oas_objs/request_body_obj.rb +54 -0
- data/lib/oas_objs/response_obj.rb +44 -0
- data/lib/oas_objs/schema_obj.rb +187 -0
- data/lib/open_api.rb +9 -0
- data/lib/open_api/config.rb +34 -0
- data/lib/open_api/dsl.rb +58 -0
- data/lib/open_api/dsl_inside_block.rb +175 -0
- data/lib/open_api/generator.rb +75 -0
- data/lib/open_api/version.rb +3 -0
- data/lib/takes/open_api.rake +6 -0
- data/zero-rails_openapi.gemspec +37 -0
- metadata +116 -0
data/bin/setup
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
class Api::V1::ExamplesController < Api::V1::BaseController
|
2
|
+
apis_set 'ExamplesController\'s APIs' do
|
3
|
+
schema :Dog => [ { id!: Integer, name: String }, dft: { id: 1, name: 'pet' } ]
|
4
|
+
path! :PathCompId => [ :id, Integer, desc: 'user id' ]
|
5
|
+
query! :QueryCompUuid => [ :product_uuid, { uuid: String, time: 'int32' }, desc: 'product uuid' ]
|
6
|
+
body! :RqBodyComp => [ :form ]
|
7
|
+
resp :RespComp => [ 'bad request', :json ]
|
8
|
+
end
|
9
|
+
|
10
|
+
open_api_set %i[index show], 'common response' do
|
11
|
+
response '567', 'query result export', :pdf, type: File
|
12
|
+
end
|
13
|
+
|
14
|
+
open_api :index, '(SUMMARY) this api blah blah ...' do
|
15
|
+
this_api_is_invalid! 'this api is expired!'
|
16
|
+
desc 'Optional multiline or single-line Markdown-formatted description',
|
17
|
+
id: 'user id',
|
18
|
+
email_addr: 'email_addr\'s desc'
|
19
|
+
email = 'git@github.com'
|
20
|
+
|
21
|
+
query! :id, Integer, enum: 0..5, length: [1, 2], pattern: /^[0-9]$/, range: {gt:0, le:5}
|
22
|
+
query! :done, Boolean, must_be: false, default: true, desc: 'must be false'
|
23
|
+
query :email_addr, String, lth: :ge_3, default: email # is_a: :email
|
24
|
+
# form! 'form', type: { id!: Integer, name: String }
|
25
|
+
file :pdf, 'desc: the media type is application/pdf'
|
26
|
+
|
27
|
+
response :success, 'success response', :json, type: :Dog
|
28
|
+
|
29
|
+
security :ApiKeyAuth
|
30
|
+
end
|
31
|
+
|
32
|
+
open_api :show do
|
33
|
+
param_ref :PathCompId, :QueryCompUuid
|
34
|
+
response_ref '123' => :RespComp, '223' => :RespComp
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'open_api'
|
2
|
+
|
3
|
+
OpenApi.configure do |c|
|
4
|
+
# [REQUIRED] The location where .json doc file will be output.
|
5
|
+
c.file_output_path = 'public/open_api'
|
6
|
+
|
7
|
+
# Everything about OAS3 is on https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md
|
8
|
+
# Getting started: https://swagger.io/docs/specification/basic-structure/
|
9
|
+
c.register_apis = {
|
10
|
+
homepage_api: {
|
11
|
+
# [REQUIRED] ZRO will scan all the descendants of the root_controller, then generate their docs.
|
12
|
+
root_controller: Api::V1::BaseController,
|
13
|
+
|
14
|
+
# [REQUIRED] Info Object: The info section contains API information
|
15
|
+
info: {
|
16
|
+
# [REQUIRED] The title of the application.
|
17
|
+
title: 'Zero Rails APIs',
|
18
|
+
# Description of the application.
|
19
|
+
description: 'API documentation of Zero-Rails Application. <br/>' \
|
20
|
+
'Optional multiline or single-line Markdown-formatted description ' \
|
21
|
+
'in [CommonMark](http://spec.commonmark.org/) or `HTML`.',
|
22
|
+
# A URL to the Terms of Service for the API. MUST be in the format of a URL.
|
23
|
+
# termsOfService: 'http://example.com/terms/',
|
24
|
+
# Contact Object: The contact information for the exposed API.
|
25
|
+
contact: {
|
26
|
+
# The identifying name of the contact person/organization.
|
27
|
+
name: 'API Support',
|
28
|
+
# The URL pointing to the contact information. MUST be in the format of a URL.
|
29
|
+
url: 'http://www.github.com',
|
30
|
+
# The email address of the contact person/organization. MUST be in the format of an email address.
|
31
|
+
email: 'git@gtihub.com'
|
32
|
+
},
|
33
|
+
# License Object: The license information for the exposed API.
|
34
|
+
license: {
|
35
|
+
# [REQUIRED] The license name used for the API.
|
36
|
+
name: 'Apache 2.0',
|
37
|
+
# A URL to the license used for the API. MUST be in the format of a URL.
|
38
|
+
url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
|
39
|
+
},
|
40
|
+
# [REQUIRED] The version of the OpenAPI document
|
41
|
+
# (which is distinct from the OpenAPI Specification version or the API implementation version).
|
42
|
+
version: '1.0.0'
|
43
|
+
},
|
44
|
+
|
45
|
+
# An array of Server Objects, which provide connectivity information to a target server.
|
46
|
+
# If the servers property is not provided, or is an empty array,
|
47
|
+
# the default value would be a Server Object with a url value of /.
|
48
|
+
# https://swagger.io/docs/specification/api-host-and-base-path/
|
49
|
+
# The servers section specifies the API server and base URL.
|
50
|
+
# You can define one or several servers, such as production and sandbox.
|
51
|
+
servers: [{
|
52
|
+
# [REQUIRED] A URL to the target host.
|
53
|
+
# This URL supports Server Variables and MAY be relative,
|
54
|
+
# to indicate that the host location is relative to the location where
|
55
|
+
# the OpenAPI document is being served.
|
56
|
+
url: 'http://localhost:2333/api/v1',
|
57
|
+
# An optional string describing the host designated by the URL.
|
58
|
+
description: 'Optional server description, e.g. Main (production) server'
|
59
|
+
},{
|
60
|
+
url: 'http://localhost:3332/api/v1',
|
61
|
+
description: 'Optional server description, e.g. Internal staging server for testing'
|
62
|
+
}],
|
63
|
+
|
64
|
+
# Authentication
|
65
|
+
# The securitySchemes and security keywords are used to describe the authentication methods used in your API.
|
66
|
+
# Security Scheme Object: An object to hold reusable Security Scheme Objects.
|
67
|
+
global_security_schemes: {
|
68
|
+
ApiKeyAuth: {
|
69
|
+
type: 'apiKey',
|
70
|
+
name: 'server_token',
|
71
|
+
in: 'query'
|
72
|
+
}
|
73
|
+
},
|
74
|
+
# Security Requirement Object
|
75
|
+
# A declaration of which security mechanisms can be used across the API.
|
76
|
+
# The list of values includes alternative security requirement objects that can be used.
|
77
|
+
# Only one of the security requirement objects need to be satisfied to authorize a request.
|
78
|
+
# Individual operations can override this definition.
|
79
|
+
# see: https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#securityRequirementObject
|
80
|
+
global_security: [{ ApiKeyAuth: [] }],
|
81
|
+
}
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
Object.const_set('Boolean', 'boolean') # Support `Boolean` writing in DSL
|
86
|
+
|
87
|
+
OpenApi.write_docs # Generate doc when Rails initializing
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module OpenApi
|
2
|
+
module Helpers
|
3
|
+
|
4
|
+
# TODO: comment-block doc
|
5
|
+
def truly_present?(obj)
|
6
|
+
obj == false || obj.present?
|
7
|
+
end
|
8
|
+
|
9
|
+
def value_present
|
10
|
+
Proc.new { |_, v| truly_present? v }
|
11
|
+
end
|
12
|
+
|
13
|
+
def assign(value)
|
14
|
+
@assign = value.is_a?(Symbol)? self.send("_#{value}") : value
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def all(*values)
|
19
|
+
@assign = values.compact.reduce({ }, :merge).keep_if &value_present
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_processed(who)
|
24
|
+
if who.is_a?(Symbol)
|
25
|
+
self.send("#{who}=", @assign)
|
26
|
+
else
|
27
|
+
processed[who.to_sym] = @assign
|
28
|
+
end if truly_present?(@assign)
|
29
|
+
|
30
|
+
processed
|
31
|
+
end
|
32
|
+
|
33
|
+
def to(who)
|
34
|
+
self[who.to_sym] = @assign if truly_present?(@assign)
|
35
|
+
end
|
36
|
+
|
37
|
+
def for_merge # to_processed
|
38
|
+
processed.tap { |it| it.merge! @assign if truly_present?(@assign) }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'oas_objs/schema_obj'
|
2
|
+
|
3
|
+
module OpenApi
|
4
|
+
module DSL
|
5
|
+
# https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#media-type-object
|
6
|
+
class MediaTypeObj < Hash
|
7
|
+
attr_accessor :media_type, :schema
|
8
|
+
def initialize(media_type, schema_hash)
|
9
|
+
self.media_type = media_type_mapping media_type
|
10
|
+
self.schema = SchemaObj.new(schema_hash[:type], schema_hash)
|
11
|
+
end
|
12
|
+
|
13
|
+
def process
|
14
|
+
schema_processed = self.schema.process
|
15
|
+
schema = schema_processed.values.join.blank? ? { } : { schema: schema_processed }
|
16
|
+
media_type.nil? ? { } : { media_type => schema }
|
17
|
+
end
|
18
|
+
|
19
|
+
# https://swagger.io/docs/specification/media-types/
|
20
|
+
# https://en.wikipedia.org/wiki/Media_type
|
21
|
+
# https://zh.wikipedia.org/wiki/%E4%BA%92%E8%81%94%E7%BD%91%E5%AA%92%E4%BD%93%E7%B1%BB%E5%9E%8B
|
22
|
+
# https://www.iana.org/assignments/media-types/media-types.xhtml
|
23
|
+
def media_type_mapping(media_type)
|
24
|
+
return media_type if media_type.is_a? String
|
25
|
+
case media_type
|
26
|
+
when :app; 'application/*'
|
27
|
+
when :json; 'application/json'
|
28
|
+
when :xml; 'application/xml'
|
29
|
+
when :xwww; 'application/x-www-form-urlencoded'
|
30
|
+
when :pdf; 'application/pdf'
|
31
|
+
when :zip; 'application/zip'
|
32
|
+
when :gzip; 'application/gzip'
|
33
|
+
when :doc; 'application/msword'
|
34
|
+
when :docx; 'application/application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
35
|
+
when :xls; 'application/vnd.ms-excel'
|
36
|
+
when :xlsx; 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
|
37
|
+
when :ppt; 'application/vnd.ms-powerpoint'
|
38
|
+
when :pptx; 'application/vnd.openxmlformats-officedocument.presentationml.presentation'
|
39
|
+
# when :pdf; 'application/pdf'
|
40
|
+
when :form; 'multipart/form-data'; when :form_data; 'multipart/form-data'
|
41
|
+
when :text; 'text/*'
|
42
|
+
when :plain; 'text/plain; charset=utf-8'
|
43
|
+
when :html; 'text/html'
|
44
|
+
when :csv; 'text/csv'
|
45
|
+
when :image; 'image/*'
|
46
|
+
when :png; 'image/png'
|
47
|
+
when :jpeg; 'image/jpeg'
|
48
|
+
when :gif; 'image/gif'
|
49
|
+
when :audio; 'audio/*'
|
50
|
+
when :video; 'video/*'
|
51
|
+
else; nil
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
__END__
|
60
|
+
|
61
|
+
Media Type Examples
|
62
|
+
|
63
|
+
{
|
64
|
+
"application/json": {
|
65
|
+
"schema": {
|
66
|
+
"$ref": "#/components/schemas/Pet"
|
67
|
+
},
|
68
|
+
"examples": {
|
69
|
+
"cat" : {
|
70
|
+
"summary": "An example of a cat",
|
71
|
+
"value":
|
72
|
+
{
|
73
|
+
"name": "Fluffy",
|
74
|
+
"petType": "Cat",
|
75
|
+
"color": "White",
|
76
|
+
"gender": "male",
|
77
|
+
"breed": "Persian"
|
78
|
+
}
|
79
|
+
},
|
80
|
+
"frog": {
|
81
|
+
"$ref": "#/components/examples/frog-example"
|
82
|
+
}
|
83
|
+
}
|
84
|
+
}
|
85
|
+
}
|
86
|
+
}
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'oas_objs/helpers'
|
2
|
+
require 'oas_objs/schema_obj'
|
3
|
+
|
4
|
+
module OpenApi
|
5
|
+
module DSL
|
6
|
+
class ParamObj < Hash
|
7
|
+
include Helpers
|
8
|
+
|
9
|
+
attr_accessor :processed, :schema
|
10
|
+
def initialize(name, param_type, type, required, schema_hash)
|
11
|
+
self.processed = {
|
12
|
+
name: name,
|
13
|
+
in: param_type,
|
14
|
+
required: "#{required}".match?(/req/),
|
15
|
+
}
|
16
|
+
self.schema = SchemaObj.new(type, schema_hash)
|
17
|
+
self.merge! schema_hash
|
18
|
+
end
|
19
|
+
|
20
|
+
def process
|
21
|
+
assign(_desc).to_processed 'description'
|
22
|
+
processed.tap { |it| it[:schema] = schema.process_for name }
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
# Getters and Setters of the original values that was passed to param()
|
27
|
+
# This mapping allows user to select the aliases in DSL writing,
|
28
|
+
# without increasing the complexity of the implementation.
|
29
|
+
{ # SELF_MAPPING
|
30
|
+
_range: %i[range number_range],
|
31
|
+
_length: %i[length lth ],
|
32
|
+
_desc: %i[desc description ],
|
33
|
+
}.each do |key, aliases|
|
34
|
+
define_method key do
|
35
|
+
aliases.each { |alias_name| self[key] ||= self[alias_name] } if self[key].nil?
|
36
|
+
self[key]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
|
41
|
+
# Interfaces for directly taking the info what you focus on,
|
42
|
+
# The next step you may want to verify the parameters based on these infos.
|
43
|
+
# The implementation of the parameters validator, see:
|
44
|
+
# TODO
|
45
|
+
alias_method :range, :_range
|
46
|
+
alias_method :length, :_length
|
47
|
+
{ # INTERFACE_MAPPING
|
48
|
+
name: %i[name ],
|
49
|
+
required: %i[required ],
|
50
|
+
in: %i[in ],
|
51
|
+
enum: %i[schema enum ],
|
52
|
+
pattern: %i[schema pattern],
|
53
|
+
regexp: %i[schema pattern],
|
54
|
+
type: %i[schema type ],
|
55
|
+
is: %i[schema format ],
|
56
|
+
}.each do |method, path|
|
57
|
+
define_method method do path.inject(processed, &:[]) end # Get value from hash by key path
|
58
|
+
end
|
59
|
+
alias_method :required?, :required
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
__END__
|
66
|
+
|
67
|
+
Parameter Object Examples
|
68
|
+
A header parameter with an array of 64 bit integer numbers:
|
69
|
+
|
70
|
+
{
|
71
|
+
"name": "token",
|
72
|
+
"in": "header",
|
73
|
+
"description": "token to be passed as a header",
|
74
|
+
"required": true,
|
75
|
+
"schema": {
|
76
|
+
"type": "array",
|
77
|
+
"items": {
|
78
|
+
"type": "integer",
|
79
|
+
"format": "int64"
|
80
|
+
}
|
81
|
+
},
|
82
|
+
"style": "simple"
|
83
|
+
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'oas_objs/media_type_obj'
|
2
|
+
require 'oas_objs/helpers'
|
3
|
+
|
4
|
+
module OpenApi
|
5
|
+
module DSL
|
6
|
+
# https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#referenceObject
|
7
|
+
class RefObj < Hash
|
8
|
+
include Helpers
|
9
|
+
|
10
|
+
attr_accessor :processed
|
11
|
+
def initialize(ref_to, component_key)
|
12
|
+
self.processed = {
|
13
|
+
'$ref': "#components/#{ref_to}s/#{component_key}"
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
def process; processed; end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
__END__
|
24
|
+
|
25
|
+
Reference Object Example
|
26
|
+
|
27
|
+
{
|
28
|
+
"$ref": "#/components/schemas/Pet"
|
29
|
+
}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'oas_objs/media_type_obj'
|
2
|
+
require 'oas_objs/helpers'
|
3
|
+
|
4
|
+
module OpenApi
|
5
|
+
module DSL
|
6
|
+
# https://swagger.io/docs/specification/describing-request-body/
|
7
|
+
# https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#requestBodyObject
|
8
|
+
class RequestBodyObj < Hash
|
9
|
+
include Helpers
|
10
|
+
|
11
|
+
attr_accessor :processed, :media_type
|
12
|
+
def initialize(required, media_type, desc, schema_hash)
|
13
|
+
self.media_type = MediaTypeObj.new(media_type, schema_hash)
|
14
|
+
self.processed = { required: "#{required}".match?(/req/), description: desc }
|
15
|
+
end
|
16
|
+
|
17
|
+
def process
|
18
|
+
assign(media_type.process).to_processed 'content'
|
19
|
+
processed
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
__END__
|
27
|
+
|
28
|
+
Request Body Examples
|
29
|
+
A request body with a referenced model definition.
|
30
|
+
|
31
|
+
{
|
32
|
+
"description": "user to add to the system",
|
33
|
+
"content": {
|
34
|
+
"application/json": {
|
35
|
+
"schema": {
|
36
|
+
"$ref": "#/components/schemas/User"
|
37
|
+
},
|
38
|
+
"examples": {
|
39
|
+
"user" : {
|
40
|
+
"summary": "User Example",
|
41
|
+
"externalValue": "http://foo.bar/examples/user-example.json"
|
42
|
+
}
|
43
|
+
}
|
44
|
+
},
|
45
|
+
"*/*": {
|
46
|
+
"examples": {
|
47
|
+
"user" : {
|
48
|
+
"summary": "User example in other format",
|
49
|
+
"externalValue": "http://foo.bar/examples/user-example.whatever"
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'oas_objs/media_type_obj'
|
2
|
+
require 'oas_objs/helpers'
|
3
|
+
|
4
|
+
module OpenApi
|
5
|
+
module DSL
|
6
|
+
# https://github.com/OAI/OpenAPI-Specification/blob/OpenAPI.next/versions/3.0.0.md#responseObject
|
7
|
+
class ResponseObj < Hash
|
8
|
+
include Helpers
|
9
|
+
|
10
|
+
attr_accessor :processed, :code, :media_type
|
11
|
+
def initialize(code, desc, media_type, schema_hash)
|
12
|
+
self.code = "#{code}"
|
13
|
+
self.media_type = MediaTypeObj.new(media_type, schema_hash)
|
14
|
+
self.processed = { description: desc }
|
15
|
+
end
|
16
|
+
|
17
|
+
def process
|
18
|
+
assign(media_type.process).to_processed 'content'
|
19
|
+
{ code => processed }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
__END__
|
27
|
+
|
28
|
+
Response Object Examples
|
29
|
+
|
30
|
+
Response of an array of a complex type:
|
31
|
+
|
32
|
+
{
|
33
|
+
"description": "A complex object array response",
|
34
|
+
"content": {
|
35
|
+
"application/json": {
|
36
|
+
"schema": {
|
37
|
+
"type": "array",
|
38
|
+
"items": {
|
39
|
+
"$ref": "#/components/schemas/VeryComplexType"
|
40
|
+
}
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
}
|