zero-rails_openapi 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|