stretchr 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,201 @@
1
+ module Stretchr
2
+
3
+ class Client
4
+ def initialize(options = {})
5
+ options ||= {}
6
+
7
+ options[:project] ||= Stretchr.configuration.project
8
+ options[:private_key] ||= Stretchr.configuration.private_key
9
+ options[:public_key] ||= Stretchr.configuration.public_key
10
+ options[:noisy_errors] ||= (Stretchr.configuration.noisy_errors || false)
11
+ # check for required arguments
12
+ [:project, :public_key, :private_key].each do | required_option |
13
+ raise MissingAttributeError, "#{required_option} is required." unless options[required_option]
14
+ end
15
+
16
+ options.each do |name, value|
17
+ send("#{name}=", value)
18
+ end
19
+
20
+ # create defaults if the user didn't specify anything
21
+ @signatory ||= Stretchr::Signatory
22
+ @transporter ||= Stretchr::DefaultTransporter.new
23
+ @version ||= "v1"
24
+ @path ||= ""
25
+ @query = {}
26
+
27
+ end
28
+
29
+ attr_accessor :project, :private_key, :public_key, :path, :http_method, :http_body, :version, :transporter, :signatory, :noisy_errors
30
+
31
+ #-------------------HTTP Actions-----------------
32
+
33
+ # generate_request makes a Stretchr::Request based on the current settings
34
+ # in this Stretchr object.
35
+ def generate_request
36
+ Stretchr::Request.new(
37
+ :http_method => http_method,
38
+ :signed_uri => signed_uri,
39
+ :body => http_body
40
+ )
41
+ end
42
+
43
+ def make_request!
44
+ # create and make the request
45
+ response = self.transporter.make_request(generate_request)
46
+ raise_errors_in_response(response) if noisy_errors
47
+ response
48
+ end
49
+
50
+ def raise_errors_in_response(response)
51
+ if [404, 500, 401, 403, 406, 400].include?(response.status)
52
+ raise_error(response.status)
53
+ end
54
+ end
55
+
56
+ def raise_error(status)
57
+ case status
58
+ when 404
59
+ raise NotFound
60
+ when 500
61
+ raise InternalServerError
62
+ when 401
63
+ raise Unauthorized
64
+ when 403
65
+ raise Forbidden
66
+ when 400
67
+ raise BadRequest
68
+ else
69
+ raise Unknown
70
+ end
71
+ end
72
+
73
+ # get performs a GET request and returns the Stretchr::Response.
74
+ def get
75
+
76
+ self.http_method = :get
77
+ make_request!
78
+
79
+ end
80
+
81
+ # post performs a POST and returns a Stretch::Response
82
+ def post
83
+ self.http_method = :post
84
+ make_request!
85
+ end
86
+
87
+ def put
88
+ self.http_method = :put
89
+ make_request!
90
+ end
91
+
92
+ def delete
93
+ self.http_method = :delete
94
+ make_request!
95
+ end
96
+
97
+ #---------------- Friendly Actions --------------
98
+
99
+ def create(object)
100
+ self.body(object).post
101
+ end
102
+
103
+ def replace(object)
104
+ self.body(object).post
105
+ end
106
+
107
+ def update(object)
108
+ self.body(object).put
109
+ end
110
+
111
+ def read
112
+ self.get
113
+ end
114
+
115
+ #----------------Friendly Functions--------------
116
+ def url
117
+ uri.to_s
118
+ end
119
+
120
+ def to_url
121
+ url
122
+ end
123
+
124
+ def uri
125
+ URI::HTTP.build(host: "#{project}.stretchr.com", query: merge_query, path: merge_path)
126
+ end
127
+
128
+ def signed_uri
129
+ Stretchr::Signatory.generate_signed_url(http_method, uri, public_key, private_key, http_body)
130
+ end
131
+
132
+ #---------------Parameter Building---------------
133
+
134
+ def order(parameters)
135
+ @query["~order"] = parameters.to_s
136
+ self
137
+ end
138
+
139
+ def skip(parameters)
140
+ @query["~skip"] = parameters.to_i
141
+ self
142
+ end
143
+
144
+ def limit(parameters)
145
+ @query["~limit"] = parameters.to_i
146
+ self
147
+ end
148
+
149
+ def page(parameters)
150
+ skip((@query["~limit"] * parameters.to_i) - @query["~limit"])
151
+ self
152
+ end
153
+
154
+ def parameters(parameters)
155
+ @query.merge!(parameters)
156
+ self
157
+ end
158
+
159
+ def body(body_params)
160
+ self.http_body = body_params.to_json
161
+
162
+ self
163
+ end
164
+
165
+ def where(params)
166
+ params.each do |key, value|
167
+ @query["\:#{key.to_s}"] = value
168
+ end
169
+ self
170
+ end
171
+
172
+ #-----------------Basic Routing-----------------
173
+
174
+ def method_missing(method, *args)
175
+ add_collection(method, *args)
176
+ end
177
+
178
+ def add_collection(collection, id = nil)
179
+ @path += "/#{collection}"
180
+ if id
181
+ id.gsub!(/[^0-9A-Za-z.\-]/, '_') if id.is_a?(String) #remove non-ascii
182
+ @path += "/#{id}"
183
+ end
184
+ self
185
+ end
186
+
187
+ private
188
+
189
+ def merge_query
190
+ unless @query == nil || @query == {}
191
+ URI.encode_www_form(@query)
192
+ end
193
+ end
194
+
195
+ def merge_path
196
+ "/api/#{version}" + @path
197
+ end
198
+
199
+ end
200
+
201
+ end
@@ -0,0 +1,20 @@
1
+ module Stretchr
2
+
3
+ class Configuration
4
+
5
+ def self.add_option(name, default_value = nil)
6
+ attr_accessor name
7
+ @name = default_value
8
+ end
9
+
10
+ add_option :private_key
11
+ add_option :public_key
12
+ add_option :project
13
+ add_option :noisy_errors
14
+
15
+ def method_missing(name, *params)
16
+ raise Stretchr::UnknownConfiguration
17
+ end
18
+ end
19
+
20
+ end
@@ -0,0 +1,18 @@
1
+ #FIXME : Right now we just define some errors that users can implement if they wish. Should we implement them for them?
2
+ module Stretchr
3
+ #basic stretchr error namespace
4
+ class StretchrError < StandardError; end
5
+
6
+ #Configuration
7
+ class MissingAttributeError < StretchrError; end #thrown when initializing client without params
8
+ class UnknownConfiguration < StretchrError; end #thrown when we try to set an unknown configuration option
9
+
10
+ #stretchr status errors
11
+ class NotFound < StretchrError; end
12
+ class InternalServerError < StretchrError; end
13
+ class BadRequest < StretchrError; end
14
+ class Unathorized < StretchrError; end
15
+ class Forbidden < StretchrError; end
16
+ class Unknown < StretchrError; end
17
+
18
+ end
@@ -0,0 +1,129 @@
1
+ module Stretchr
2
+ class Resource
3
+
4
+ # Class Methods to find objects
5
+
6
+ def self.find(params = {})
7
+ stretchr = stretchr_client
8
+ #FIXME : Why does this need to be duplicated?
9
+ stretchr.path = prep_path(stretchr.path.dup, params)
10
+ response = stretchr.get
11
+ return false if !response.success?
12
+ self.new(response.data)
13
+ end
14
+
15
+ def self.all(params = {})
16
+ stretchr = stretchr_client
17
+ stretchr.path = prep_path(stretchr.path.dup, params)
18
+ response = stretchr.get
19
+ return [] if response.data["~c"] == 0 || !response.data["~i"]
20
+ response.data["~i"].map {|r| self.new(r) }
21
+ end
22
+
23
+ def self.where(params = {})
24
+ stretchr = stretchr_client
25
+ #snag the vars that are needed for the path
26
+ path_vars = stretchr.path.scan(/:([a-zA-Z0-9_-]*)/i).flatten
27
+ #now convert the path as best we can
28
+ stretchr.path = prep_path(stretchr.path.dup, params)
29
+ #now remove the path params from the request
30
+ params.delete_if {|key, value| path_vars.include?(key.to_s)}
31
+
32
+ response = stretchr.where(params).get
33
+ #return false if nothing returned or search wasn't successful
34
+ return false if !response.success? || response.data["~c"] == 0 || !response.data["~i"]
35
+ response.data["~i"].map {|i| self.new(i) }
36
+ end
37
+
38
+ def self.stretchr_client
39
+ Stretchr::Client.new(@config)
40
+ end
41
+
42
+ def self.create(objects = [], params = {})
43
+ #convert it to an array for easy adding
44
+ objects = [objects] if !objects.is_a?(Array)
45
+ objects.map! {|o| setup_attributes_for_stretchr(o) }
46
+ stretchr = stretchr_client
47
+ stretchr.path = prep_path(stretchr.path.dup, params)
48
+ stretchr.body(objects)
49
+ response = stretchr.post
50
+ count = 0
51
+ stretchr_objects = objects.map do |o|
52
+ account = self.new(o)
53
+ account.parse_changes(response.changed[count])
54
+ count += 1
55
+ account
56
+ end
57
+ return stretchr_objects.first if stretchr_objects.length == 1
58
+ return stretchr_objects
59
+ end
60
+
61
+ def self.stretchr_config(params = {})
62
+ @config ||= {}
63
+ @config.merge!(params)
64
+ end
65
+
66
+ # Instance Methods
67
+
68
+ def initialize(params = {})
69
+ @attributes = {}
70
+ params.each {|key, value| self.send("#{key}=", value) }
71
+ end
72
+
73
+ def save(params = {})
74
+ stretchr = self.class.stretchr_client
75
+ stretchr.path = self.class.prep_path(stretchr.path.dup, params)
76
+ stretchr.body(setup_attributes_for_stretchr)
77
+ if self.stretchr_id
78
+ response = stretchr.put
79
+ else
80
+ response = stretchr.post
81
+ end
82
+ parse_changes(response.changed)
83
+ end
84
+
85
+ def parse_changes(response)
86
+ #FIXME : Should handle change objects and deltas here and update the object accordingly
87
+ return unless response
88
+ response.each {|key, value| self.send("#{key}=", value)}
89
+ self
90
+ end
91
+
92
+ def to_hash
93
+ @attributes
94
+ end
95
+
96
+ def to_json
97
+ to_hash.to_json
98
+ end
99
+
100
+ def method_missing(method, *args)
101
+ attribute = method.to_s
102
+ if attribute =~ /=$/ #if the method name ends in an =
103
+ @attributes[attribute.chop.gsub("~", "stretchr_")] = args[0]
104
+ else
105
+ @attributes[attribute]
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ def self.prep_path(path, params = {})
112
+ params.each {|key, value| path.gsub!(":#{key.to_s}", value.to_s) unless value == nil}
113
+ #remove any unchanged params from the path
114
+ path.gsub!(/(:[a-zA-Z0-9_-]*)/, "")
115
+ path
116
+ end
117
+
118
+ def setup_attributes_for_stretchr
119
+ self.class.setup_attributes_for_stretchr(@attributes)
120
+ end
121
+
122
+ def self.setup_attributes_for_stretchr(attributes)
123
+ stretchr_attributes = {}
124
+ attributes.each_pair {|key, value| stretchr_attributes[key.to_s.gsub(/^stretchr_/, "~")] = value}
125
+ stretchr_attributes
126
+ end
127
+
128
+ end
129
+ end
@@ -0,0 +1 @@
1
+ require_relative 'resources/resource.rb'
@@ -0,0 +1,52 @@
1
+ require 'uri' unless defined? URI
2
+ require 'cgi' unless defined? CGI
3
+ require 'digest/sha1'
4
+
5
+ module Stretchr
6
+ class Signatory
7
+ class << self
8
+ def generate_signed_url(method, uri, public_key, private_key, body = nil)
9
+
10
+ #we need a URI, let's make sure it's what we have
11
+ uri = URI.parse(URI.escape(uri)) unless uri.is_a?(URI)
12
+
13
+ #preparation
14
+ query = CGI.parse(uri.query || "")
15
+ query["~key"] = public_key
16
+
17
+ #store this for later, we'll need it
18
+ public_query = URI.encode_www_form(query)
19
+
20
+ #now add the private stuff
21
+ query["~private"] = private_key
22
+ query["~bodyhash"] = Digest::SHA1.hexdigest(body) unless body == nil
23
+
24
+ #sort it
25
+ query = sort_query(query)
26
+ uri.query = URI.encode_www_form(query)
27
+
28
+ #append the method
29
+ signature = generate_signature(method, uri.to_s)
30
+
31
+ #now we prepare it for public use
32
+ public_query = public_query + "&" unless public_query == nil
33
+ uri.query = public_query + URI.encode_www_form("~sign" => signature)
34
+
35
+ return uri
36
+ end
37
+
38
+ private
39
+
40
+ def generate_signature(http_method, private_url)
41
+ combined = "#{http_method.to_s.upcase}&#{CGI.unescape(private_url.to_s)}"
42
+ Digest::SHA1.hexdigest(combined)
43
+ end
44
+
45
+ def sort_query(query)
46
+ query.each do |key, value|
47
+ value.sort! if value.is_a?(Array)
48
+ end.sort
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1 @@
1
+ require_relative 'security/signatory'
@@ -0,0 +1,16 @@
1
+ module Stretchr
2
+ class Request
3
+
4
+ attr_accessor :http_method, :body, :signed_uri, :headers
5
+
6
+ def initialize(options = {})
7
+
8
+ @http_method = options[:http_method]
9
+ @body = options[:body]
10
+ @signed_uri = options[:signed_uri]
11
+ @headers = options[:headers]
12
+
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,39 @@
1
+ require 'json' unless defined? JSON
2
+ module Stretchr
3
+ class Response
4
+
5
+ attr_reader :json_string, :json_object, :status, :client_context, :data, :changed, :errors, :raw_response
6
+
7
+ def initialize(options = nil)
8
+
9
+ options ||= {}
10
+
11
+ @raw_response = options[:response]
12
+
13
+ if options[:json]
14
+
15
+ # save the original json string
16
+ @json_string = options[:json]
17
+ @json_object = JSON.parse(@json_string)
18
+
19
+ @status = @json_object["~s"]
20
+
21
+ @client_context = @json_object["~x"]
22
+ @data = @json_object["~d"]
23
+ @changed = @json_object["~ch"]["~deltas"] if @json_object["~ch"]
24
+
25
+ unless @json_object["~e"].nil?
26
+ @errors = @json_object["~e"].collect {| error_obj | error_obj["~m"] }
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+
33
+ # Gets whether this is a successful response.
34
+ def success?
35
+ @status >= 200 && @status <= 299
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ require "uri" if !defined? URI
2
+ require "net/http" if !defined? Net
3
+
4
+ module Stretchr
5
+ class DefaultTransporter
6
+
7
+ def make_request(request)
8
+ response = nil
9
+
10
+ Net::HTTP.start(request.signed_uri.host, request.signed_uri.port) do |http|
11
+
12
+ http_request = generate_request(request)
13
+ response = http.request http_request # Net::HTTPResponse object
14
+
15
+ end
16
+
17
+ return Stretchr::Response.new({:response => response, :json => response.body})
18
+ end
19
+
20
+ def generate_request(request)
21
+
22
+ request_uri = request.signed_uri.request_uri
23
+
24
+ case request.http_method
25
+ when :get
26
+ req = Net::HTTP::Get.new request_uri
27
+ when :post
28
+ req = Net::HTTP::Post.new request_uri, {'Content-Type' => "application/json"}
29
+ req.body = request.body
30
+ req
31
+ when :put
32
+ req = Net::HTTP::Put.new request_uri, {'Content-Type' => "application/json"}
33
+ req.body = request.body
34
+ req
35
+ when :delete
36
+ req = Net::HTTP::Delete.new request_uri
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,25 @@
1
+ module Stretchr
2
+ class TestTransporter
3
+
4
+ attr_accessor :requests, :responses
5
+
6
+ def initialize
7
+ self.requests = []
8
+ self.responses = []
9
+ end
10
+
11
+ def make_request(request)
12
+
13
+ # store the request and return the next response in the local queue
14
+ # not NOT actually make any http requests
15
+
16
+ self.requests << request
17
+
18
+ # return the response
19
+ self.responses.shift
20
+
21
+ end
22
+
23
+
24
+ end
25
+ end
@@ -0,0 +1,2 @@
1
+ require_relative 'transporters/default_transporter.rb'
2
+ require_relative 'transporters/test_transporter.rb'
data/lib/stretchr.rb ADDED
@@ -0,0 +1,32 @@
1
+ require "uri"
2
+ require "cgi"
3
+ require "json"
4
+
5
+
6
+ module Stretchr
7
+ #exceptions that can be used by stretchr
8
+ require_relative 'stretchr/exceptions'
9
+
10
+ #configuration
11
+ require_relative 'stretchr/configuration'
12
+
13
+ @configuration = Stretchr::Configuration.new
14
+
15
+ def self.configuration
16
+ @configuration
17
+ end
18
+
19
+ def self.config
20
+ yield(@configuration)
21
+ end
22
+
23
+ #the actual client library
24
+ require_relative 'stretchr/client'
25
+ require_relative 'stretchr/security'
26
+ require_relative 'stretchr/transporters'
27
+ require_relative 'stretchr/resources'
28
+ require_relative 'stretchr/stretchr_request'
29
+ require_relative 'stretchr/stretchr_response'
30
+
31
+
32
+ end