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.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/Gemfile +8 -0
  4. data/LICENSE +20 -0
  5. data/README.md +38 -0
  6. data/Rakefile +6 -0
  7. data/apiture.gemspec +30 -0
  8. data/lib/apiture.rb +24 -0
  9. data/lib/apiture/api_base.rb +27 -0
  10. data/lib/apiture/api_builder.rb +196 -0
  11. data/lib/apiture/api_error.rb +3 -0
  12. data/lib/apiture/api_group.rb +28 -0
  13. data/lib/apiture/data_model.rb +24 -0
  14. data/lib/apiture/endpoint.rb +25 -0
  15. data/lib/apiture/middleware/auth/api_key.rb +39 -0
  16. data/lib/apiture/middleware/auth/basic.rb +25 -0
  17. data/lib/apiture/middleware/auth/oauth2.rb +31 -0
  18. data/lib/apiture/middleware/convert_json_body.rb +15 -0
  19. data/lib/apiture/middleware/debug.rb +20 -0
  20. data/lib/apiture/middleware/set_body_parameter.rb +20 -0
  21. data/lib/apiture/middleware/set_header.rb +15 -0
  22. data/lib/apiture/middleware/set_parameter_base.rb +37 -0
  23. data/lib/apiture/middleware/set_path_parameter.rb +18 -0
  24. data/lib/apiture/middleware/set_query_parameter.rb +13 -0
  25. data/lib/apiture/middleware_builder.rb +15 -0
  26. data/lib/apiture/middleware_stack.rb +21 -0
  27. data/lib/apiture/request_context.rb +103 -0
  28. data/lib/apiture/swagger/data_type_field.rb +25 -0
  29. data/lib/apiture/swagger/definition.rb +20 -0
  30. data/lib/apiture/swagger/external_docs.rb +10 -0
  31. data/lib/apiture/swagger/info.rb +14 -0
  32. data/lib/apiture/swagger/node.rb +149 -0
  33. data/lib/apiture/swagger/operation.rb +32 -0
  34. data/lib/apiture/swagger/parameter.rb +35 -0
  35. data/lib/apiture/swagger/parser.rb +148 -0
  36. data/lib/apiture/swagger/path.rb +37 -0
  37. data/lib/apiture/swagger/property.rb +15 -0
  38. data/lib/apiture/swagger/security.rb +21 -0
  39. data/lib/apiture/swagger/security_definition.rb +23 -0
  40. data/lib/apiture/swagger/specification.rb +31 -0
  41. data/lib/apiture/uri.rb +36 -0
  42. data/lib/apiture/utils/inflections.rb +46 -0
  43. data/lib/apiture/version.rb +3 -0
  44. data/spec/apiture/api_builder_spec.rb +20 -0
  45. data/spec/apiture/swagger/parser_spec.rb +107 -0
  46. data/spec/apiture/swagger/specification_spec.rb +19 -0
  47. data/spec/apiture_spec.rb +228 -0
  48. data/spec/files/github.json +186 -0
  49. data/spec/files/harvest.json +75 -0
  50. data/spec/files/honeybadger.json +80 -0
  51. data/spec/files/mandrill.json +502 -0
  52. data/spec/files/pivotal_tracker.json +94 -0
  53. data/spec/files/slack.json +88 -0
  54. data/spec/files/uber.json +43 -0
  55. data/spec/fixtures/vcr_cassettes/github_checkAuthorization.yml +70 -0
  56. data/spec/fixtures/vcr_cassettes/github_getUser.yml +82 -0
  57. data/spec/fixtures/vcr_cassettes/harvest_invoiceList.yml +85 -0
  58. data/spec/fixtures/vcr_cassettes/honeybadger.yml +98 -0
  59. data/spec/fixtures/vcr_cassettes/mandrill_messageSend.yml +44 -0
  60. data/spec/fixtures/vcr_cassettes/mandrill_userPing.yml +44 -0
  61. data/spec/fixtures/vcr_cassettes/pivotal_tracker_create_story.yml +61 -0
  62. data/spec/fixtures/vcr_cassettes/slack.yml +43 -0
  63. data/spec/fixtures/vcr_cassettes/uber_getProducts.yml +60 -0
  64. data/spec/spec_helper.rb +11 -0
  65. metadata +241 -0
@@ -0,0 +1,10 @@
1
+ require 'apiture/swagger/node'
2
+
3
+ module Apiture
4
+ module Swagger
5
+ class ExternalDocs < Node
6
+ attribute :description
7
+ attribute :url
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ require 'apiture/swagger/node'
2
+
3
+ module Apiture
4
+ module Swagger
5
+ class Info < Node
6
+ attribute :title
7
+ attribute :description
8
+ attribute :terms_of_service
9
+ attribute :contact
10
+ attribute :license
11
+ attribute :version
12
+ end
13
+ end
14
+ end
@@ -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
@@ -0,0 +1,15 @@
1
+ require 'apiture/swagger/data_type_field'
2
+
3
+ module Apiture
4
+ module Swagger
5
+ class Property < DataTypeField
6
+ attr_reader :id
7
+
8
+ def initialize(id)
9
+ super()
10
+ @id = id
11
+ end
12
+
13
+ end
14
+ end
15
+ end