api_sketch 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/.gitignore +14 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +8 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +404 -0
- data/Rakefile +8 -0
- data/api_sketch.gemspec +30 -0
- data/bin/api_sketch +10 -0
- data/lib/api_sketch/config.rb +7 -0
- data/lib/api_sketch/dsl.rb +213 -0
- data/lib/api_sketch/error.rb +2 -0
- data/lib/api_sketch/examples_server.rb +54 -0
- data/lib/api_sketch/generators.rb +87 -0
- data/lib/api_sketch/helpers.rb +21 -0
- data/lib/api_sketch/model.rb +227 -0
- data/lib/api_sketch/renderers.rb +48 -0
- data/lib/api_sketch/runner.rb +92 -0
- data/lib/api_sketch/templates/bootstrap/assets/fonts/glyphicons-halflings-regular.eot +0 -0
- data/lib/api_sketch/templates/bootstrap/assets/fonts/glyphicons-halflings-regular.svg +229 -0
- data/lib/api_sketch/templates/bootstrap/assets/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/lib/api_sketch/templates/bootstrap/assets/fonts/glyphicons-halflings-regular.woff +0 -0
- data/lib/api_sketch/templates/bootstrap/assets/images/favicon.ico +0 -0
- data/lib/api_sketch/templates/bootstrap/assets/javascripts/bootstrap.js +2114 -0
- data/lib/api_sketch/templates/bootstrap/assets/javascripts/bootstrap.min.js +6 -0
- data/lib/api_sketch/templates/bootstrap/assets/javascripts/docs.min.js +24 -0
- data/lib/api_sketch/templates/bootstrap/assets/javascripts/ie10-viewport-bug-workaround.js +22 -0
- data/lib/api_sketch/templates/bootstrap/assets/javascripts/jquery-1.11.1.min.js +4 -0
- data/lib/api_sketch/templates/bootstrap/assets/stylesheets/bootstrap-theme.css +442 -0
- data/lib/api_sketch/templates/bootstrap/assets/stylesheets/bootstrap-theme.css.map +1 -0
- data/lib/api_sketch/templates/bootstrap/assets/stylesheets/bootstrap-theme.min.css +5 -0
- data/lib/api_sketch/templates/bootstrap/assets/stylesheets/bootstrap.css +6203 -0
- data/lib/api_sketch/templates/bootstrap/assets/stylesheets/bootstrap.css.map +1 -0
- data/lib/api_sketch/templates/bootstrap/assets/stylesheets/bootstrap.min.css +5 -0
- data/lib/api_sketch/templates/bootstrap/assets/stylesheets/dashboard.css +130 -0
- data/lib/api_sketch/templates/bootstrap/resource.html.erb +215 -0
- data/lib/api_sketch/version.rb +3 -0
- data/lib/api_sketch.rb +19 -0
- data/spec/lib/dsl_spec.rb +474 -0
- data/spec/spec_helper.rb +1 -0
- metadata +187 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
class ApiSketch::DSL
|
|
2
|
+
|
|
3
|
+
COMPLEX_ATTRIBUTE_NAMES = [:headers, :parameters, :responses]
|
|
4
|
+
|
|
5
|
+
def initialize(definitions_dir=ApiSketch::Config[:definitions_dir])
|
|
6
|
+
@definitions_dir = definitions_dir
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def init!
|
|
10
|
+
Dir.glob("#{@definitions_dir}/**/*.rb").each do |file_path|
|
|
11
|
+
puts_info("\t read: #{file_path}")
|
|
12
|
+
binding.eval(File.open(File.expand_path(file_path)).read, file_path)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class AttributeParser
|
|
17
|
+
|
|
18
|
+
def initialize(container_type, &block)
|
|
19
|
+
@attribute_values = {}
|
|
20
|
+
@container_type = container_type
|
|
21
|
+
# INFO: Such long method name is used to ensure that we are would not have such value as key at hash
|
|
22
|
+
define_singleton_method(:set_attributes_as_hash_value_format, block)
|
|
23
|
+
set_attributes_as_hash_value_format
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def method_missing(method_name, *arguments, &block)
|
|
27
|
+
@attribute_values[method_name] = arguments.first || block
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_h
|
|
31
|
+
@attribute_values
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Attributes
|
|
38
|
+
|
|
39
|
+
TYPES = [:integer, :string, :float, :boolean, :datetime, :timestamp, :document, :array]
|
|
40
|
+
|
|
41
|
+
def initialize(container_type, &block)
|
|
42
|
+
@container_type = container_type
|
|
43
|
+
@params = []
|
|
44
|
+
define_singleton_method(:initialize_attributes, block)
|
|
45
|
+
initialize_attributes
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def to_a
|
|
49
|
+
@params
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def shared(name)
|
|
53
|
+
self.instance_eval(&::ApiSketch::Model::SharedBlock.find(name))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
TYPES.each do |type_name|
|
|
57
|
+
define_method(type_name) do |*args, &block|
|
|
58
|
+
name = args.first
|
|
59
|
+
if @container_type == :document
|
|
60
|
+
if name.nil? || name.empty? # key name is not provided
|
|
61
|
+
raise ::ApiSketch::Error.new, "Key inside document should have name"
|
|
62
|
+
end
|
|
63
|
+
elsif @container_type == :array
|
|
64
|
+
if (!name.nil? && !name.empty?) # key name is provided
|
|
65
|
+
raise ::ApiSketch::Error.new, "Array element can't have name"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
@params << self.class.build_by(type_name, name, &block)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
class << self
|
|
73
|
+
def build_by(data_type, attribute_name, &block)
|
|
74
|
+
options = {data_type: data_type}
|
|
75
|
+
options[:name] = attribute_name if attribute_name
|
|
76
|
+
case data_type
|
|
77
|
+
when :document, :array
|
|
78
|
+
::ApiSketch::Model::Attribute.new(::ApiSketch::DSL::ComplexAttributeParser.new(data_type, &block).to_h.merge(options))
|
|
79
|
+
else
|
|
80
|
+
::ApiSketch::Model::Attribute.new(::ApiSketch::DSL::AttributeParser.new(data_type, &block).to_h.merge(options))
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class ComplexAttributeParser < ApiSketch::DSL::AttributeParser
|
|
88
|
+
|
|
89
|
+
def method_missing(method_name, *arguments, &block)
|
|
90
|
+
if method_name == :content
|
|
91
|
+
@attribute_values[:content] = ApiSketch::DSL::Attributes.new(@container_type, &block).to_a
|
|
92
|
+
else
|
|
93
|
+
super(method_name, *arguments, &block)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class Headers
|
|
101
|
+
|
|
102
|
+
def initialize(&block)
|
|
103
|
+
@list = []
|
|
104
|
+
define_singleton_method(:initialize_headers_list, block)
|
|
105
|
+
initialize_headers_list
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def to_a
|
|
109
|
+
@list
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def add(name, &block)
|
|
113
|
+
@list << ::ApiSketch::Model::Header.new(::ApiSketch::DSL::AttributeParser.new(:document, &block).to_h.merge(name: name))
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
class Parameters
|
|
119
|
+
|
|
120
|
+
def initialize(&block)
|
|
121
|
+
@query = []
|
|
122
|
+
@body = []
|
|
123
|
+
@query_container_type = nil
|
|
124
|
+
@body_container_type = nil
|
|
125
|
+
define_singleton_method(:initialize_parameters_list, block)
|
|
126
|
+
initialize_parameters_list
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def to_h
|
|
130
|
+
{
|
|
131
|
+
query: @query,
|
|
132
|
+
body: @body,
|
|
133
|
+
query_container_type: @query_container_type,
|
|
134
|
+
body_container_type: @body_container_type
|
|
135
|
+
}
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def query(container_type, &block)
|
|
139
|
+
@query_container_type = container_type
|
|
140
|
+
@query += ::ApiSketch::DSL::Attributes.new(container_type, &block).to_a
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def body(container_type, &block)
|
|
144
|
+
@body_container_type = container_type
|
|
145
|
+
@body += ::ApiSketch::DSL::Attributes.new(container_type, &block).to_a
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
class Responses
|
|
151
|
+
|
|
152
|
+
def initialize(&block)
|
|
153
|
+
@list = []
|
|
154
|
+
define_singleton_method(:initialize_responses_list, block)
|
|
155
|
+
initialize_responses_list
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def to_a
|
|
159
|
+
@list
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def context(name, &block)
|
|
163
|
+
attributes = ::ApiSketch::DSL::AttributeParser.new(:root, &block).to_h
|
|
164
|
+
if attributes[:parameters]
|
|
165
|
+
params = ::ApiSketch::DSL::Parameters.new(&attributes[:parameters]).to_h
|
|
166
|
+
attributes[:parameters] = ::ApiSketch::Model::Parameters.new(params)
|
|
167
|
+
end
|
|
168
|
+
@list << ::ApiSketch::Model::Response.new(attributes.merge(name: name))
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def shared_block(name, block)
|
|
174
|
+
::ApiSketch::Model::SharedBlock.add(name, block)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def resource(name, &block)
|
|
178
|
+
attributes = get_attrs(name, &block)
|
|
179
|
+
|
|
180
|
+
COMPLEX_ATTRIBUTE_NAMES.each do |attribute_name|
|
|
181
|
+
block_value = attributes[attribute_name]
|
|
182
|
+
attributes[attribute_name] = get_complex_attribute(attribute_name, &block_value) if block_value
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Assign resource namespace
|
|
186
|
+
attributes[:namespace] ||= block.source_location[0].gsub(definitions_dir, "").gsub(".rb", "").split("/").reject { |ns| ns.nil? || ns == "" }.join("/")
|
|
187
|
+
|
|
188
|
+
::ApiSketch::Model::Resource.create(attributes)
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
private
|
|
193
|
+
def definitions_dir
|
|
194
|
+
@definitions_dir
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def get_attrs(name, &block)
|
|
198
|
+
::ApiSketch::DSL::AttributeParser.new(:root, &block).to_h.merge(name: name)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def get_complex_attribute(attribute_name, &block)
|
|
202
|
+
case attribute_name
|
|
203
|
+
when :headers
|
|
204
|
+
::ApiSketch::DSL::Headers.new(&block).to_a
|
|
205
|
+
when :parameters
|
|
206
|
+
params = ::ApiSketch::DSL::Parameters.new(&block).to_h
|
|
207
|
+
::ApiSketch::Model::Parameters.new(params)
|
|
208
|
+
when :responses
|
|
209
|
+
::ApiSketch::DSL::Responses.new(&block).to_a
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
class ApiSketch::ExamplesServer
|
|
2
|
+
def self.call(env)
|
|
3
|
+
new(env).response.finish
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def initialize(env)
|
|
7
|
+
@request = Rack::Request.new(env)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def response
|
|
11
|
+
if api_resource
|
|
12
|
+
api_response = if api_response_context
|
|
13
|
+
api_resource.responses.find { |rsp| rsp.name == api_response_context }
|
|
14
|
+
else
|
|
15
|
+
api_resource.responses.first
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
if api_response
|
|
19
|
+
Rack::Response.new do |response|
|
|
20
|
+
api_response.headers.each do |header|
|
|
21
|
+
response[header.name] = header.value
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
response['Content-Type'] = 'application/json'
|
|
25
|
+
|
|
26
|
+
response.status = Rack::Utils.status_code(api_response.http_status)
|
|
27
|
+
response.write(ApiSketch::ResponseRenderer.new(api_response.parameters.body, api_response.parameters.body_container_type).to_json)
|
|
28
|
+
end
|
|
29
|
+
else
|
|
30
|
+
api_sketch_message("No any responses defined for this resource", 404)
|
|
31
|
+
end
|
|
32
|
+
else
|
|
33
|
+
api_sketch_message("Resource is not Found", 404)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
def api_resource
|
|
39
|
+
@api_resource = if @request.params["api_sketch_resource_id"]
|
|
40
|
+
ApiSketch::Model::Resource.find(@request.params["api_sketch_resource_id"])
|
|
41
|
+
else
|
|
42
|
+
ApiSketch::Model::Resource.all.find { |res| res.http_method == @request.request_method && res.path }
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def api_response_context
|
|
47
|
+
@request.params["api_sketch_response_context"]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def api_sketch_message(message, status)
|
|
51
|
+
Rack::Response.new({"api_sketch" => message}.to_json, 404)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module ApiSketch::Generators
|
|
2
|
+
|
|
3
|
+
class Base
|
|
4
|
+
|
|
5
|
+
attr_accessor :definitions_dir, :documentation_dir
|
|
6
|
+
|
|
7
|
+
attr_reader :templates_folder
|
|
8
|
+
|
|
9
|
+
# TODO: Add here some validations for folders existance, etc
|
|
10
|
+
def initialize(options = {})
|
|
11
|
+
self.definitions_dir = options[:definitions_dir]
|
|
12
|
+
self.documentation_dir = options[:documentation_dir]
|
|
13
|
+
@templates_folder = File.expand_path("templates/#{self.class.name.split("::").last.downcase}", File.dirname(__FILE__))
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def generate!
|
|
17
|
+
puts_info("Load definitions")
|
|
18
|
+
load_definitions
|
|
19
|
+
puts_info("Create documentation directory")
|
|
20
|
+
puts_info("\t path: #{self.documentation_dir}")
|
|
21
|
+
create_documentation_directory
|
|
22
|
+
puts_info("Create documentation files")
|
|
23
|
+
create_documentation_files
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
def create_documentation_directory
|
|
28
|
+
FileUtils.rm_r(self.documentation_dir, :force => true)
|
|
29
|
+
FileUtils.mkdir_p(self.documentation_dir)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# TODO: This is unfinished sample file generator it should be more complex at some other generators
|
|
33
|
+
# Other generors should inherit from this class and implement this method
|
|
34
|
+
def create_documentation_files
|
|
35
|
+
raise "This method should be implemented at child class who inherits from ApiSketch::Generators::Base"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def load_definitions
|
|
39
|
+
ApiSketch::Model::Resource.reload!(self.definitions_dir)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Bootstrap < ApiSketch::Generators::Base
|
|
46
|
+
|
|
47
|
+
def initialize(options = {})
|
|
48
|
+
super(options)
|
|
49
|
+
@resource_template = ERB.new(File.read("#{self.templates_folder}/resource.html.erb"))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# This is defined here because it is related for this type of generator only
|
|
53
|
+
def filename_for(resource)
|
|
54
|
+
resource.id + '.html'
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
private
|
|
58
|
+
def copy_assets
|
|
59
|
+
# copy assets from template directory
|
|
60
|
+
source = File.join(self.templates_folder, 'assets')
|
|
61
|
+
target = File.join(self.documentation_dir, 'assets')
|
|
62
|
+
FileUtils.copy_entry(source, target)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def create_documentation_files
|
|
66
|
+
@generator = self
|
|
67
|
+
copy_assets
|
|
68
|
+
@resources = ApiSketch::Model::Resource.all
|
|
69
|
+
@resources.each do |resource|
|
|
70
|
+
@resource = resource
|
|
71
|
+
|
|
72
|
+
filename = File.join(self.documentation_dir, filename_for(@resource))
|
|
73
|
+
html_data = @resource_template.result(binding)
|
|
74
|
+
|
|
75
|
+
dir = File.dirname(filename)
|
|
76
|
+
unless File.directory?(dir)
|
|
77
|
+
puts_info("\t create directory: #{dir}")
|
|
78
|
+
FileUtils.mkdir_p(dir)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
puts_info("\t write: #{filename}")
|
|
82
|
+
File.open(filename, 'w+') { |file| file.write(html_data) }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Output info message to console.
|
|
2
|
+
#
|
|
3
|
+
# @param message [String] message to output
|
|
4
|
+
def puts_info(message)
|
|
5
|
+
$stdout.puts "\e[32m[INFO] #{message}\e[0m" if ApiSketch::Config[:debug]
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Output warning message to console.
|
|
9
|
+
#
|
|
10
|
+
# @param message [String] message to output
|
|
11
|
+
def puts_warning(message)
|
|
12
|
+
$stdout.puts "\e[33m[WARNING] #{message}\e[0m" if ApiSketch::Config[:debug]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Output error message to console.
|
|
16
|
+
#
|
|
17
|
+
# @param message [String] message to output
|
|
18
|
+
def puts_error(message)
|
|
19
|
+
$stderr.puts "\e[31m[ERROR] #{message}\e[0m" if ApiSketch::Config[:debug]
|
|
20
|
+
exit(1)
|
|
21
|
+
end
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
module ApiSketch::Model
|
|
2
|
+
|
|
3
|
+
class Base
|
|
4
|
+
|
|
5
|
+
attr_accessor :name, :description
|
|
6
|
+
|
|
7
|
+
def initialize(attributes = {})
|
|
8
|
+
attributes = default_values_hash.merge(attributes)
|
|
9
|
+
attributes.each do |attribute, value|
|
|
10
|
+
self.send("#{attribute}=", value)
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
def default_values_hash
|
|
16
|
+
{}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class Attribute < ApiSketch::Model::Base
|
|
23
|
+
attr_accessor :data_type, :value, :example, :required, :default, :content
|
|
24
|
+
|
|
25
|
+
def example_value(defaults_allowed=false)
|
|
26
|
+
value = self.example
|
|
27
|
+
value ||= example_value_default if defaults_allowed
|
|
28
|
+
|
|
29
|
+
value.respond_to?(:call) ? value.call : value
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# TODO: These default values should be configurable via DSL
|
|
33
|
+
# Some logic to defer value example from key name, - email from key with email part inside, etc.
|
|
34
|
+
def example_value_default
|
|
35
|
+
{
|
|
36
|
+
integer: lambda { rand(1000) + 1 },
|
|
37
|
+
string: lambda { "random_string_#{('A'..'Z').to_a.shuffle.first(8).join}" },
|
|
38
|
+
float: lambda { rand(100) + rand(100) * 0.01 },
|
|
39
|
+
boolean: lambda { [true, false].sample },
|
|
40
|
+
datetime: lambda { Time.now.strftime("%d-%m-%Y %H:%M:%S") },
|
|
41
|
+
timestamp: lambda { Time.now.to_i }
|
|
42
|
+
}[data_type]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def to_hash
|
|
46
|
+
{
|
|
47
|
+
data_type: self.data_type,
|
|
48
|
+
example_value: self.example_value,
|
|
49
|
+
required: !!self.required,
|
|
50
|
+
default: self.default,
|
|
51
|
+
content: self.content_to_hash
|
|
52
|
+
}
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def content_to_hash
|
|
56
|
+
if self.content
|
|
57
|
+
self.content.map do |item|
|
|
58
|
+
item.to_hash
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class Header < ApiSketch::Model::Base
|
|
67
|
+
attr_accessor :value, :example, :required
|
|
68
|
+
|
|
69
|
+
def example_value
|
|
70
|
+
self.example.respond_to?(:call) ? self.example.call : self.example
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class Parameters < ApiSketch::Model::Base
|
|
76
|
+
attr_accessor :query, :body, :query_container_type, :body_container_type
|
|
77
|
+
|
|
78
|
+
def initialize(attributes = {})
|
|
79
|
+
super(attributes)
|
|
80
|
+
self.query ||= []
|
|
81
|
+
self.body ||= []
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def as_full_names
|
|
85
|
+
fullname_params = self.class.new
|
|
86
|
+
[:query, :body].each do |param_location|
|
|
87
|
+
new_params = []
|
|
88
|
+
self.send(param_location).each do |param|
|
|
89
|
+
if param.data_type == :document
|
|
90
|
+
full_names_for(param, param.name, new_params)
|
|
91
|
+
else
|
|
92
|
+
new_params << param
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
fullname_params.send("#{param_location}=", new_params)
|
|
96
|
+
end
|
|
97
|
+
fullname_params
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
def full_names_for(param, name = "", new_params)
|
|
102
|
+
name = name.to_s # ensure that this value is always a string
|
|
103
|
+
if param.content.kind_of?(Array)
|
|
104
|
+
param.content.each do |attribute|
|
|
105
|
+
renamed_attribute = attribute.clone
|
|
106
|
+
renamed_attribute.name = name.empty? ? attribute.name.to_s : "#{name}[#{attribute.name}]"
|
|
107
|
+
if renamed_attribute.data_type == :document
|
|
108
|
+
full_names_for(renamed_attribute, renamed_attribute.name, new_params)
|
|
109
|
+
else
|
|
110
|
+
new_params << renamed_attribute
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class Resource < ApiSketch::Model::Base
|
|
119
|
+
|
|
120
|
+
attr_accessor :namespace, :action, :path, :http_method, :format, :headers, :parameters, :responses
|
|
121
|
+
|
|
122
|
+
# TODO: update this method to provide better id that is used as part of filename
|
|
123
|
+
def id
|
|
124
|
+
[self.namespace, self.action].reject { |v| v.nil? || v == "" }.join("/")
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
class << self
|
|
128
|
+
|
|
129
|
+
def create(attributes)
|
|
130
|
+
res = self.new(attributes)
|
|
131
|
+
res.send(:run_validations!)
|
|
132
|
+
self.add(res)
|
|
133
|
+
res
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def add(resource)
|
|
137
|
+
@resources ||= []
|
|
138
|
+
@resources << resource
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def reset!
|
|
142
|
+
@resources = []
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def reload!(definitions_dir)
|
|
146
|
+
self.reset!
|
|
147
|
+
ApiSketch::DSL.new(definitions_dir).init!
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def all
|
|
151
|
+
@resources ||= []
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def find(id)
|
|
155
|
+
self.all.find { |res| res.id == id }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def first
|
|
159
|
+
self.all.first
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def last
|
|
163
|
+
self.all.last
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def count
|
|
167
|
+
self.all.count
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
private
|
|
173
|
+
def default_values_hash
|
|
174
|
+
{
|
|
175
|
+
http_method: "GET",
|
|
176
|
+
format: "json",
|
|
177
|
+
headers: [],
|
|
178
|
+
parameters: ::ApiSketch::Model::Parameters.new,
|
|
179
|
+
responses: []
|
|
180
|
+
}
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def run_validations!
|
|
184
|
+
unless self.action =~ /\A\w*\z/
|
|
185
|
+
message = "'#{self.action}' is invalid action value"
|
|
186
|
+
# puts_error(message)
|
|
187
|
+
raise ::ApiSketch::Error, message
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
if self.class.find(self.id)
|
|
191
|
+
message = "'#{self.id}' is not unique id. Change values of 'namespace' and/or 'action' attributes"
|
|
192
|
+
# puts_error(message)
|
|
193
|
+
raise ::ApiSketch::Error, message
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class Response < ApiSketch::Model::Base
|
|
201
|
+
attr_accessor :http_status, :parameters, :format, :headers
|
|
202
|
+
|
|
203
|
+
private
|
|
204
|
+
def default_values_hash
|
|
205
|
+
{
|
|
206
|
+
format: "json",
|
|
207
|
+
headers: [],
|
|
208
|
+
parameters: ::ApiSketch::Model::Parameters.new
|
|
209
|
+
}
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
module SharedBlock
|
|
214
|
+
@list_hash = {}
|
|
215
|
+
|
|
216
|
+
class << self
|
|
217
|
+
def add(name, block)
|
|
218
|
+
@list_hash[name] = block
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def find(name)
|
|
222
|
+
@list_hash[name] || raise(::ApiSketch::Error, "Shared block '#{name}' is not defined")
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
class ApiSketch::ResponseRenderer
|
|
2
|
+
|
|
3
|
+
attr_reader :params, :container_type
|
|
4
|
+
|
|
5
|
+
def initialize(params, container_type)
|
|
6
|
+
@params = params
|
|
7
|
+
@container_type = container_type
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def to_h
|
|
11
|
+
render_content(params, container_type)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_json
|
|
15
|
+
self.to_h.to_json
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# TODO: Add this feature in future
|
|
19
|
+
# def to_xml
|
|
20
|
+
# XML conversion code here
|
|
21
|
+
# end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
def render_content(items, placeholder_type)
|
|
25
|
+
placeholder = case placeholder_type
|
|
26
|
+
when :array then []
|
|
27
|
+
when :document then {}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
items.each do |param, index|
|
|
31
|
+
value = if [:array, :document].include?(param.data_type) && param.content
|
|
32
|
+
render_content(param.content, param.data_type)
|
|
33
|
+
else
|
|
34
|
+
param.example_value(true)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
case placeholder_type
|
|
38
|
+
when :array
|
|
39
|
+
placeholder << value
|
|
40
|
+
when :document
|
|
41
|
+
placeholder[param.name] = value
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
placeholder
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
end
|