resto 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/lib/blankslate.rb +110 -0
  2. data/lib/resto.rb +264 -0
  3. data/lib/resto/attributes.rb +56 -0
  4. data/lib/resto/extra/copy.rb +34 -0
  5. data/lib/resto/extra/delegation.rb +26 -0
  6. data/lib/resto/extra/hash_args.rb +56 -0
  7. data/lib/resto/format.rb +20 -0
  8. data/lib/resto/format/default.rb +11 -0
  9. data/lib/resto/format/json.rb +30 -0
  10. data/lib/resto/format/plain.rb +14 -0
  11. data/lib/resto/format/xml.rb +66 -0
  12. data/lib/resto/property.rb +50 -0
  13. data/lib/resto/property/handler.rb +57 -0
  14. data/lib/resto/property/integer.rb +29 -0
  15. data/lib/resto/property/string.rb +19 -0
  16. data/lib/resto/property/time.rb +43 -0
  17. data/lib/resto/request/base.rb +88 -0
  18. data/lib/resto/request/factory.rb +66 -0
  19. data/lib/resto/request/header.rb +58 -0
  20. data/lib/resto/request/option.rb +126 -0
  21. data/lib/resto/request/uri.rb +50 -0
  22. data/lib/resto/response/base.rb +85 -0
  23. data/lib/resto/translator/request_factory.rb +44 -0
  24. data/lib/resto/translator/response_factory.rb +28 -0
  25. data/lib/resto/validate.rb +37 -0
  26. data/lib/resto/validate/inclusion.rb +39 -0
  27. data/lib/resto/validate/length.rb +36 -0
  28. data/lib/resto/validate/presence.rb +24 -0
  29. data/lib/resto/version.rb +5 -0
  30. data/resto.gemspec +43 -0
  31. data/spec/resto/extra/copy_spec.rb +58 -0
  32. data/spec/resto/extra/hash_args_spec.rb +71 -0
  33. data/spec/resto/format/default_spec.rb +24 -0
  34. data/spec/resto/format/json_spec.rb +29 -0
  35. data/spec/resto/format/plain_spec.rb +21 -0
  36. data/spec/resto/format/xml_spec.rb +105 -0
  37. data/spec/resto/property/handler_spec.rb +57 -0
  38. data/spec/resto/property/integer_spec.rb +67 -0
  39. data/spec/resto/property/time_spec.rb +124 -0
  40. data/spec/resto/property_spec.rb +60 -0
  41. data/spec/resto/request/base_spec.rb +253 -0
  42. data/spec/resto/request/factory_spec.rb +114 -0
  43. data/spec/resto/translator/response_factory_spec.rb +93 -0
  44. data/spec/resto/validate/presence_spec.rb +102 -0
  45. data/spec/resto_spec.rb +531 -0
  46. data/spec/spec_helper.rb +119 -0
  47. metadata +48 -3
