conduit 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Rakefile +22 -0
- data/app/controllers/conduit/responses_controller.rb +14 -0
- data/app/models/conduit/concerns/storage.rb +41 -0
- data/app/models/conduit/request.rb +109 -0
- data/app/models/conduit/response.rb +50 -0
- data/app/models/conduit/subscription.rb +6 -0
- data/config/routes.rb +3 -0
- data/db/migrate/20131209223122_create_conduit_requests.rb +13 -0
- data/db/migrate/20131209223734_create_conduit_responses.rb +10 -0
- data/db/migrate/20140305202729_create_conduit_subscriptions.rb +12 -0
- data/lib/conduit.rb +60 -0
- data/lib/conduit/acts_as_conduit_subscriber.rb +41 -0
- data/lib/conduit/configuration.rb +43 -0
- data/lib/conduit/core/action.rb +136 -0
- data/lib/conduit/core/connection.rb +85 -0
- data/lib/conduit/core/driver.rb +83 -0
- data/lib/conduit/core/parser.rb +55 -0
- data/lib/conduit/core/render.rb +69 -0
- data/lib/conduit/drivers/keep +0 -0
- data/lib/conduit/engine.rb +20 -0
- data/lib/conduit/storage.rb +37 -0
- data/lib/conduit/storage/aws.rb +94 -0
- data/lib/conduit/storage/file.rb +74 -0
- data/lib/conduit/util.rb +17 -0
- data/lib/conduit/version.rb +3 -0
- data/lib/tasks/conduit_tasks.rake +4 -0
- data/spec/classes/acts_as_conduit_subscriber_spec.rb +61 -0
- data/spec/classes/core/action_spec.rb +53 -0
- data/spec/classes/core/driver_spec.rb +24 -0
- data/spec/controllers/responses_controller_spec.rb +29 -0
- data/spec/dummy/README.rdoc +28 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/javascripts/application.js +13 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +5 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +29 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +20 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +29 -0
- data/spec/dummy/config/environments/production.rb +80 -0
- data/spec/dummy/config/environments/test.rb +36 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/conduit.rb +5 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +16 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +12 -0
- data/spec/dummy/config/initializers/session_store.rb +3 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +23 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/db/schema.rb +46 -0
- data/spec/dummy/lib/conduit/drivers/my_driver/actions/foo.rb +10 -0
- data/spec/dummy/lib/conduit/drivers/my_driver/driver.rb +7 -0
- data/spec/dummy/lib/conduit/drivers/my_driver/parsers/foo.rb +13 -0
- data/spec/dummy/lib/conduit/drivers/my_driver/views/foo.erb +5 -0
- data/spec/dummy/lib/conduit/drivers/my_driver/views/layout.erb +3 -0
- data/spec/dummy/log/development.log +0 -0
- data/spec/dummy/log/test.log +9707 -0
- data/spec/dummy/public/404.html +58 -0
- data/spec/dummy/public/422.html +58 -0
- data/spec/dummy/public/500.html +57 -0
- data/spec/dummy/tmp/conduit/1/my_driver/foo/1-response.xml +3 -0
- data/spec/dummy/tmp/conduit/1/my_driver/foo/request.xml +7 -0
- data/spec/models/conduit/request_spec.rb +50 -0
- data/spec/models/conduit/response_spec.rb +52 -0
- data/spec/spec_helper.rb +36 -0
- data/spec/support/helper.rb +15 -0
- data/spec/support/xml/xml_request.xml +7 -0
- data/spec/support/xml/xml_response.xml +3 -0
- metadata +311 -0
@@ -0,0 +1,136 @@
|
|
1
|
+
#
|
2
|
+
# The job of this class is to provide common
|
3
|
+
# functionality to a action class.
|
4
|
+
#
|
5
|
+
# e.g.
|
6
|
+
# => class MyAction < Conduit::Core::Action
|
7
|
+
# => required_attributes :foo, :bar
|
8
|
+
# => optional_attributes :baz
|
9
|
+
# => end
|
10
|
+
#
|
11
|
+
# => action = MyAction.new(foo: 'foo', bar: 'bar', baz: 'baz')
|
12
|
+
# => action.perform
|
13
|
+
#
|
14
|
+
|
15
|
+
require 'set'
|
16
|
+
|
17
|
+
module Conduit
|
18
|
+
module Core
|
19
|
+
class Action
|
20
|
+
|
21
|
+
def self.inherited(base)
|
22
|
+
base.send :include, Conduit::Core::Connection
|
23
|
+
base.send :include, Conduit::Core::Render
|
24
|
+
base.send :include, InstanceMethods
|
25
|
+
base.extend ClassMethods
|
26
|
+
|
27
|
+
# TODO: Move this to the driver scope
|
28
|
+
# which allows for setting this
|
29
|
+
# "globally" for the driver.
|
30
|
+
#
|
31
|
+
path = caller.first[/^[^:]+/]
|
32
|
+
define_method(:action_path) do
|
33
|
+
File.dirname(path)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module ClassMethods
|
38
|
+
|
39
|
+
attr_accessor :_action_path
|
40
|
+
|
41
|
+
# Set required attributes
|
42
|
+
#
|
43
|
+
# e.g.
|
44
|
+
# => required_attributes :foo, :bar, :baz
|
45
|
+
#
|
46
|
+
def required_attributes(*args)
|
47
|
+
requirements.merge(args)
|
48
|
+
attributes.merge(args)
|
49
|
+
end
|
50
|
+
|
51
|
+
# Set optional attributes
|
52
|
+
#
|
53
|
+
# e.g.
|
54
|
+
# => optional_attributes :foo, :bar, :baz
|
55
|
+
#
|
56
|
+
def optional_attributes(*args)
|
57
|
+
attributes.merge(args)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Storage array for required attributes
|
61
|
+
#
|
62
|
+
def requirements
|
63
|
+
@requirements ||= Set.new
|
64
|
+
end
|
65
|
+
|
66
|
+
# Storage array for all attributes
|
67
|
+
#
|
68
|
+
def attributes
|
69
|
+
@attributes ||= Set.new
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
module InstanceMethods
|
75
|
+
|
76
|
+
delegate :requirements, :attributes, to: :class
|
77
|
+
|
78
|
+
def initialize(**options)
|
79
|
+
@options = options
|
80
|
+
validate!(options)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Object used for passing data to the view
|
84
|
+
# Only keys listed in attributes will be
|
85
|
+
# used.
|
86
|
+
#
|
87
|
+
def view_context
|
88
|
+
OpenStruct.new(@options.select do |k,v|
|
89
|
+
attributes.include?(k)
|
90
|
+
end)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Location where the view files can be found
|
94
|
+
# Default to lib/conduit/drivers/<drivername>/views
|
95
|
+
# Can be overriden per class.
|
96
|
+
#
|
97
|
+
def view_path
|
98
|
+
File.join(File.dirname(action_path), 'views')
|
99
|
+
end
|
100
|
+
|
101
|
+
# Return the rendered view
|
102
|
+
#
|
103
|
+
def view
|
104
|
+
tpl = self.class.name.demodulize
|
105
|
+
.underscore.downcase
|
106
|
+
render(tpl)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Entry method. The class will use
|
110
|
+
# use this to trigger the request.
|
111
|
+
#
|
112
|
+
# Override to customize.
|
113
|
+
#
|
114
|
+
def perform
|
115
|
+
request(body: view, method: :post)
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
# Ensures that all required attributes are present
|
121
|
+
# If not all attributes are present, will raise
|
122
|
+
# an ArgumentError listing missing attributes
|
123
|
+
#
|
124
|
+
def validate!(options)
|
125
|
+
missing_keys = (requirements.to_a - options.keys)
|
126
|
+
if missing_keys.any?
|
127
|
+
raise ArgumentError,
|
128
|
+
"Missing keys: #{missing_keys.join(', ')}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
end
|
133
|
+
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
#
|
2
|
+
# The job of this module is to handle network
|
3
|
+
# communication rom Conduit to a remote host.
|
4
|
+
#
|
5
|
+
|
6
|
+
require 'conduit/version'
|
7
|
+
require 'excon'
|
8
|
+
|
9
|
+
module Conduit
|
10
|
+
module Core
|
11
|
+
module Connection
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
base.extend ClassMethods
|
15
|
+
base.send :include, InstanceMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
|
20
|
+
# Define a remote_url
|
21
|
+
#
|
22
|
+
# e.g.
|
23
|
+
# remote_url 'http://myapi.com/endpoint'
|
24
|
+
#
|
25
|
+
def remote_url(host=nil)
|
26
|
+
@remote_url ||= host
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
module InstanceMethods
|
32
|
+
|
33
|
+
delegate :remote_url, to: :class
|
34
|
+
|
35
|
+
# Make a request
|
36
|
+
#
|
37
|
+
# @param [Hash] params
|
38
|
+
# @option params [String] :body text to be sent over a socket
|
39
|
+
# @option params [Hash<Symbol, String>] :headers The default headers to supply in a request
|
40
|
+
# @option params [String] :host The destination host's reachable DNS name or IP, in the form of a String
|
41
|
+
# @option params [String] :path appears after 'scheme://host:port/'
|
42
|
+
# @option params [Fixnum] :port The port on which to connect, to the destination host
|
43
|
+
# @option params [Hash] :query appended to the 'scheme://host:port/path/' in the form of '?key=value'
|
44
|
+
# @option params [String] :scheme The protocol; 'https' causes OpenSSL to be used
|
45
|
+
# @option params [Proc] :response_block
|
46
|
+
#
|
47
|
+
#
|
48
|
+
def request(params, &block)
|
49
|
+
params[:headers] ||= {}
|
50
|
+
params[:headers]['User-Agent'] ||= "conduit/#{Conduit::VERSION}"
|
51
|
+
connection.request(params, &block)
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
# Connection that will be used
|
57
|
+
#
|
58
|
+
# @param [Hash] params
|
59
|
+
# @option params [String] :body Default text to be sent over a socket. Only used if :body absent in Connection#request params
|
60
|
+
# @option params [Hash<Symbol, String>] :headers The default headers to supply in a request. Only used if params[:headers] is not supplied to Connection#request
|
61
|
+
# @option params [String] :host The destination host's reachable DNS name or IP, in the form of a String
|
62
|
+
# @option params [String] :path Default path; appears after 'scheme://host:port/'. Only used if params[:path] is not supplied to Connection#request
|
63
|
+
# @option params [Fixnum] :port The port on which to connect, to the destination host
|
64
|
+
# @option params [Hash] :query Default query; appended to the 'scheme://host:port/path/' in the form of '?key=value'. Will only be used if params[:query] is not supplied to Connection#request
|
65
|
+
# @option params [String] :scheme The protocol; 'https' causes OpenSSL to be used
|
66
|
+
# @option params [String] :proxy Proxy server; e.g. 'http://myproxy.com:8888'
|
67
|
+
# @option params [Fixnum] :retry_limit Set how many times we'll retry a failed request. (Default 4)
|
68
|
+
# @option params [Class] :instrumentor Responds to #instrument as in ActiveSupport::Notifications
|
69
|
+
# @option params [String] :instrumentor_name Name prefix for #instrument events. Defaults to 'excon'
|
70
|
+
#
|
71
|
+
# @return [Excon::Response]
|
72
|
+
#
|
73
|
+
# @raise [Excon::Errors::StubNotFound]
|
74
|
+
# @raise [Excon::Errors::Timeout]
|
75
|
+
# @raise [Excon::Errors::SocketError]
|
76
|
+
#
|
77
|
+
def connection(**params)
|
78
|
+
@excon ||= Excon.new(remote_url, params)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
#
|
2
|
+
# The job of this class is to require all
|
3
|
+
# actions belonging to this driver
|
4
|
+
#
|
5
|
+
# e.g.
|
6
|
+
# => module Conduit::Driver
|
7
|
+
# => class MyDriver < Conduit::Core::Driver
|
8
|
+
# => required_credentials :foo, :bar, :baz
|
9
|
+
# =>
|
10
|
+
# => action :purchase
|
11
|
+
# => action :activate
|
12
|
+
# => action :suspend
|
13
|
+
# =>
|
14
|
+
# => end
|
15
|
+
# => end
|
16
|
+
#
|
17
|
+
|
18
|
+
require 'set'
|
19
|
+
|
20
|
+
module Conduit
|
21
|
+
module Core
|
22
|
+
module Driver
|
23
|
+
|
24
|
+
def self.extended(base)
|
25
|
+
base.instance_variable_set("@_driver_path",
|
26
|
+
File.dirname(caller.first[/^[^:]+/]))
|
27
|
+
end
|
28
|
+
|
29
|
+
# Set required credentials
|
30
|
+
#
|
31
|
+
# e.g.
|
32
|
+
# => required_credentials :foo, :bar, :baz
|
33
|
+
#
|
34
|
+
def required_credentials(*args)
|
35
|
+
credentials.merge(args)
|
36
|
+
end
|
37
|
+
|
38
|
+
# Set available actions
|
39
|
+
#
|
40
|
+
# e.g.
|
41
|
+
# => action :purchase
|
42
|
+
#
|
43
|
+
def action(action_name)
|
44
|
+
require File.join(@_driver_path, 'actions', action_name.to_s)
|
45
|
+
require File.join(@_driver_path, 'parsers', action_name.to_s)
|
46
|
+
actions << action_name
|
47
|
+
end
|
48
|
+
|
49
|
+
# Storage array for required credentials
|
50
|
+
#
|
51
|
+
# e.g.
|
52
|
+
# Conduit::Driver::Fusion.credentials
|
53
|
+
# => [:foo, :bar, :baz]
|
54
|
+
#
|
55
|
+
def credentials
|
56
|
+
@credentials ||= Set.new
|
57
|
+
end
|
58
|
+
|
59
|
+
# Storage array for required credentials
|
60
|
+
#
|
61
|
+
# e.g.
|
62
|
+
# Conduit::Driver::Fusion.actions
|
63
|
+
# => [:purchase]
|
64
|
+
#
|
65
|
+
def actions
|
66
|
+
@actions ||= Set.new
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# Return the name of the driver
|
72
|
+
#
|
73
|
+
# e.g.
|
74
|
+
# Conduit::Drivers::Fusion.name
|
75
|
+
# => "fusion"
|
76
|
+
#
|
77
|
+
def driver_name
|
78
|
+
self.name.demodulize.underscore.downcase
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#
|
2
|
+
# The job of this class is parse a raw response
|
3
|
+
# and provide parsed attributes that can be
|
4
|
+
# predictably consumed.
|
5
|
+
#
|
6
|
+
module Conduit
|
7
|
+
module Core
|
8
|
+
class Parser
|
9
|
+
|
10
|
+
class << self
|
11
|
+
|
12
|
+
# Define an attribute that will be publically exposed
|
13
|
+
# when dealing with conduit responses.
|
14
|
+
#
|
15
|
+
def attribute(attr_name, &block)
|
16
|
+
attributes << attr_name
|
17
|
+
define_method(attr_name, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Storage array for all attributes
|
21
|
+
#
|
22
|
+
def attributes
|
23
|
+
@attributes ||= []
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
delegate :attributes, to: :class
|
29
|
+
|
30
|
+
# Returns a hash representation of each attribute
|
31
|
+
# defined in a parser and its value.
|
32
|
+
#
|
33
|
+
def serializable_hash
|
34
|
+
attributes.inject({}) do |hash, attribute|
|
35
|
+
hash.tap { |h| h[attribute] = send(attribute) }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Default response status.
|
40
|
+
# Should be overwritten by parser implementation.
|
41
|
+
#
|
42
|
+
def response_status
|
43
|
+
raise NoMethodError, "Please define response_status in your parser."
|
44
|
+
end
|
45
|
+
|
46
|
+
# Default response error container.
|
47
|
+
# Should be overwritten by parser implementation.
|
48
|
+
#
|
49
|
+
def response_errors
|
50
|
+
raise NoMethodError, "Please define response_errors in your parser."
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
#
|
2
|
+
# The job of this module is to provide template
|
3
|
+
# rendering functionality. Included classes
|
4
|
+
# must provide a view_path, and view_context
|
5
|
+
# methods to be of any use.
|
6
|
+
#
|
7
|
+
|
8
|
+
require 'tilt'
|
9
|
+
|
10
|
+
module Conduit
|
11
|
+
module Core
|
12
|
+
module Render
|
13
|
+
|
14
|
+
# Create instance variables, any of these can
|
15
|
+
# be overriden within the including class.
|
16
|
+
#
|
17
|
+
# view_path: Location where the view files are stored
|
18
|
+
# view_context: Object that contains the variables used in the template
|
19
|
+
#
|
20
|
+
def self.included(base)
|
21
|
+
attr_accessor :view_path, :view_context
|
22
|
+
end
|
23
|
+
|
24
|
+
# Render a template file
|
25
|
+
#
|
26
|
+
# e.g. Without layout
|
27
|
+
# => render :purchase, layout: false
|
28
|
+
#
|
29
|
+
# e.g. With layout
|
30
|
+
# => render :purchase
|
31
|
+
#
|
32
|
+
def render(file, layout: true)
|
33
|
+
raise ViewPathNotDefined, "" unless view_path
|
34
|
+
layout ? render_with_layout(file) : render_template(file)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Render a template file
|
38
|
+
#
|
39
|
+
# e.g. Without layout
|
40
|
+
# => render_template(:template)
|
41
|
+
#
|
42
|
+
# e.g. With layout
|
43
|
+
# => render_template(:layout) do
|
44
|
+
# => render_template(:template)
|
45
|
+
# => end
|
46
|
+
#
|
47
|
+
def render_template(file)
|
48
|
+
path = File.join(view_path, "#{file}.erb")
|
49
|
+
Tilt::ERBTemplate.new(path).render(view_context) do
|
50
|
+
yield if block_given?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Render the file with a layout
|
55
|
+
#
|
56
|
+
# e.g.
|
57
|
+
# => render_layout(:template)
|
58
|
+
#
|
59
|
+
def render_with_layout(file)
|
60
|
+
render_template(:layout) do
|
61
|
+
render_template(file)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class ViewPathNotDefined < StandardError; end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
File without changes
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Conduit
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace Conduit
|
4
|
+
|
5
|
+
config.generators do |g|
|
6
|
+
g.test_framework :rspec, :fixture => false
|
7
|
+
g.assets false
|
8
|
+
g.helper false
|
9
|
+
end
|
10
|
+
|
11
|
+
initializer "conduit.load_app_root", before: :load_config_initializers do |app|
|
12
|
+
Conduit.app_root = app.root
|
13
|
+
end
|
14
|
+
|
15
|
+
initializer 'conduit.load_drivers', after: :load_config_initializers do |app|
|
16
|
+
Conduit::Driver.load_drivers
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|