apiture 0.2.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 +19 -0
- data/Gemfile +8 -0
- data/LICENSE +20 -0
- data/README.md +38 -0
- data/Rakefile +6 -0
- data/apiture.gemspec +30 -0
- data/lib/apiture.rb +24 -0
- data/lib/apiture/api_base.rb +27 -0
- data/lib/apiture/api_builder.rb +196 -0
- data/lib/apiture/api_error.rb +3 -0
- data/lib/apiture/api_group.rb +28 -0
- data/lib/apiture/data_model.rb +24 -0
- data/lib/apiture/endpoint.rb +25 -0
- data/lib/apiture/middleware/auth/api_key.rb +39 -0
- data/lib/apiture/middleware/auth/basic.rb +25 -0
- data/lib/apiture/middleware/auth/oauth2.rb +31 -0
- data/lib/apiture/middleware/convert_json_body.rb +15 -0
- data/lib/apiture/middleware/debug.rb +20 -0
- data/lib/apiture/middleware/set_body_parameter.rb +20 -0
- data/lib/apiture/middleware/set_header.rb +15 -0
- data/lib/apiture/middleware/set_parameter_base.rb +37 -0
- data/lib/apiture/middleware/set_path_parameter.rb +18 -0
- data/lib/apiture/middleware/set_query_parameter.rb +13 -0
- data/lib/apiture/middleware_builder.rb +15 -0
- data/lib/apiture/middleware_stack.rb +21 -0
- data/lib/apiture/request_context.rb +103 -0
- data/lib/apiture/swagger/data_type_field.rb +25 -0
- data/lib/apiture/swagger/definition.rb +20 -0
- data/lib/apiture/swagger/external_docs.rb +10 -0
- data/lib/apiture/swagger/info.rb +14 -0
- data/lib/apiture/swagger/node.rb +149 -0
- data/lib/apiture/swagger/operation.rb +32 -0
- data/lib/apiture/swagger/parameter.rb +35 -0
- data/lib/apiture/swagger/parser.rb +148 -0
- data/lib/apiture/swagger/path.rb +37 -0
- data/lib/apiture/swagger/property.rb +15 -0
- data/lib/apiture/swagger/security.rb +21 -0
- data/lib/apiture/swagger/security_definition.rb +23 -0
- data/lib/apiture/swagger/specification.rb +31 -0
- data/lib/apiture/uri.rb +36 -0
- data/lib/apiture/utils/inflections.rb +46 -0
- data/lib/apiture/version.rb +3 -0
- data/spec/apiture/api_builder_spec.rb +20 -0
- data/spec/apiture/swagger/parser_spec.rb +107 -0
- data/spec/apiture/swagger/specification_spec.rb +19 -0
- data/spec/apiture_spec.rb +228 -0
- data/spec/files/github.json +186 -0
- data/spec/files/harvest.json +75 -0
- data/spec/files/honeybadger.json +80 -0
- data/spec/files/mandrill.json +502 -0
- data/spec/files/pivotal_tracker.json +94 -0
- data/spec/files/slack.json +88 -0
- data/spec/files/uber.json +43 -0
- data/spec/fixtures/vcr_cassettes/github_checkAuthorization.yml +70 -0
- data/spec/fixtures/vcr_cassettes/github_getUser.yml +82 -0
- data/spec/fixtures/vcr_cassettes/harvest_invoiceList.yml +85 -0
- data/spec/fixtures/vcr_cassettes/honeybadger.yml +98 -0
- data/spec/fixtures/vcr_cassettes/mandrill_messageSend.yml +44 -0
- data/spec/fixtures/vcr_cassettes/mandrill_userPing.yml +44 -0
- data/spec/fixtures/vcr_cassettes/pivotal_tracker_create_story.yml +61 -0
- data/spec/fixtures/vcr_cassettes/slack.yml +43 -0
- data/spec/fixtures/vcr_cassettes/uber_getProducts.yml +60 -0
- data/spec/spec_helper.rb +11 -0
- metadata +241 -0
@@ -0,0 +1,149 @@
|
|
1
|
+
require 'apiture/utils/inflections'
|
2
|
+
|
3
|
+
module Apiture
|
4
|
+
module Swagger
|
5
|
+
class Node
|
6
|
+
include Apiture::Utils::Inflections
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def inherited(base)
|
10
|
+
base.instance_variable_set(:@attribute_names, attribute_names.dup)
|
11
|
+
base.instance_variable_set(:@list_names, list_names.dup)
|
12
|
+
base.instance_variable_set(:@hash_names, hash_names.dup)
|
13
|
+
end
|
14
|
+
|
15
|
+
def attribute_names
|
16
|
+
@attribute_names || []
|
17
|
+
end
|
18
|
+
|
19
|
+
def list_names
|
20
|
+
@list_names || []
|
21
|
+
end
|
22
|
+
|
23
|
+
def hash_names
|
24
|
+
@hash_names || []
|
25
|
+
end
|
26
|
+
|
27
|
+
def attribute(name, options = {})
|
28
|
+
name = name.to_sym
|
29
|
+
(@attribute_names ||= []) << name
|
30
|
+
attr_accessor name
|
31
|
+
if options[:type] == :boolean
|
32
|
+
define_method("#{name}?".to_sym) do
|
33
|
+
!!send(name)
|
34
|
+
end
|
35
|
+
elsif options[:symbolize]
|
36
|
+
define_method("#{name}=".to_sym) do |value|
|
37
|
+
instance_variable_set("@#{name}".to_sym, value.nil? ? nil : value.to_sym)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
(@validates_children ||= []) << name if options[:validate]
|
41
|
+
end
|
42
|
+
|
43
|
+
def list(name, options = {})
|
44
|
+
attr_accessor name
|
45
|
+
(@list_names ||= []) << name
|
46
|
+
(@validates_children ||= []) << name if options[:validate]
|
47
|
+
end
|
48
|
+
|
49
|
+
def hash(name, options = {})
|
50
|
+
attr_accessor name
|
51
|
+
(@hash_names ||= []) << name
|
52
|
+
(@validates_children ||= []) << name if options[:validate]
|
53
|
+
end
|
54
|
+
|
55
|
+
def collect_errors(node, all_errors = [])
|
56
|
+
if @validates_children
|
57
|
+
@validates_children.each do |name|
|
58
|
+
if attr_val = node.send(name)
|
59
|
+
if @hash_names && @hash_names.include?(name)
|
60
|
+
attr_val.each_pair { |k,v| v.collect_errors(all_errors) }
|
61
|
+
|
62
|
+
elsif @list_names && @list_names.include?(name)
|
63
|
+
attr_val.each { |v| v.collect_errors(all_errors) }
|
64
|
+
|
65
|
+
else
|
66
|
+
unless attr_val.respond_to? :collect_errors
|
67
|
+
raise "Expecting #{name} to be a node"
|
68
|
+
end
|
69
|
+
attr_val.collect_errors(all_errors)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
all_errors
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
attr_reader :errors
|
79
|
+
|
80
|
+
def initialize
|
81
|
+
@errors = []
|
82
|
+
end
|
83
|
+
|
84
|
+
def extensions
|
85
|
+
@extensions ||= {}
|
86
|
+
end
|
87
|
+
|
88
|
+
def validate
|
89
|
+
end
|
90
|
+
|
91
|
+
def valid?
|
92
|
+
validate
|
93
|
+
@errors.any?
|
94
|
+
end
|
95
|
+
|
96
|
+
def collect_errors(all_errors = [])
|
97
|
+
validate; all_errors.concat(errors)
|
98
|
+
self.class.collect_errors(self, all_errors)
|
99
|
+
end
|
100
|
+
|
101
|
+
def serializable_hash
|
102
|
+
h = {}
|
103
|
+
|
104
|
+
if self.class.attribute_names
|
105
|
+
self.class.attribute_names.each do |nm|
|
106
|
+
if v = __send__(nm)
|
107
|
+
h[camelize(nm, false)] = value_or_serializable_hash(v)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
if self.class.list_names
|
113
|
+
self.class.list_names.each do |nm|
|
114
|
+
if arr = __send__(nm)
|
115
|
+
result = arr.map do |v|
|
116
|
+
value_or_serializable_hash(v)
|
117
|
+
end
|
118
|
+
h[camelize(nm, false)] = result if result.any?
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
if self.class.hash_names
|
124
|
+
self.class.hash_names.each do |nm|
|
125
|
+
if value_hash = __send__(nm)
|
126
|
+
result = value_hash.reduce({}) do |m,(k,v)|
|
127
|
+
m[k] = value_or_serializable_hash(v); m
|
128
|
+
end
|
129
|
+
h[camelize(nm, false)] = result if result.any?
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
h
|
135
|
+
end
|
136
|
+
|
137
|
+
def to_json
|
138
|
+
MultiJson.dump(serializable_hash)
|
139
|
+
end
|
140
|
+
alias :to_s :to_json
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def value_or_serializable_hash(v)
|
145
|
+
v.respond_to?(:serializable_hash) ? v.serializable_hash : v
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'apiture/swagger/node'
|
2
|
+
require 'apiture/swagger/parameter'
|
3
|
+
|
4
|
+
module Apiture
|
5
|
+
module Swagger
|
6
|
+
class Operation < Node
|
7
|
+
attr_reader :id
|
8
|
+
alias :method_name :id
|
9
|
+
|
10
|
+
attribute :summary
|
11
|
+
attribute :description
|
12
|
+
attribute :external_docs, validate: true
|
13
|
+
attribute :operation_id
|
14
|
+
attribute :deprecated
|
15
|
+
|
16
|
+
list :tags
|
17
|
+
list :consumes
|
18
|
+
list :produces
|
19
|
+
list :parameters, validate: true
|
20
|
+
list :responses, validate: true
|
21
|
+
list :schemes
|
22
|
+
|
23
|
+
hash :security_definitions, validate: true
|
24
|
+
hash :security, validate: true
|
25
|
+
|
26
|
+
def initialize(id)
|
27
|
+
super()
|
28
|
+
@id = id
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'apiture/swagger/data_type_field'
|
2
|
+
|
3
|
+
module Apiture
|
4
|
+
module Swagger
|
5
|
+
class Parameter < DataTypeField
|
6
|
+
VALID_TYPES = %w(
|
7
|
+
string
|
8
|
+
number
|
9
|
+
integer
|
10
|
+
boolean
|
11
|
+
array
|
12
|
+
file
|
13
|
+
)
|
14
|
+
|
15
|
+
attribute :name
|
16
|
+
attribute :in, symbolize: true
|
17
|
+
attribute :description
|
18
|
+
attribute :required, type: :boolean
|
19
|
+
attribute :schema
|
20
|
+
|
21
|
+
def schema?
|
22
|
+
!!schema
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate
|
26
|
+
if self.in.nil?
|
27
|
+
errors << "in attribute is required"
|
28
|
+
end
|
29
|
+
if self.in == :path && !required?
|
30
|
+
errors << "Path parameters must be defined as required"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
require 'multi_json'
|
2
|
+
require 'yaml'
|
3
|
+
require 'apiture/utils/inflections'
|
4
|
+
require 'apiture/swagger/specification'
|
5
|
+
|
6
|
+
|
7
|
+
module Apiture
|
8
|
+
module Swagger
|
9
|
+
|
10
|
+
class ParseError < Exception; end
|
11
|
+
|
12
|
+
class Parser
|
13
|
+
include Apiture::Utils::Inflections
|
14
|
+
|
15
|
+
def parse_json(spec)
|
16
|
+
build_specification(MultiJson.load(spec))
|
17
|
+
end
|
18
|
+
alias :parse :parse_json
|
19
|
+
|
20
|
+
def parse_yaml(spec)
|
21
|
+
build_specification(YAML.load(spec))
|
22
|
+
end
|
23
|
+
|
24
|
+
def build_specification(json)
|
25
|
+
specification = build_node(Specification, json)
|
26
|
+
specification.info = build_node(Info, json['info'])
|
27
|
+
specification.external_docs = build_node(ExternalDocs, json['externalDocs'])
|
28
|
+
specification.schemes = json['schemes']
|
29
|
+
specification.produces = json['produces'] || []
|
30
|
+
specification.consumes = json['consumes'] || []
|
31
|
+
build_security_definition_hash(specification, json)
|
32
|
+
build_security_hash(specification, json)
|
33
|
+
specification.paths = build_node_hash(Path, json, 'paths') do |path, path_json|
|
34
|
+
trace = [path.id]
|
35
|
+
[:get, :put, :post, :delete, :options, :head, :patch].each do |method|
|
36
|
+
if op_json = path_json[method.to_s]
|
37
|
+
trace.unshift(method)
|
38
|
+
op = build_node(Operation, op_json, constructor_args: [method], trace: trace)
|
39
|
+
op.external_docs = build_node(ExternalDocs, op_json['externalDocs'], trace: trace)
|
40
|
+
op.parameters = build_node_list(Parameter, op_json, 'parameters', trace: trace) do |param, param_json|
|
41
|
+
trace.unshift(param_json["name"])
|
42
|
+
if param_json["schema"] && param_json["schema"].kind_of?(Hash)
|
43
|
+
trace.unshift("schema")
|
44
|
+
schema_json = param_json["schema"]
|
45
|
+
param.schema = build_node(Definition, schema_json, trace: trace)
|
46
|
+
build_schema_content(param.schema, schema_json)
|
47
|
+
trace.shift
|
48
|
+
end
|
49
|
+
trace.shift
|
50
|
+
end
|
51
|
+
build_security_definition_hash(op, json)
|
52
|
+
build_security_hash(op, op_json)
|
53
|
+
path.send("#{method}=".to_sym, op)
|
54
|
+
trace.shift
|
55
|
+
end
|
56
|
+
end
|
57
|
+
trace.shift
|
58
|
+
end
|
59
|
+
specification.definitions = build_node_hash(Definition, json, 'definitions') do |definition, def_json|
|
60
|
+
build_schema_content(definition, def_json)
|
61
|
+
end
|
62
|
+
specification
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
def build_schema_content(definition, def_json)
|
68
|
+
definition.properties = build_node_hash(Property, def_json, 'properties') do |prop, prop_json|
|
69
|
+
prop.enum = prop_json["enum"]
|
70
|
+
prop.items = prop_json["items"]
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def build_security_hash(parent_node, json)
|
75
|
+
# leave security nil to defer security to parent. An empty hash means
|
76
|
+
# no security.
|
77
|
+
if sec_json = json['security']
|
78
|
+
parent_node.security = sec_json.reduce({}) do |memo, (k,v)|
|
79
|
+
memo[k] = security = Security.new(k)
|
80
|
+
security.scopes = v
|
81
|
+
memo
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def build_security_definition_hash(parent_node, json)
|
87
|
+
if json['securityDefinitions']
|
88
|
+
parent_node.security_definitions = build_node_hash(SecurityDefinition, json, 'securityDefinitions')
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def build_node(model_class, json, options = {})
|
93
|
+
if json
|
94
|
+
model = if constructor_args = options[:constructor_args]
|
95
|
+
model_class.new(*constructor_args)
|
96
|
+
else
|
97
|
+
model_class.new
|
98
|
+
end
|
99
|
+
model_class.attribute_names.each do |att|
|
100
|
+
model.send("#{att}=".to_sym, json[camelize(att.to_s, false)])
|
101
|
+
end
|
102
|
+
assert_type json, Hash, "expecting object node", options[:trace]
|
103
|
+
json.each_pair do |key, value|
|
104
|
+
if key.match(/^x-/)
|
105
|
+
extension_name = underscore(key.sub(/^x-/, '')).to_sym
|
106
|
+
model.extensions[extension_name] = value
|
107
|
+
end
|
108
|
+
end
|
109
|
+
model
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def build_node_list(model_class, json, json_list_key, options = {})
|
114
|
+
trace = options[:trace] || []
|
115
|
+
trace.unshift(json_list_key)
|
116
|
+
node_list = []
|
117
|
+
if json_list = json[json_list_key]
|
118
|
+
assert_type json_list, Array, "expecting list", trace
|
119
|
+
node_list = json_list.map do |node_json|
|
120
|
+
node = build_node(model_class, node_json, trace: trace)
|
121
|
+
yield(node, node_json) if block_given?
|
122
|
+
node
|
123
|
+
end
|
124
|
+
end
|
125
|
+
trace.unshift
|
126
|
+
node_list
|
127
|
+
end
|
128
|
+
|
129
|
+
def build_node_hash(model_class, json, json_hash_key)
|
130
|
+
(json[json_hash_key] || {}).reduce({}) do |m, (k, v)|
|
131
|
+
m[k] = node = build_node(model_class, v, constructor_args: [k])
|
132
|
+
yield(node, v) if block_given?
|
133
|
+
m
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def assert_type target, expected_type, message, trace
|
138
|
+
unless target.kind_of?(expected_type)
|
139
|
+
if trace
|
140
|
+
message = "[#{trace.reverse.join(" / ")}] - #{message}: #{target.class.name}"
|
141
|
+
end
|
142
|
+
raise ParseError, message
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'apiture/swagger/node'
|
2
|
+
require 'apiture/swagger/operation'
|
3
|
+
|
4
|
+
module Apiture
|
5
|
+
module Swagger
|
6
|
+
class Path < Node
|
7
|
+
attr_reader :id
|
8
|
+
alias :path_name :id
|
9
|
+
|
10
|
+
REQUEST_METHODS = [:get, :put, :post, :delete, :options, :head, :patch]
|
11
|
+
|
12
|
+
REQUEST_METHODS.each do |m|
|
13
|
+
attribute m, validate: true
|
14
|
+
end
|
15
|
+
|
16
|
+
list :parameters, validate: true
|
17
|
+
|
18
|
+
def initialize(id)
|
19
|
+
super()
|
20
|
+
@id = id
|
21
|
+
end
|
22
|
+
|
23
|
+
def operations
|
24
|
+
REQUEST_METHODS.map { |m| __send__(m) }.compact
|
25
|
+
end
|
26
|
+
|
27
|
+
def operations_map
|
28
|
+
REQUEST_METHODS.reduce({}) do |m, method|
|
29
|
+
if op = __send__(method)
|
30
|
+
m[method] = op
|
31
|
+
end
|
32
|
+
m
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|