@@ -0,0 +1,66 @@
1
+ # encoding: utf-8
2
+
3
+ require 'net/https'
4
+ require 'resto/extra/delegation'
5
+
6
+ module Resto
7
+ module Request
8
+ class Factory
9
+ extend Resto::Extra::Delegation
10
+
11
+ delegate :read_host, :read_port, :options, :read_body, :composed_path,
12
+ :composed_headers, :scheme, :use_ssl, :to => :@request
13
+
14
+ def initialize(request)
15
+ @request = request
16
+ end
17
+
18
+ def head
19
+ http.start do |http|
20
+ http.request(Net::HTTP::Head.new(composed_path, composed_headers))
21
+ end
22
+ end
23
+
24
+ def get
25
+ http.start do |http|
26
+ http.request(Net::HTTP::Get.new(composed_path, composed_headers))
27
+ end
28
+ end
29
+
30
+ def post
31
+ http.start do |http|
32
+ http.request(Net::HTTP::Post.new(composed_path, composed_headers),
33
+ read_body)
34
+ end
35
+ end
36
+
37
+ def put
38
+ http.start do |http|
39
+ http.request(Net::HTTP::Put.new(composed_path, composed_headers),
40
+ read_body)
41
+ end
42
+ end
43
+
44
+ def delete
45
+ http.start do |http|
46
+ http.request(Net::HTTP::Delete.new(composed_path, composed_headers))
47
+ end
48
+ end
49
+
50
+ def http
51
+ ::Net::HTTP.new(read_host, read_port).tap do |http|
52
+
53
+ use_ssl if scheme == "https"
54
+
55
+ unless options.keys.empty?
56
+ http.methods.grep(/\A(\w+)=\z/) do |meth|
57
+ key = $1.to_sym
58
+ options.key?(key) or next
59
+ http.__send__(meth, options[key])
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+ require 'resto/format'
3
+
4
+ require 'resto/extra/hash_args'
5
+ class BasicAuth < Resto::Extra::HashArgs
6
+ key :username
7
+ key :password
8
+ end
9
+
10
+ class FormatExtension < Resto::Extra::HashArgs
11
+ key :extension
12
+ end
13
+
14
+ module Resto
15
+ module Request
16
+ module Header
17
+
18
+ def format(symbol, options=nil)
19
+ formatter(Resto::Format.get(@symbol = symbol), options)
20
+ end
21
+
22
+ def formatter(formatter, options=nil)
23
+ @add_extension =
24
+ FormatExtension.new(options).fetch(:extension) { false }
25
+ @formatter = formatter
26
+ accept(formatter.accept)
27
+ content_type(formatter.content_type)
28
+ end
29
+
30
+ def composed_headers
31
+ @headers ||= { 'accept'=> '*/*' , 'user-agent'=> 'Ruby' }
32
+ end
33
+
34
+ def headers(headers)
35
+ tap { composed_headers.merge!(headers) }
36
+ end
37
+
38
+ def accept(accept)
39
+ tap { composed_headers.store('accept', accept) }
40
+ end
41
+
42
+ def content_type(content_type)
43
+ tap { composed_headers.store('content-type', content_type) }
44
+ end
45
+
46
+ def basic_auth(options)
47
+ options = BasicAuth.new(options)
48
+
49
+ username = options.fetch(:username)
50
+ password = options.fetch(:password)
51
+ base64_encode = ["#{username}:#{password}"].pack('m').delete("\r\n")
52
+ basic_encode = 'Basic ' + base64_encode
53
+
54
+ tap { composed_headers.store('authorization', basic_encode) }
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,126 @@
1
+ # encoding: utf-8
2
+
3
+ require 'openssl'
4
+
5
+ module Resto
6
+ module Request
7
+ module Option
8
+
9
+ def options
10
+ @options ||= {}
11
+ end
12
+
13
+ begin :ssl_attributes
14
+
15
+ def use_ssl(use_ssl=true)#
16
+ tap do
17
+ { :verify_mode => OpenSSL::SSL::VERIFY_PEER }.update(options)
18
+ options.store(:use_ssl, use_ssl)
19
+ end
20
+ end
21
+
22
+ # Sets the SSL version. See OpenSSL::SSL::SSLContext#ssl_version=
23
+ def ssl_version(ssl_version)#1.9
24
+ tap { options.store(:ssl_version, ssl_version) }
25
+ end
26
+
27
+ # Sets an OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object.
28
+ # (This method is appeared in Michal Rokos's OpenSSL extension.)
29
+ def key(key)#
30
+ tap { options.store(:key, key) }
31
+ end
32
+
33
+ # Sets an OpenSSL::X509::Certificate object as client certificate.
34
+ # (This method is appeared in Michal Rokos's OpenSSL extension).
35
+ def cert(cert)#
36
+ tap { options.store(:cert, cert) }
37
+ end
38
+
39
+ # Sets path of a CA certification file in PEM format.
40
+ # The file can contain several CA certificates.
41
+ def ca_file(ca_file)#
42
+ tap { options.store(:ca_file, ca_file) }
43
+ end
44
+
45
+ # Sets path of a CA certification directory containing certifications in
46
+ # PEM format.
47
+ def ca_path(ca_path)#
48
+ tap { options.store(:ca_path, ca_path) }
49
+ end
50
+
51
+ # Sets the X509::Store to verify peer certificate.
52
+ def cert_store(cert_store)#
53
+ tap { options.store(:cert_store, cert_store) }
54
+ end
55
+
56
+ # Sets the available ciphers. See OpenSSL::SSL::SSLContext#ciphers=
57
+ def ciphers(ciphers) # 1.9
58
+ tap { options.store(:ciphers, ciphers) }
59
+ end
60
+
61
+ # Sets the flags for server the certification verification at beginning of
62
+ # SSL/TLS session.
63
+ #
64
+ # OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER are acceptable.
65
+ def verify_mode(verify_mode)#
66
+ tap { options.store(:verify_mode, verify_mode) }
67
+ end
68
+
69
+ def verify_none
70
+ tap { verify_mode(OpenSSL::SSL::VERIFY_NONE) }
71
+ end
72
+
73
+ def verify_peer
74
+ tap { verify_mode(OpenSSL::SSL::VERIFY_PEER) }
75
+ end
76
+
77
+ # Sets the verify callback for the server certification verification.
78
+ def verify_callback(verify_callback)#
79
+ tap { options.store(:verify_callback, verify_callback)}
80
+ end
81
+
82
+ def verify_depth(verify_depth)#
83
+ tap { options.store(:verify_depth, verify_depth)}
84
+ end
85
+
86
+ # Sets the SSL timeout seconds.
87
+ def ssl_timeout(ssl_timeout)#
88
+ tap { options.store(:ssl_timeout, ssl_timeout)}
89
+ end
90
+ end #:ssl_attributes
91
+
92
+ def close_on_empty_response(close_on_empty_response)#
93
+ tap { options.store(:close_on_empty_response, close_on_empty_response) }
94
+ end
95
+
96
+ # Number of seconds to wait for the connection to open.
97
+ # If the HTTP object cannot open a connection in this many seconds,
98
+ # it raises a TimeoutError exception.
99
+ def open_timeout(time)#
100
+ tap { options.store(:open_timeout, time)}
101
+ end
102
+
103
+ # Number of seconds to wait for one block to be read (via one read(2) call).
104
+ # If the HTTP object cannot read data in this many seconds,
105
+ # it raises a TimeoutError exception.
106
+ def read_timeout(time)#
107
+ tap { options.store(:read_timeout, time)}
108
+ end
109
+
110
+ #Seconds to wait until reading one block (by one read(2) call).
111
+ def timeout(timeout) # 1.8.7 1.9??
112
+ tap { options.store(:timeout, timeout)}
113
+ end
114
+
115
+ # *WARNING* This method opens a serious security hole.
116
+ # Never use this method in production code.
117
+ #
118
+ # Sets an output stream for debugging.
119
+ #
120
+ # Resto.url('www.google.com').set_debug_output($stderr).get
121
+ def set_debug_output(output)
122
+ tap { options.store(:set_debug_output, output)}
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,50 @@
1
+ # encoding: utf-8
2
+ require 'uri'
3
+ require 'cgi'
4
+
5
+ module Resto
6
+ module Request
7
+ module Uri
8
+ def read_host
9
+ return nil unless @host
10
+
11
+ normalize_uri(@host)
12
+ @uri.host
13
+ end
14
+
15
+ def read_port; @port ||= 80; end
16
+
17
+ def composed_path
18
+ path = ["/#{@path}", @append_path].compact.join('/').gsub('//', "/")
19
+ path = "#{path}.#{current_formatter.extension}" if @add_extension
20
+ params = hash_to_params
21
+ return path unless (@query or params)
22
+
23
+ [path, [@query, params].compact.join('&')].join('?')
24
+ end
25
+
26
+ attr_reader :scheme
27
+ def normalize_uri(url)
28
+ @uri = URI.parse(url.match(/^https?:/) ? url : "http://#{url}")
29
+ @scheme ||= @uri.scheme
30
+ @host ||= @uri.host
31
+ @port ||= @uri.port
32
+ @path ||= @uri.path
33
+ @query ||= @uri.query
34
+ end
35
+
36
+ def parse_url(url)
37
+ normalize_uri(url)
38
+ end
39
+
40
+ def hash_to_params
41
+ return nil unless @params
42
+ raise ArgumentError unless @params.is_a?(Hash)
43
+
44
+ @params.sort.map do |a|
45
+ "#{CGI.escape(a.fetch(0).to_s)}=#{CGI.escape(a.fetch(1).to_s)}"
46
+ end.join('&')
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,85 @@
1
+ # encoding: utf-8
2
+
3
+ require 'resto/format'
4
+ require 'resto/translator/response_factory'
5
+
6
+ module Resto
7
+ module Response
8
+ class Base
9
+
10
+ def klass(klass)
11
+ tap { @klass = klass }
12
+ end
13
+
14
+ def translator(translator)
15
+ @translator = Resto::Translator::ResponseFactory.create(translator)
16
+ self
17
+ end
18
+
19
+ def format(symbol, options = {})
20
+ xpath = options[:xpath]
21
+ xpath(xpath) if xpath
22
+ formatter(Resto::Format.get(@symbol = symbol))
23
+ end
24
+
25
+ def xpath(xpath)
26
+ tap { @xpath = xpath }
27
+ end
28
+
29
+ def read_xpath
30
+ @xpath || "//#{@klass.to_s.downcase}"
31
+ end
32
+
33
+ def formatter(formatter)
34
+ tap { @formatter = formatter }
35
+ end
36
+
37
+ def current_formatter
38
+ @formatter ||= Resto::Format.get(@symbol || :default)
39
+ end
40
+
41
+ def http_response(response)
42
+ tap { @response = response }
43
+ end
44
+
45
+ def read_body
46
+ body ? current_formatter.decode(body, :xpath => read_xpath) : nil
47
+ end
48
+
49
+ def body
50
+ @response ? @response.body : nil
51
+ end
52
+
53
+ def code
54
+ @response ? @response.code : nil
55
+ end
56
+
57
+ def valid?
58
+ (/\A20\d{1}\z/ =~ code.to_s) == 0
59
+ end
60
+
61
+ attr_reader :response
62
+
63
+ def to_object
64
+ return self unless @translator
65
+
66
+ @translator.call(@klass, read_body).tap do |instance|
67
+ instance.response = self
68
+ end
69
+ end
70
+
71
+ def to_collection
72
+ return self unless @translator
73
+
74
+ body = read_body.is_a?(Hash) ? [read_body] : read_body
75
+
76
+ (body || []).map do |hash|
77
+ @translator.call(@klass, hash).tap do |instance|
78
+ instance.response = self
79
+ end
80
+ end
81
+ end
82
+
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+
3
+ module Resto
4
+ module Translator
5
+ class Factory; end
6
+
7
+ class << Factory
8
+ def create(translator)
9
+ if translator.is_a?(Symbol) and :default == translator
10
+ new([])
11
+ elsif translator.is_a?(Array)
12
+ new(translator)
13
+ elsif translator.is_a?(Proc)
14
+ translator
15
+ elsif translator.is_a?(Class)
16
+ translator.new
17
+ else
18
+ raise(ArgumentError,
19
+ "Invalid argument. Valid symbol is :default. Array, Class or a
20
+ Proc is also a valid translator.")
21
+ end
22
+ end
23
+ end
24
+
25
+ class Factory
26
+ def initialize(keys)
27
+ @keys = keys
28
+ end
29
+ end
30
+
31
+ class RequestFactory < Factory
32
+
33
+ def call(attributes)
34
+ attributes ||= {}
35
+ raise ArgumentError unless attributes.is_a?(Hash)
36
+
37
+ @keys.reverse.inject(attributes) do |memo, item|
38
+ Hash.new.tap {|h| h[item] = memo }
39
+ end
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ require 'resto/translator/request_factory'
4
+ module Resto
5
+ module Translator
6
+ class ResponseFactory < Factory
7
+
8
+ def call(klass, hash)
9
+ hash ||= {}
10
+ raise ArgumentError unless hash.is_a?(Hash)
11
+
12
+ klass.new(traverse(hash, 0))
13
+ end
14
+
15
+ private
16
+
17
+ def traverse(hash, index)
18
+ next_hash = hash[@keys.at(index)] || hash[@keys.at(index).to_s]
19
+
20
+ if next_hash
21
+ traverse(next_hash, index + 1)
22
+ else
23
+ hash
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end