resto 0.0.3 → 0.0.4

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 (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