docit 0.1.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/.github/ISSUE_TEMPLATE/bug_report.md +31 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +27 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
- data/.github/workflows/ci.yml +30 -0
- data/CHANGELOG.md +15 -0
- data/CODE_OF_CONDUCT.md +39 -0
- data/CONTRIBUTING.md +78 -0
- data/LICENSE.txt +21 -0
- data/README.md +416 -0
- data/Rakefile +12 -0
- data/app/controllers/docit/ui_controller.rb +56 -0
- data/config/routes.rb +6 -0
- data/lib/docit/builders/parameter_builder.rb +28 -0
- data/lib/docit/builders/request_body_builder.rb +45 -0
- data/lib/docit/builders/response_builder.rb +47 -0
- data/lib/docit/configuration.rb +66 -0
- data/lib/docit/doc_file.rb +56 -0
- data/lib/docit/dsl.rb +28 -0
- data/lib/docit/engine.rb +19 -0
- data/lib/docit/operation.rb +58 -0
- data/lib/docit/registry.rb +26 -0
- data/lib/docit/route_inspector.rb +60 -0
- data/lib/docit/schema_definition.rb +33 -0
- data/lib/docit/schema_generator.rb +187 -0
- data/lib/docit/version.rb +5 -0
- data/lib/docit.rb +48 -0
- data/lib/generators/docit/install/install_generator.rb +29 -0
- data/lib/generators/docit/install/templates/initializer.rb +18 -0
- data/sig/docit.rbs +4 -0
- metadata +90 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docit
|
|
4
|
+
class UiController < ActionController::Base
|
|
5
|
+
def index
|
|
6
|
+
render html: swagger_ui_html.html_safe, layout: false
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def spec
|
|
10
|
+
RouteInspector.eager_load_controllers!
|
|
11
|
+
render json: SchemaGenerator.generate
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def swagger_ui_html
|
|
17
|
+
spec_url = "#{request.base_url}#{Docit::Engine.routes.url_helpers.spec_path}"
|
|
18
|
+
escaped_title = ERB::Util.html_escape(Docit.configuration.title)
|
|
19
|
+
|
|
20
|
+
<<~HTML
|
|
21
|
+
<!DOCTYPE html>
|
|
22
|
+
<html lang="en">
|
|
23
|
+
<head>
|
|
24
|
+
<meta charset="UTF-8">
|
|
25
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
26
|
+
<title>#{escaped_title}</title>
|
|
27
|
+
<link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
|
|
28
|
+
<style>
|
|
29
|
+
html { box-sizing: border-box; overflow-y: scroll; }
|
|
30
|
+
*, *:before, *:after { box-sizing: inherit; }
|
|
31
|
+
body { margin: 0; background: #fafafa; }
|
|
32
|
+
</style>
|
|
33
|
+
</head>
|
|
34
|
+
<body>
|
|
35
|
+
<div id="swagger-ui"></div>
|
|
36
|
+
<script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
|
|
37
|
+
<script>
|
|
38
|
+
SwaggerUIBundle({
|
|
39
|
+
url: "#{spec_url}",
|
|
40
|
+
dom_id: '#swagger-ui',
|
|
41
|
+
presets: [
|
|
42
|
+
SwaggerUIBundle.presets.apis,
|
|
43
|
+
SwaggerUIBundle.SwaggerUIStandalonePreset
|
|
44
|
+
],
|
|
45
|
+
layout: "BaseLayout",
|
|
46
|
+
deepLinking: true,
|
|
47
|
+
showExtensions: true,
|
|
48
|
+
showCommonExtensions: true
|
|
49
|
+
})
|
|
50
|
+
</script>
|
|
51
|
+
</body>
|
|
52
|
+
</html>
|
|
53
|
+
HTML
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/config/routes.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docit
|
|
4
|
+
module Builders
|
|
5
|
+
# Collects query, path, and header parameters for an operation.
|
|
6
|
+
class ParameterBuilder
|
|
7
|
+
attr_reader :params
|
|
8
|
+
|
|
9
|
+
def initialize
|
|
10
|
+
@params = []
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def add(name, location:, type: :string, required: false, description: nil, example: nil, enum: nil, **opts)
|
|
14
|
+
param = {
|
|
15
|
+
name: name.to_s,
|
|
16
|
+
in: location.to_s,
|
|
17
|
+
required: required,
|
|
18
|
+
schema: { type: type.to_s }
|
|
19
|
+
}
|
|
20
|
+
param[:description] = description if description
|
|
21
|
+
param[:schema][:enum] = enum if enum
|
|
22
|
+
param[:schema][:example] = example if example
|
|
23
|
+
param[:schema].merge!(opts)
|
|
24
|
+
@params << param
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docit
|
|
4
|
+
module Builders
|
|
5
|
+
# Builds the schema for a request body, including properties,
|
|
6
|
+
# required fields, and schema references.
|
|
7
|
+
class RequestBodyBuilder
|
|
8
|
+
attr_reader :properties, :required, :content_type, :schema_ref
|
|
9
|
+
|
|
10
|
+
def initialize(required: false, content_type: "application/json")
|
|
11
|
+
@required = required
|
|
12
|
+
@content_type = content_type
|
|
13
|
+
@properties = []
|
|
14
|
+
@schema_ref = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def schema(ref:)
|
|
18
|
+
@schema_ref = ref.to_sym
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def property(name, type:, required: false, format: nil, example: nil, enum: nil, description: nil, items: nil,
|
|
22
|
+
**opts, &block)
|
|
23
|
+
prop = { name: name, type: type, required: required }
|
|
24
|
+
prop[:format] = format if format
|
|
25
|
+
prop[:example] = example if example
|
|
26
|
+
prop[:enum] = enum if enum
|
|
27
|
+
prop[:description] = description if description
|
|
28
|
+
prop[:items] = items if items
|
|
29
|
+
prop.merge!(opts)
|
|
30
|
+
|
|
31
|
+
if block_given?
|
|
32
|
+
nested = self.class.new(required: @required, content_type: @content_type)
|
|
33
|
+
nested.instance_eval(&block)
|
|
34
|
+
prop[:children] = nested.properties
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
@properties << prop
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def required_properties
|
|
41
|
+
@properties.select { |prop| prop[:required] }.map { |prop| prop[:name].to_s }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docit
|
|
4
|
+
module Builders
|
|
5
|
+
# Builds the schema for a single HTTP response, including properties,
|
|
6
|
+
# examples, and schema references.
|
|
7
|
+
class ResponseBuilder
|
|
8
|
+
attr_reader :status, :description, :properties, :examples, :schema_ref
|
|
9
|
+
|
|
10
|
+
def initialize(status:, description:)
|
|
11
|
+
@status = status
|
|
12
|
+
@description = description
|
|
13
|
+
@properties = []
|
|
14
|
+
@examples = []
|
|
15
|
+
@schema_ref = nil
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def schema(ref:)
|
|
19
|
+
@schema_ref = ref.to_sym
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def property(name, type:, format: nil, example: nil, enum: nil, description: nil, items: nil, **opts, &block)
|
|
23
|
+
prop = { name: name, type: type }
|
|
24
|
+
prop[:format] = format if format
|
|
25
|
+
prop[:example] = example if example
|
|
26
|
+
prop[:enum] = enum if enum
|
|
27
|
+
prop[:description] = description if description
|
|
28
|
+
prop[:items] = items if items
|
|
29
|
+
prop.merge!(opts)
|
|
30
|
+
|
|
31
|
+
if block_given?
|
|
32
|
+
nested = self.class.new(status: @status, description: @description)
|
|
33
|
+
nested.instance_eval(&block)
|
|
34
|
+
prop[:children] = nested.properties
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
@properties << prop
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def example(name, value, description: nil)
|
|
41
|
+
ex = { name: name, value: value }
|
|
42
|
+
ex[:description] = description if description
|
|
43
|
+
@examples << ex
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docit
|
|
4
|
+
# Holds global API documentation settings: metadata, authentication, tags, and servers.
|
|
5
|
+
class Configuration
|
|
6
|
+
attr_accessor :title, :version, :description, :base_url
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@title = "API Documentation"
|
|
10
|
+
@version = "1.0.0"
|
|
11
|
+
@description = ""
|
|
12
|
+
@base_url = "/"
|
|
13
|
+
@security_schemes = {}
|
|
14
|
+
@tags = []
|
|
15
|
+
@servers = []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def auth(type, **options)
|
|
19
|
+
case type.to_s.downcase
|
|
20
|
+
when "basic"
|
|
21
|
+
@security_schemes[:basic_auth] = {
|
|
22
|
+
type: "http",
|
|
23
|
+
scheme: "basic"
|
|
24
|
+
}
|
|
25
|
+
when "bearer"
|
|
26
|
+
@security_schemes[:bearer_auth] = {
|
|
27
|
+
type: "http",
|
|
28
|
+
scheme: "bearer",
|
|
29
|
+
bearerFormat: options[:bearer_format] || "JWT"
|
|
30
|
+
}
|
|
31
|
+
when "api_key"
|
|
32
|
+
@security_schemes[:api_key] = {
|
|
33
|
+
type: "apiKey",
|
|
34
|
+
name: options[:name] || "X-API-Key",
|
|
35
|
+
in: options[:location] || "header"
|
|
36
|
+
}
|
|
37
|
+
else
|
|
38
|
+
raise ArgumentError, "Unsupported auth type: #{type}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def security_schemes
|
|
43
|
+
@security_schemes.dup
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def tag(name, description: nil)
|
|
47
|
+
entry = { name: name.to_s }
|
|
48
|
+
entry[:description] = description if description
|
|
49
|
+
@tags << entry
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def tags
|
|
53
|
+
@tags.dup
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def server(url, description: nil)
|
|
57
|
+
entry = { url: url.to_s }
|
|
58
|
+
entry[:description] = description if description
|
|
59
|
+
@servers << entry
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def servers
|
|
63
|
+
@servers.dup
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docit
|
|
4
|
+
# DocFile lets you define API documentation in separate files,
|
|
5
|
+
# keeping your controllers clean. Extend any module with DocFile,
|
|
6
|
+
# then use `doc :action_name do ... end` to define docs.
|
|
7
|
+
#
|
|
8
|
+
# Usage:
|
|
9
|
+
#
|
|
10
|
+
# # app/docs/users_docs.rb
|
|
11
|
+
# module UsersDocs
|
|
12
|
+
# extend Docit::DocFile
|
|
13
|
+
#
|
|
14
|
+
# doc :index do
|
|
15
|
+
# summary "List users"
|
|
16
|
+
# tags "Users"
|
|
17
|
+
# response 200, "Users retrieved"
|
|
18
|
+
# end
|
|
19
|
+
#
|
|
20
|
+
# doc :create do
|
|
21
|
+
# summary "Create a user"
|
|
22
|
+
# tags "Users"
|
|
23
|
+
# request_body required: true do
|
|
24
|
+
# property :email, type: :string, required: true
|
|
25
|
+
# end
|
|
26
|
+
# response 201, "User created"
|
|
27
|
+
# end
|
|
28
|
+
# end
|
|
29
|
+
#
|
|
30
|
+
# # app/controllers/users_controller.rb
|
|
31
|
+
# class UsersController < ApplicationController
|
|
32
|
+
# use_docs UsersDocs
|
|
33
|
+
#
|
|
34
|
+
# def index; end
|
|
35
|
+
# def create; end
|
|
36
|
+
# end
|
|
37
|
+
#
|
|
38
|
+
module DocFile
|
|
39
|
+
def self.extended(base)
|
|
40
|
+
base.instance_variable_set(:@_docs, {})
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# The block receives the same DSL as swagger_doc.
|
|
44
|
+
def doc(action, &block)
|
|
45
|
+
@_docs[action.to_sym] = block
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def [](action)
|
|
49
|
+
@_docs[action.to_sym]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def actions
|
|
53
|
+
@_docs.keys
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/docit/dsl.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docit
|
|
4
|
+
# Included in all Rails controllers via the Engine.
|
|
5
|
+
# Provides +swagger_doc+ and +use_docs+ class methods.
|
|
6
|
+
module DSL
|
|
7
|
+
def self.included(base)
|
|
8
|
+
base.extend(ClassMethods)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
module ClassMethods
|
|
12
|
+
def swagger_doc(action, &block)
|
|
13
|
+
operation = Operation.new(
|
|
14
|
+
controller: name,
|
|
15
|
+
action: action
|
|
16
|
+
)
|
|
17
|
+
operation.instance_eval(&block) if block_given?
|
|
18
|
+
Registry.register(operation)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def use_docs(doc_module)
|
|
22
|
+
doc_module.actions.each do |action|
|
|
23
|
+
swagger_doc(action, &doc_module[action])
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
data/lib/docit/engine.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "docit"
|
|
4
|
+
|
|
5
|
+
module Docit
|
|
6
|
+
class Engine < ::Rails::Engine
|
|
7
|
+
isolate_namespace Docit
|
|
8
|
+
|
|
9
|
+
initializer "docit.include_dsl" do
|
|
10
|
+
ActiveSupport.on_load(:action_controller_api) do
|
|
11
|
+
include Docit::DSL
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
ActiveSupport.on_load(:action_controller_base) do
|
|
15
|
+
include Docit::DSL
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docit
|
|
4
|
+
# Represents the documentation for a single controller action.
|
|
5
|
+
# Created by +swagger_doc+ and stored in the {Registry}.
|
|
6
|
+
class Operation
|
|
7
|
+
attr_reader :controller, :action, :_summary, :_description,
|
|
8
|
+
:_tags, :_responses, :_request_body, :_parameters,
|
|
9
|
+
:_security, :_deprecated
|
|
10
|
+
|
|
11
|
+
def initialize(controller:, action:)
|
|
12
|
+
@controller = controller
|
|
13
|
+
@action = action.to_s
|
|
14
|
+
@_tags = []
|
|
15
|
+
@_responses = []
|
|
16
|
+
@_parameters = Builders::ParameterBuilder.new
|
|
17
|
+
@_request_body = nil
|
|
18
|
+
@_security = []
|
|
19
|
+
@_deprecated = false
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def summary(text)
|
|
23
|
+
@_summary = text
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def description(text)
|
|
27
|
+
@_description = text
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def tags(*tags_list)
|
|
31
|
+
@_tags = tags_list.flatten
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def deprecated(value: true)
|
|
35
|
+
@_deprecated = value
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def security(scheme)
|
|
39
|
+
@_security << scheme
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def parameter(name, location:, type: :string, required: false, description: nil, **opts)
|
|
43
|
+
@_parameters.add(name, location: location, type: type, required: required, description: description, **opts)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def request_body(required: false, content_type: "application/json", &block)
|
|
47
|
+
builder = Builders::RequestBodyBuilder.new(required: required, content_type: content_type)
|
|
48
|
+
builder.instance_eval(&block) if block_given?
|
|
49
|
+
@_request_body = builder
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def response(status, description = "", &block)
|
|
53
|
+
builder = Builders::ResponseBuilder.new(status: status, description: description)
|
|
54
|
+
builder.instance_eval(&block) if block_given?
|
|
55
|
+
@_responses << builder
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docit
|
|
4
|
+
# Central store for all documented operations.
|
|
5
|
+
class Registry
|
|
6
|
+
class << self
|
|
7
|
+
def operations
|
|
8
|
+
@operations ||= []
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def register(operation)
|
|
12
|
+
operations << operation
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def find(controller:, action:)
|
|
16
|
+
operations.find do |op|
|
|
17
|
+
op.controller == controller && op.action == action
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def clear!
|
|
22
|
+
@operations = []
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docit
|
|
4
|
+
# Introspects Rails routes to map controller actions to HTTP paths and methods.
|
|
5
|
+
class RouteInspector
|
|
6
|
+
VALID_METHODS = %w[get post put patch delete].freeze
|
|
7
|
+
|
|
8
|
+
# Eagerly loads controller classes so swagger_doc/use_docs macros run before spec generation.
|
|
9
|
+
def self.eager_load_controllers!
|
|
10
|
+
return if defined?(Rails).nil? || Rails.application.routes.nil?
|
|
11
|
+
|
|
12
|
+
controller_paths = Rails.application.routes.routes.filter_map do |route|
|
|
13
|
+
route.defaults[:controller]
|
|
14
|
+
end.uniq
|
|
15
|
+
|
|
16
|
+
controller_paths.each do |path|
|
|
17
|
+
class_name = "#{path}_controller".camelize
|
|
18
|
+
class_name.constantize
|
|
19
|
+
rescue NameError
|
|
20
|
+
# Skip controllers that can't be loaded (e.g., Rails internal routes)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.routes_for(controller_name, action_name)
|
|
25
|
+
return [] if defined?(Rails).nil? || Rails.application.routes.nil?
|
|
26
|
+
|
|
27
|
+
action = action_name.to_s
|
|
28
|
+
|
|
29
|
+
# Convert Api::V1::AuthController to api/v1/auth
|
|
30
|
+
controller_path = controller_name.underscore.delete_suffix("_controller").gsub("::", "/")
|
|
31
|
+
|
|
32
|
+
Rails.application.routes.routes.filter_map do |route|
|
|
33
|
+
next if route.defaults[:controller] != controller_path
|
|
34
|
+
next if route.defaults[:action] != action
|
|
35
|
+
|
|
36
|
+
verb = extract_verb(route)
|
|
37
|
+
next if VALID_METHODS.exclude?(verb)
|
|
38
|
+
|
|
39
|
+
{ path: normalize_path(route.path.spec.to_s), method: verb }
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.extract_verb(route)
|
|
44
|
+
verb = route.verb
|
|
45
|
+
verb = verb.source if verb.is_a?(Regexp)
|
|
46
|
+
verb.to_s.downcase.gsub(/[^a-z]/, "")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private_class_method :extract_verb
|
|
50
|
+
|
|
51
|
+
def self.normalize_path(path)
|
|
52
|
+
path
|
|
53
|
+
.gsub("(.:format)", "")
|
|
54
|
+
.gsub(/\(\.?:(\w+)\)/, '{\1}')
|
|
55
|
+
.gsub(/:(\w+)/, '{\1}')
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private_class_method :normalize_path
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docit
|
|
4
|
+
# A reusable schema definition that can be referenced via +$ref+.
|
|
5
|
+
# Defined with {Docit.define_schema} and rendered under
|
|
6
|
+
# +components/schemas+ in the OpenAPI spec.
|
|
7
|
+
class SchemaDefinition
|
|
8
|
+
attr_reader :name, :properties
|
|
9
|
+
|
|
10
|
+
def initialize(name)
|
|
11
|
+
@name = name
|
|
12
|
+
@properties = []
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def property(prop_name, type:, format: nil, example: nil, enum: nil, description: nil, items: nil, **opts, &block)
|
|
16
|
+
prop = { name: prop_name, type: type }
|
|
17
|
+
prop[:format] = format if format
|
|
18
|
+
prop[:example] = example if example
|
|
19
|
+
prop[:enum] = enum if enum
|
|
20
|
+
prop[:description] = description if description
|
|
21
|
+
prop[:items] = items if items
|
|
22
|
+
prop.merge!(opts)
|
|
23
|
+
|
|
24
|
+
if block_given?
|
|
25
|
+
nested = self.class.new(:"#{@name}_#{prop_name}")
|
|
26
|
+
nested.instance_eval(&block)
|
|
27
|
+
prop[:children] = nested.properties
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
@properties << prop
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|