stretchr 1.0.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.
@@ -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