sawyer 0.0.4 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,47 +1,5 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
- require 'date'
4
-
5
- #############################################################################
6
- #
7
- # Helper functions
8
- #
9
- #############################################################################
10
-
11
- def name
12
- @name ||= Dir['*.gemspec'].first.split('.').first
13
- end
14
-
15
- def version
16
- line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
17
- line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
18
- end
19
-
20
- def date
21
- Date.today.to_s
22
- end
23
-
24
- def rubyforge_project
25
- name
26
- end
27
-
28
- def gemspec_file
29
- "#{name}.gemspec"
30
- end
31
-
32
- def gem_file
33
- "#{name}-#{version}.gem"
34
- end
35
-
36
- def replace_header(head, header_name)
37
- head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
38
- end
39
-
40
- #############################################################################
41
- #
42
- # Standard tasks
43
- #
44
- #############################################################################
45
3
 
46
4
  task :default => :test
47
5
 
@@ -52,84 +10,3 @@ Rake::TestTask.new(:test) do |test|
52
10
  test.verbose = true
53
11
  end
54
12
 
55
- desc "Open an irb session preloaded with this library"
56
- task :console do
57
- sh "irb -rubygems -r ./lib/#{name}.rb"
58
- end
59
-
60
- #############################################################################
61
- #
62
- # Custom tasks (add your own tasks here)
63
- #
64
- #############################################################################
65
-
66
-
67
-
68
- #############################################################################
69
- #
70
- # Packaging tasks
71
- #
72
- #############################################################################
73
-
74
- desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
75
- task :release => :build do
76
- unless `git branch` =~ /^\* master$/
77
- puts "You must be on the master branch to release!"
78
- exit!
79
- end
80
- sh "git commit --allow-empty -a -m 'Release #{version}'"
81
- sh "git tag v#{version}"
82
- sh "git push origin master"
83
- sh "git push origin v#{version}"
84
- sh "gem push pkg/#{name}-#{version}.gem"
85
- end
86
-
87
- desc "Build #{gem_file} into the pkg directory"
88
- task :build => :gemspec do
89
- sh "mkdir -p pkg"
90
- sh "gem build #{gemspec_file}"
91
- sh "mv #{gem_file} pkg"
92
- end
93
-
94
- desc "Generate #{gemspec_file}"
95
- task :gemspec => :validate do
96
- # read spec file and split out manifest section
97
- spec = File.read(gemspec_file)
98
- head, manifest, tail = spec.split(" # = MANIFEST =\n")
99
-
100
- # replace name version and date
101
- replace_header(head, :name)
102
- replace_header(head, :version)
103
- replace_header(head, :date)
104
- #comment this out if your rubyforge_project has a different name
105
- replace_header(head, :rubyforge_project)
106
-
107
- # determine file list from git ls-files
108
- files = `git ls-files`.
109
- split("\n").
110
- sort.
111
- reject { |file| file =~ /^\./ }.
112
- reject { |file| file =~ /^(rdoc|pkg)/ }.
113
- map { |file| " #{file}" }.
114
- join("\n")
115
-
116
- # piece file back together and write
117
- manifest = " s.files = %w[\n#{files}\n ]\n"
118
- spec = [head, manifest, tail].join(" # = MANIFEST =\n")
119
- File.open(gemspec_file, 'w') { |io| io.write(spec) }
120
- puts "Updated #{gemspec_file}"
121
- end
122
-
123
- desc "Validate #{gemspec_file}"
124
- task :validate do
125
- libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
126
- unless libfiles.empty?
127
- puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
128
- exit!
129
- end
130
- unless Dir['VERSION*'].empty?
131
- puts "A `VERSION` file at root level violates Gem best practices."
132
- exit!
133
- end
134
- end
135
-
@@ -1,5 +1,5 @@
1
1
  module Sawyer
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.7"
3
3
 
4
4
  class Error < StandardError; end
5
5
  end
@@ -12,4 +12,5 @@ require 'set'
12
12
  response
13
13
  serializer
14
14
  agent
15
+ hal_rels_parser
15
16
  ).each { |f| require File.expand_path("../sawyer/#{f}", __FILE__) }
@@ -5,6 +5,9 @@ module Sawyer
5
5
  class Agent
6
6
  NO_BODY = Set.new([:get, :head])
7
7
 
8
+ attr_accessor :links_parser
9
+ attr_accessor :allow_undefined_methods
10
+
8
11
  class << self
9
12
  attr_writer :serializer
10
13
  end
@@ -33,8 +36,11 @@ module Sawyer
33
36
  # Yields the Faraday::Connection if a block is given.
34
37
  def initialize(endpoint, options = nil)
35
38
  @endpoint = endpoint
36
- @conn = (options && options[:faraday]) || Faraday.new(endpoint)
39
+ @conn = (options && options[:faraday]) || Faraday.new
37
40
  @serializer = (options && options[:serializer]) || self.class.serializer
41
+ @links_parser = (options && options[:links_parser]) || HalLinksParser.new
42
+ @allow_undefined_methods = (options && options[:allow_undefined_methods])
43
+ @conn.url_prefix = @endpoint
38
44
  yield @conn if block_given?
39
45
  end
40
46
 
@@ -42,7 +48,7 @@ module Sawyer
42
48
  #
43
49
  # Returns a Sawyer::Relation::Map.
44
50
  def rels
45
- @rels ||= root.data.rels
51
+ @rels ||= root.data._rels
46
52
  end
47
53
 
48
54
  # Public: Retains a reference to the root response of the API.
@@ -115,12 +121,20 @@ module Sawyer
115
121
  @serializer.decode(str)
116
122
  end
117
123
 
124
+ def parse_links(data)
125
+ @links_parser.parse(data)
126
+ end
127
+
118
128
  def expand_url(url, options = nil)
119
129
  tpl = url.respond_to?(:expand) ? url : URITemplate.new(url.to_s)
120
130
  expand = tpl.method(:expand)
121
131
  options ? expand.call(options) : expand.call
122
132
  end
123
133
 
134
+ def allow_undefined_methods?
135
+ !!@allow_undefined_methods
136
+ end
137
+
124
138
  def inspect
125
139
  %(<#{self.class} #{@endpoint}>)
126
140
  end
@@ -0,0 +1,13 @@
1
+ module Sawyer
2
+
3
+ class HalLinksParser
4
+
5
+ def parse(data)
6
+ links = data.delete(:_links)
7
+
8
+ return data, links
9
+ end
10
+
11
+ end
12
+
13
+ end
@@ -13,7 +13,7 @@ module Sawyer
13
13
  #
14
14
  # Returns nothing.
15
15
  def <<(rel)
16
- @map[rel.name] = rel
16
+ @map[rel.name] = rel if rel
17
17
  end
18
18
 
19
19
  # Gets the raw Relation by its name.
@@ -82,7 +82,12 @@ module Sawyer
82
82
  #
83
83
  # Returns a Relation.
84
84
  def self.from_link(agent, name, options)
85
- new agent, name, options[:href], options[:method]
85
+ case options
86
+ when Hash
87
+ new agent, name, options[:href], options[:method]
88
+ when String
89
+ new agent, name, options
90
+ end
86
91
  end
87
92
 
88
93
  # A Relation represents an available next action for a resource.
@@ -94,7 +99,11 @@ module Sawyer
94
99
  def initialize(agent, name, href, method = nil)
95
100
  @agent = agent
96
101
  @name = name.to_sym
97
- @href_template = URITemplate.new(href.to_s)
102
+ @href = href
103
+ begin
104
+ @href_template = URITemplate.new(href.to_s)
105
+ rescue URITemplate::RFC6570::Invalid => e
106
+ end
98
107
 
99
108
  methods = nil
100
109
 
@@ -225,6 +234,7 @@ module Sawyer
225
234
  end
226
235
 
227
236
  def href(options = nil)
237
+ return @href if @href_template.nil?
228
238
  method = @href_template.method(:expand)
229
239
  options ? method.call(options) : method.call
230
240
  end
@@ -243,7 +253,7 @@ module Sawyer
243
253
  # Returns a Sawyer::Response.
244
254
  def call(data = nil, options = nil)
245
255
  m = options && options[:method]
246
- if m && !@available_methods.include?(m == :head ? :get : m)
256
+ if m && !@agent.allow_undefined_methods? && !@available_methods.include?(m == :head ? :get : m)
247
257
  raise ArgumentError, "method #{m.inspect} is not available: #{@available_methods.to_a.inspect}"
248
258
  end
249
259
 
@@ -1,20 +1,26 @@
1
1
  module Sawyer
2
2
  class Resource
3
- SPECIAL_METHODS = Set.new %w(agent rels fields)
3
+ SPECIAL_METHODS = Set.new(%w(agent rels fields))
4
4
  attr_reader :_agent, :_rels, :_fields
5
+ attr_reader :attrs
6
+ alias to_hash attrs
5
7
 
6
8
  # Initializes a Resource with the given data.
7
9
  #
8
10
  # agent - The Sawyer::Agent that made the API request.
9
11
  # data - Hash of key/value properties.
10
- def initialize(agent, data)
12
+ def initialize(agent, data = {})
11
13
  @_agent = agent
12
- @_rels = Relation.from_links(agent, data.delete(:_links))
13
- @_fields = Set.new []
14
+ data, links = agent.parse_links(data)
15
+ @_rels = Relation.from_links(agent, links)
16
+ @_fields = Set.new
17
+ @_metaclass = (class << self; self; end)
18
+ @attrs = {}
14
19
  data.each do |key, value|
15
20
  @_fields << key
16
- instance_variable_set "@#{key}", process_value(value)
21
+ @attrs[key.to_sym] = process_value(value)
17
22
  end
23
+ @_metaclass.send(:attr_accessor, *data.keys)
18
24
  end
19
25
 
20
26
  # Processes an individual value of this resource. Hashes get exploded
@@ -40,6 +46,29 @@ module Sawyer
40
46
  @_fields.include? key
41
47
  end
42
48
 
49
+ # Allow fields to be retrieved via Hash notation
50
+ #
51
+ # method - key name
52
+ #
53
+ # Returns the value from attrs if exists
54
+ def [](method)
55
+ send(method.to_sym)
56
+ rescue NoMethodError
57
+ nil
58
+ end
59
+
60
+ # Allow fields to be set via Hash notation
61
+ #
62
+ # method - key name
63
+ # value - value to set for the attr key
64
+ #
65
+ # Returns - value
66
+ def []=(method, value)
67
+ send("#{method}=", value)
68
+ rescue NoMethodError
69
+ nil
70
+ end
71
+
43
72
  ATTR_SETTER = '='.freeze
44
73
  ATTR_PREDICATE = '?'.freeze
45
74
 
@@ -47,14 +76,14 @@ module Sawyer
47
76
  def method_missing(method, *args)
48
77
  attr_name, suffix = method.to_s.scan(/([a-z0-9\_]+)(\?|\=)?$/i).first
49
78
  if suffix == ATTR_SETTER
50
- (class << self; self; end).send :attr_accessor, attr_name
79
+ @_metaclass.send(:attr_accessor, attr_name)
51
80
  @_fields << attr_name.to_sym
52
- instance_variable_set "@#{attr_name}", args.first
53
- elsif @_fields.include?(attr_name.to_sym)
54
- value = instance_variable_get("@#{attr_name}")
81
+ send(method, args.first)
82
+ elsif attr_name && @_fields.include?(attr_name.to_sym)
83
+ value = @attrs[attr_name.to_sym]
55
84
  case suffix
56
85
  when nil
57
- (class << self; self; end).send :attr_accessor, attr_name
86
+ @_metaclass.send(:attr_accessor, attr_name)
58
87
  value
59
88
  when ATTR_PREDICATE then !!value
60
89
  end
@@ -64,6 +93,25 @@ module Sawyer
64
93
  super
65
94
  end
66
95
  end
96
+
97
+ # Wire up accessor methods to pull from attrs
98
+ def self.attr_accessor(*attrs)
99
+ attrs.each do |attribute|
100
+ class_eval do
101
+ define_method attribute do
102
+ @attrs[attribute.to_sym]
103
+ end
104
+
105
+ define_method "#{attribute}=" do |value|
106
+ @attrs[attribute.to_sym] = value
107
+ end
108
+
109
+ define_method "#{attribute}?" do
110
+ !!@attrs[attribute.to_sym]
111
+ end
112
+ end
113
+ end
114
+ end
67
115
  end
68
116
  end
69
117
 
@@ -29,8 +29,7 @@ module Sawyer
29
29
  when Hash then Resource.new(agent, data)
30
30
  when Array then data.map { |hash| process_data(hash) }
31
31
  when nil then nil
32
- else
33
- raise ArgumentError, "Unable to process #{data.inspect}. Want a Hash or Array"
32
+ else data
34
33
  end
35
34
  end
36
35
 
@@ -49,6 +49,8 @@ module Sawyer
49
49
  @dump.call(encode_object(data))
50
50
  end
51
51
 
52
+ alias dump encode
53
+
52
54
  # Public: Decodes a String into an Object (usually a Hash or Array of
53
55
  # Hashes).
54
56
  #
@@ -60,10 +62,12 @@ module Sawyer
60
62
  decode_object(@load.call(data))
61
63
  end
62
64
 
65
+ alias load decode
66
+
63
67
  def encode_object(data)
64
68
  case data
65
69
  when Hash then encode_hash(data)
66
- when Array then data.map { |o| encode_object(data) }
70
+ when Array then data.map { |o| encode_object(o) }
67
71
  else data
68
72
  end
69
73
  end
@@ -73,6 +77,7 @@ module Sawyer
73
77
  case value = hash[key]
74
78
  when Date then hash[key] = value.to_time.utc.xmlschema
75
79
  when Time then hash[key] = value.utc.xmlschema
80
+ when Hash then hash[key] = encode_hash(value)
76
81
  end
77
82
  end
78
83
  hash
@@ -81,7 +86,7 @@ module Sawyer
81
86
  def decode_object(data)
82
87
  case data
83
88
  when Hash then decode_hash(data)
84
- when Array then data.map { |o| decode_object(data) }
89
+ when Array then data.map { |o| decode_object(o) }
85
90
  else data
86
91
  end
87
92
  end
@@ -94,13 +99,19 @@ module Sawyer
94
99
  end
95
100
 
96
101
  def decode_hash_value(key, value)
97
- if key =~ /^_(at|on)$/
102
+ if time_field?(key, value)
98
103
  Time.parse(value)
99
104
  elsif value.is_a?(Hash)
100
105
  decode_hash(value)
106
+ elsif value.is_a?(Array)
107
+ value.map { |o| decode_hash_value(key, o) }
101
108
  else
102
109
  value
103
110
  end
104
111
  end
112
+
113
+ def time_field?(key, value)
114
+ value && key =~ /_(at|on)$/
115
+ end
105
116
  end
106
117
  end
@@ -1,78 +1,33 @@
1
- ## This is the rakegem gemspec template. Make sure you read and understand
2
- ## all of the comments. Some sections require modification, and others can
3
- ## be deleted if you don't need them. Once you understand the contents of
4
- ## this file, feel free to delete any comments that begin with two hash marks.
5
- ## You can find comprehensive Gem::Specification documentation, at
6
- ## http://docs.rubygems.org/read/chapter/20
7
- Gem::Specification.new do |s|
8
- s.specification_version = 2 if s.respond_to? :specification_version=
9
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
- s.rubygems_version = '1.3.5'
1
+ lib = "sawyer"
2
+ lib_file = File.expand_path("../lib/#{lib}.rb", __FILE__)
3
+ File.read(lib_file) =~ /\bVERSION\s*=\s*["'](.+?)["']/
4
+ version = $1
11
5
 
12
- ## Leave these as is they will be modified for you by the rake gemspec task.
13
- ## If your rubyforge_project name is different, then edit it and comment out
14
- ## the sub! line in the Rakefile
15
- s.name = 'sawyer'
16
- s.version = '0.0.4'
17
- s.date = '2012-09-27'
18
- s.rubyforge_project = 'sawyer'
6
+ Gem::Specification.new do |spec|
7
+ spec.specification_version = 2 if spec.respond_to? :specification_version=
8
+ spec.required_rubygems_version = Gem::Requirement.new(">= 1.3.5") if spec.respond_to? :required_rubygems_version=
19
9
 
20
- ## Make sure your summary is short. The description may be as long
21
- ## as you like.
22
- s.summary = "Secret User Agent of HTTP"
23
- s.description = "#{s.summary} built on Faraday"
10
+ spec.name = lib
11
+ spec.version = version
24
12
 
25
- ## List the primary authors. If there are a bunch of authors, it's probably
26
- ## better to set the email to an email list or something. If you don't have
27
- ## a custom homepage, consider using your GitHub URL or the like.
28
- s.authors = ["Rick Olson"]
29
- s.email = 'technoweenie@gmail.com'
30
- s.homepage = 'https://github.com/technoweenie/sawyer'
13
+ spec.summary = "Secret User Agent of HTTP"
31
14
 
32
- ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
33
- ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
34
- s.require_paths = %w[lib]
15
+ spec.authors = ["Rick Olson"]
16
+ spec.email = 'technoweenie@gmail.com'
17
+ spec.homepage = 'https://github.com/lostisland/sawyer'
18
+ spec.licenses = ['MIT']
35
19
 
36
- ## List your runtime dependencies here. Runtime dependencies are those
37
- ## that are needed for an end user to actually USE your code.
38
- s.add_dependency('faraday', ['~> 0.8.4'])
39
- s.add_dependency('uri_template', ['~> 0.5.0'])
20
+ spec.add_dependency 'faraday', ['~> 0.8.4']
21
+ spec.add_dependency 'uri_template', ['~> 0.5.0']
40
22
 
41
- ## List your development dependencies here. Development dependencies are
42
- ## those that are only needed during development
43
- #s.add_development_dependency('DEVDEPNAME', [">= 1.1.0", "< 2.0.0"])
23
+ spec.files = %w(Gemfile LICENSE.md README.md Rakefile)
24
+ spec.files << "#{lib}.gemspec"
25
+ spec.files += Dir.glob("lib/**/*.rb")
26
+ spec.files += Dir.glob("test/**/*.rb")
27
+ spec.files += Dir.glob("script/*")
44
28
 
45
- ## Leave this section as-is. It will be automatically generated from the
46
- ## contents of your Git repository via the gemspec task. DO NOT REMOVE
47
- ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
48
- # = MANIFEST =
49
- s.files = %w[
50
- Gemfile
51
- LICENSE.md
52
- README.md
53
- Rakefile
54
- SPEC.md
55
- example/client.rb
56
- example/nigiri.schema.json
57
- example/server.rb
58
- example/user.schema.json
59
- lib/sawyer.rb
60
- lib/sawyer/agent.rb
61
- lib/sawyer/relation.rb
62
- lib/sawyer/resource.rb
63
- lib/sawyer/response.rb
64
- lib/sawyer/serializer.rb
65
- sawyer.gemspec
66
- test/agent_test.rb
67
- test/helper.rb
68
- test/relation_test.rb
69
- test/resource_test.rb
70
- test/response_test.rb
71
- ]
72
- # = MANIFEST =
73
-
74
- ## Test files will be grabbed from the file list. Make sure the path glob
75
- ## matches what you actually use.
76
- s.test_files = s.files.select { |path| path =~ /^test\/.*_test\.rb/ }
29
+ dev_null = File.exist?('/dev/null') ? '/dev/null' : 'NUL'
30
+ git_files = `git ls-files -z 2>#{dev_null}`
31
+ spec.files &= git_files.split("\0") if $?.success?
77
32
  end
78
33
 
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ # Usage: script/console
3
+ # Starts an IRB console with this library loaded.
4
+
5
+ gemspec="$(ls *.gemspec | head -1)"
6
+
7
+ exec bundle exec irb -r "${gemspec%.*}"
8
+
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ # Usage: script/gem
3
+ # Updates the gemspec and builds a new gem in the pkg directory.
4
+
5
+ mkdir -p pkg
6
+ gem build *.gemspec
7
+ mv *.gem pkg
8
+
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env bash
2
+ # Usage: script/release
3
+ # Build the package, tag a commit, push it to origin, and then release the
4
+ # package publicly.
5
+
6
+ set -e
7
+
8
+ version="$(script/package | grep Version: | awk '{print $2}')"
9
+ [ -n "$version" ] || exit 1
10
+
11
+ git commit --allow-empty -a -m "Release $version"
12
+ git tag "v$version"
13
+ git push origin
14
+ git push origin "v$version"
15
+ git push legacy
16
+ git push legacy "v$version"
17
+ gem push pkg/*-${version}.gem
18
+
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ # Usage: script/test
3
+ # Runs the library's test suite.
4
+
5
+ bundle exec rake test
@@ -2,6 +2,16 @@ require File.expand_path("../helper", __FILE__)
2
2
 
3
3
  module Sawyer
4
4
  class AgentTest < TestCase
5
+
6
+ class InlineRelsParser
7
+ def parse(data)
8
+ links = {}
9
+ data.keys.select {|k| k[/_url$/] }.each {|k| links[k.to_s.gsub(/_url$/, '')] = data.delete(k) }
10
+
11
+ return data, links
12
+ end
13
+ end
14
+
5
15
  def setup
6
16
  @stubs = Faraday::Adapter::Test::Stubs.new
7
17
  @agent = Sawyer::Agent.new "http://foo.com/a/" do |conn|
@@ -25,6 +35,31 @@ module Sawyer
25
35
  assert_equal :get, @agent.rels[:users].method
26
36
  end
27
37
 
38
+ def test_allows_custom_rel_parsing
39
+ @stubs.get '/a/' do |env|
40
+ assert_equal 'foo.com', env[:url].host
41
+
42
+ [200, {}, Sawyer::Agent.encode(
43
+ :url => '/',
44
+ :users_url => '/users',
45
+ :repos_url => '/repos')]
46
+ end
47
+
48
+ agent = Sawyer::Agent.new "http://foo.com/a/" do |conn|
49
+ conn.builder.handlers.delete(Faraday::Adapter::NetHttp)
50
+ conn.adapter :test, @stubs
51
+ end
52
+ agent.links_parser = InlineRelsParser.new
53
+
54
+ assert_equal 200, agent.root.status
55
+
56
+ assert_equal '/users', agent.rels[:users].href
57
+ assert_equal :get, agent.rels[:users].method
58
+ assert_equal '/repos', agent.rels[:repos].href
59
+ assert_equal :get, agent.rels[:repos].method
60
+
61
+ end
62
+
28
63
  def test_saves_root_endpoint
29
64
  @stubs.get '/a/' do |env|
30
65
  [200, {}, '{}']
@@ -79,6 +114,26 @@ module Sawyer
79
114
  :query => {:foo => 'bar'}
80
115
  assert_equal 200, res.status
81
116
  end
117
+
118
+ def test_encodes_and_decodes_times
119
+ time = Time.at(Time.now.to_i)
120
+ data = {:a => 1, :b => true, :c => 'c', :created_at => time, :published_at => nil}
121
+ data = [data.merge(:foo => [data])]
122
+ encoded = Sawyer::Agent.encode(data)
123
+ decoded = Sawyer::Agent.decode(encoded)
124
+
125
+ 2.times do
126
+ assert_equal 1, decoded.size
127
+ decoded = decoded.shift
128
+
129
+ assert_equal 1, decoded[:a]
130
+ assert_equal true, decoded[:b]
131
+ assert_equal 'c', decoded[:c]
132
+ assert_equal time, decoded[:created_at]
133
+ assert_nil decoded[:published_at]
134
+ decoded = decoded[:foo]
135
+ end
136
+ end
82
137
  end
83
138
  end
84
139
 
@@ -28,6 +28,23 @@ module Sawyer
28
28
  assert_kind_of URITemplate, rel.href_template
29
29
  end
30
30
 
31
+ def test_builds_rels_from_hash
32
+ index = {
33
+ 'self' => '/users/1'
34
+ }
35
+
36
+ rels = Sawyer::Relation.from_links(nil, index)
37
+
38
+ assert_equal 1, rels.size
39
+ assert_equal [:self], rels.keys
40
+ assert rel = rels[:self]
41
+ assert_equal :self, rel.name
42
+ assert_equal '/users/1', rel.href
43
+ assert_equal :get, rel.method
44
+ assert_equal [:get], rel.available_methods.to_a
45
+ assert_kind_of URITemplate, rel.href_template
46
+ end
47
+
31
48
  def test_builds_rels_from_hash_index
32
49
  index = {
33
50
  'self' => {:href => '/users/1'}
@@ -109,6 +126,41 @@ module Sawyer
109
126
  assert_equal 404, rel.get.status
110
127
  assert_equal 200, rel.get(:uri => {'user' => 'octocat', 'repo' => 'hello', 'a' => 1, 'b' => 2}).status
111
128
  end
129
+
130
+ def test_handles_invalid_uri
131
+ hash = {:href => '/this has spaces', :method => 'post'}
132
+ rel = Sawyer::Relation.from_link(nil, :self, hash)
133
+
134
+ assert_equal :self, rel.name
135
+ assert_equal '/this has spaces', rel.href
136
+ end
137
+
138
+ def test_allows_all_methods_when_not_in_strict_mode
139
+
140
+ agent = Sawyer::Agent.new "http://foo.com/a/", :allow_undefined_methods => true do |conn|
141
+ conn.builder.handlers.delete(Faraday::Adapter::NetHttp)
142
+ conn.adapter :test do |stubs|
143
+ stubs.get '/a/1' do
144
+ [200, {}, '{}']
145
+ end
146
+ stubs.delete '/a/1' do
147
+ [204, {}, '{}']
148
+ end
149
+ stubs.post '/a/1' do
150
+ [200, {}, '{}']
151
+ end
152
+ stubs.put '/a/1' do
153
+ [204, {}, '{}']
154
+ end
155
+ end
156
+ end
157
+
158
+ rel = Sawyer::Relation.new agent, :self, "/a/1"
159
+ assert_equal 200, rel.get.status
160
+ assert_equal 200, rel.post.status
161
+ assert_equal 204, rel.put.status
162
+ assert_equal 204, rel.delete.status
163
+ end
112
164
  end
113
165
  end
114
166
 
@@ -2,19 +2,28 @@ require File.expand_path("../helper", __FILE__)
2
2
 
3
3
  module Sawyer
4
4
  class ResourceTest < TestCase
5
+
6
+ def setup
7
+ @stubs = Faraday::Adapter::Test::Stubs.new
8
+ @agent = Sawyer::Agent.new "http://foo.com/a/" do |conn|
9
+ conn.builder.handlers.delete(Faraday::Adapter::NetHttp)
10
+ conn.adapter :test, @stubs
11
+ end
12
+ end
13
+
5
14
  def test_accessible_keys
6
- res = Resource.new :agent, :a => 1,
15
+ res = Resource.new @agent, :a => 1,
7
16
  :_links => {:self => {:href => '/'}}
8
17
 
9
18
  assert_equal 1, res.a
10
19
  assert res.rels[:self]
11
- assert_equal :agent, res.agent
20
+ assert_equal @agent, res.agent
12
21
  assert_equal 1, res.fields.size
13
22
  assert res.fields.include?(:a)
14
23
  end
15
24
 
16
25
  def test_clashing_keys
17
- res = Resource.new :agent, :agent => 1, :rels => 2, :fields => 3,
26
+ res = Resource.new @agent, :agent => 1, :rels => 2, :fields => 3,
18
27
  :_links => {:self => {:href => '/'}}
19
28
 
20
29
  assert_equal 1, res.agent
@@ -22,7 +31,7 @@ module Sawyer
22
31
  assert_equal 3, res.fields
23
32
 
24
33
  assert res._rels[:self]
25
- assert_equal :agent, res._agent
34
+ assert_equal @agent, res._agent
26
35
  assert_equal 3, res._fields.size
27
36
  [:agent, :rels, :fields].each do |f|
28
37
  assert res._fields.include?(f)
@@ -30,7 +39,7 @@ module Sawyer
30
39
  end
31
40
 
32
41
  def test_nested_object
33
- res = Resource.new :agent,
42
+ res = Resource.new @agent,
34
43
  :user => {:id => 1, :_links => {:self => {:href => '/users/1'}}},
35
44
  :_links => {:self => {:href => '/'}}
36
45
 
@@ -41,7 +50,7 @@ module Sawyer
41
50
  end
42
51
 
43
52
  def test_nested_collection
44
- res = Resource.new :agent,
53
+ res = Resource.new @agent,
45
54
  :users => [{:id => 1, :_links => {:self => {:href => '/users/1'}}}],
46
55
  :_links => {:self => {:href => '/'}}
47
56
 
@@ -55,7 +64,7 @@ module Sawyer
55
64
  end
56
65
 
57
66
  def test_attribute_predicates
58
- res = Resource.new :agent, :a => 1, :b => true, :c => nil, :d => false
67
+ res = Resource.new @agent, :a => 1, :b => true, :c => nil, :d => false
59
68
 
60
69
  assert res.a?
61
70
  assert res.b?
@@ -64,7 +73,7 @@ module Sawyer
64
73
  end
65
74
 
66
75
  def test_attribute_setter
67
- res = Resource.new :agent, :a => 1
76
+ res = Resource.new @agent, :a => 1
68
77
  assert_equal 1, res.a
69
78
  assert !res.key?(:b)
70
79
 
@@ -74,10 +83,10 @@ module Sawyer
74
83
  end
75
84
 
76
85
  def test_dynamic_attribute_methods_from_getter
77
- res = Resource.new :agent, :a => 1
78
- assert res.key?(:a)
79
- assert !res.respond_to?(:a)
80
- assert !res.respond_to?(:a=)
86
+ res = Resource.new @agent, :a => 1
87
+ assert res.key?(:a)
88
+ assert res.respond_to?(:a)
89
+ assert res.respond_to?(:a=)
81
90
 
82
91
  assert_equal 1, res.a
83
92
  assert res.respond_to?(:a)
@@ -85,7 +94,7 @@ module Sawyer
85
94
  end
86
95
 
87
96
  def test_dynamic_attribute_methods_from_setter
88
- res = Resource.new :agent, :a => 1
97
+ res = Resource.new @agent, :a => 1
89
98
  assert !res.key?(:b)
90
99
  assert !res.respond_to?(:b)
91
100
  assert !res.respond_to?(:b=)
@@ -95,5 +104,19 @@ module Sawyer
95
104
  assert res.respond_to?(:b)
96
105
  assert res.respond_to?(:b=)
97
106
  end
107
+
108
+ def test_attrs
109
+ res = Resource.new @agent, :a => 1
110
+ hash = {:a => 1 }
111
+ assert_equal hash, res.attrs
112
+ end
113
+
114
+ def test_handle_hash_notation_with_string_key
115
+ res = Resource.new @agent, :a => 1
116
+ assert_equal 1, res['a']
117
+
118
+ res[:b] = 2
119
+ assert_equal 2, res.b
120
+ end
98
121
  end
99
122
  end
@@ -16,6 +16,11 @@ module Sawyer
16
16
  }
17
17
  )]
18
18
  end
19
+
20
+ stub.get '/emails' do
21
+ emails = %w(rick@example.com technoweenie@example.com)
22
+ [200, {'Content-Type' => 'application/json'}, Sawyer::Agent.encode(emails)]
23
+ end
19
24
  end
20
25
  end
21
26
 
@@ -55,6 +60,11 @@ module Sawyer
55
60
  assert_equal 201, res.status
56
61
  assert_nil res.data
57
62
  end
63
+
64
+ def test_handles_arrays_of_strings
65
+ res = @agent.call(:get, '/emails')
66
+ assert_equal 'rick@example.com', res.data.first
67
+ end
58
68
  end
59
69
  end
60
70
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sawyer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.7
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-09-27 00:00:00.000000000 Z
12
+ date: 2013-01-05 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: faraday
@@ -43,7 +43,7 @@ dependencies:
43
43
  - - ~>
44
44
  - !ruby/object:Gem::Version
45
45
  version: 0.5.0
46
- description: Secret User Agent of HTTP built on Faraday
46
+ description:
47
47
  email: technoweenie@gmail.com
48
48
  executables: []
49
49
  extensions: []
@@ -53,25 +53,26 @@ files:
53
53
  - LICENSE.md
54
54
  - README.md
55
55
  - Rakefile
56
- - SPEC.md
57
- - example/client.rb
58
- - example/nigiri.schema.json
59
- - example/server.rb
60
- - example/user.schema.json
61
- - lib/sawyer.rb
56
+ - sawyer.gemspec
62
57
  - lib/sawyer/agent.rb
58
+ - lib/sawyer/hal_rels_parser.rb
63
59
  - lib/sawyer/relation.rb
64
60
  - lib/sawyer/resource.rb
65
61
  - lib/sawyer/response.rb
66
62
  - lib/sawyer/serializer.rb
67
- - sawyer.gemspec
63
+ - lib/sawyer.rb
68
64
  - test/agent_test.rb
69
65
  - test/helper.rb
70
66
  - test/relation_test.rb
71
67
  - test/resource_test.rb
72
68
  - test/response_test.rb
73
- homepage: https://github.com/technoweenie/sawyer
74
- licenses: []
69
+ - script/console
70
+ - script/package
71
+ - script/release
72
+ - script/test
73
+ homepage: https://github.com/lostisland/sawyer
74
+ licenses:
75
+ - MIT
75
76
  post_install_message:
76
77
  rdoc_options: []
77
78
  require_paths:
@@ -82,23 +83,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
82
83
  - - ! '>='
83
84
  - !ruby/object:Gem::Version
84
85
  version: '0'
85
- segments:
86
- - 0
87
- hash: -3466190819871303014
88
86
  required_rubygems_version: !ruby/object:Gem::Requirement
89
87
  none: false
90
88
  requirements:
91
89
  - - ! '>='
92
90
  - !ruby/object:Gem::Version
93
- version: '0'
91
+ version: 1.3.5
94
92
  requirements: []
95
- rubyforge_project: sawyer
93
+ rubyforge_project:
96
94
  rubygems_version: 1.8.23
97
95
  signing_key:
98
96
  specification_version: 2
99
97
  summary: Secret User Agent of HTTP
100
- test_files:
101
- - test/agent_test.rb
102
- - test/relation_test.rb
103
- - test/resource_test.rb
104
- - test/response_test.rb
98
+ test_files: []
99
+ has_rdoc:
data/SPEC.md DELETED
@@ -1,71 +0,0 @@
1
- # Sawyer
2
-
3
- To use Sawyer, create a new Agent with a URL endpoint.
4
-
5
- ```ruby
6
- endpoint = "http://my-api.com"
7
- agent = Sawyer::Agent.new endpoint do |conn|
8
- conn.headers['content-type'] = 'application/vnd.my-api+json'
9
- end
10
- ```
11
-
12
- From here, we can access the root to get the initial actions.
13
-
14
- ```ruby
15
- root = agent.start
16
- ```
17
-
18
- Every request in Sawyer returns a Sawyer::Response. It's very similar
19
- to a raw Faraday::Response, with a few extra methods.
20
-
21
- ```ruby
22
- # HTTP Headers
23
- root.headers
24
-
25
- # HTTP status
26
- root.status
27
-
28
- # The JSON Schema
29
- root.schema
30
-
31
- # The link relations
32
- root.rels
33
-
34
- # The contents (probably empty from the root)
35
- root.data
36
- ```
37
-
38
- Now, we can access a relation off the root resource.
39
-
40
- ```ruby
41
- resource = root.data
42
- res = resource.rels[:users].call :query => {:sort => 'login'}
43
-
44
- # An array of users
45
- users = res.data
46
- ```
47
-
48
- This call returns two types of relations: relations on the collection of
49
- users, and relations on each user. You can access the collection
50
- resources from the response.
51
-
52
- ```ruby
53
- # get the next page of users
54
- res2 = res.rels[:next].call
55
-
56
- # page 2 of the users collection
57
- res2.data
58
- ```
59
-
60
- Each user has it's own relations too:
61
-
62
- ```ruby
63
- # favorite the previous user
64
- users.each_with_index do |user, index|
65
- res = user.rels[:favorite].call users[index-1]
66
- if !res.success?
67
- puts "#{user.name} could not favorite #{users[index-1].name}"
68
- end
69
- end
70
- ```
71
-
@@ -1,50 +0,0 @@
1
- require File.expand_path("../../lib/sawyer", __FILE__)
2
- require 'faraday'
3
- require 'pp'
4
-
5
- endpoint = "http://localhost:9393/"
6
- agent = Sawyer::Agent.new(endpoint) do |http|
7
- http.headers['content-type'] = 'application/json'
8
- end
9
- puts agent.inspect
10
- puts
11
-
12
- root = agent.start
13
- puts root.inspect
14
-
15
- puts "LISTING USERS"
16
- users_rel = root.data.rels[:users]
17
-
18
- puts users_rel.inspect
19
- puts
20
-
21
- users_res = users_rel.get
22
- puts users_res.inspect
23
-
24
- users = users_res.data
25
-
26
- users.each do |user|
27
- puts "#{user.login} favorites:"
28
-
29
- fav_res = user.rels[:favorites].get
30
-
31
- fav_res.data.each do |sushi|
32
- puts "- #{sushi.inspect})"
33
- end
34
- puts
35
- end
36
-
37
- puts "CREATING USER"
38
- create_user_rel = root.data.rels[:users]
39
-
40
- puts create_user_rel.inspect
41
-
42
- created = create_user_rel.post(:login => 'booya')
43
- puts created.inspect
44
- puts
45
-
46
- puts "ADD A FAVORITE"
47
- created_user = created.data
48
- create_fav_res = created_user.rels[:favorites].post nil, :query => {:id => 1}
49
- puts create_fav_res.inspect
50
-
@@ -1,47 +0,0 @@
1
- {
2
- "type": "object",
3
- "relations": [
4
- {"rel": "all", "href": "/nigiri"}
5
- ],
6
- "properties": {
7
- "id": {
8
- "type": "integer",
9
- "minimum": 1,
10
- "readonly": true
11
- },
12
- "name": {
13
- "type": "string"
14
- },
15
- "fish": {
16
- "type": "string"
17
- },
18
- "_links": {
19
- "type": "array",
20
- "items": {
21
- "type": "object",
22
- "properties": {
23
- "rel": {
24
- "type": "string"
25
- },
26
- "href": {
27
- "type": "string",
28
- "optional": true
29
- },
30
- "method": {
31
- "type": "string",
32
- "default": "get"
33
- },
34
- "schema": {
35
- "type": "string",
36
- "optional": true
37
- }
38
- },
39
- "additionalProperties": false
40
- },
41
- "additionalProperties": false,
42
- "readonly": true
43
- }
44
- }
45
- }
46
-
47
-
@@ -1,114 +0,0 @@
1
- require 'sinatra'
2
- require 'yajl'
3
-
4
- get '/' do
5
- app_type
6
-
7
- Yajl.dump({
8
- :_links => {
9
- :users => {:href => "/users", :method => 'get,post'},
10
- :nigiri => {:href => "/nigiri"}
11
- }
12
- }, :pretty => true)
13
- end
14
-
15
- def app_type
16
- content_type "application/vnd.sushihub+json"
17
- end
18
-
19
- users = [
20
- {:id => 1, :login => 'sawyer', :created_at => Time.utc(2004, 9, 22),
21
- :_links => {
22
- :self => {:href => '/users/sawyer'},
23
- :favorites => {:href => '/users/sawyer/favorites', :method => 'get,post'}
24
- }},
25
- {:id => 2, :login => 'faraday', :created_at => Time.utc(2004, 12, 22),
26
- :_links => {
27
- :self => {:href => '/users/faraday'},
28
- :favorites => {:href => '/users/faraday/favorites', :method => 'get,post'}
29
- }}
30
- ]
31
-
32
- nigiri = [
33
- {:id => 1, :name => 'sake', :fish => 'salmon',
34
- :_links => {
35
- :self => {:href => '/nigiri/sake'}
36
- }},
37
- {:id => 2, :name => 'unagi', :fish => 'eel',
38
- :_links => {
39
- :self => {:href => '/nigiri/unagi'}
40
- }}
41
- ]
42
-
43
- get '/users' do
44
- app_type
45
-
46
- Yajl.dump users, :pretty => true
47
- end
48
-
49
- new_users = {}
50
- post '/users' do
51
- if env['CONTENT_TYPE'].to_s !~ /json/i
52
- halt 400, "Needs JSON"
53
- end
54
-
55
- app_type
56
-
57
- hash = Yajl.load request.body.read, :symbolize_keys => true
58
- new_users[hash[:login]] = hash
59
-
60
- headers "Location" => "/users/#{hash[:login]}"
61
- status 201
62
- Yajl.dump hash.update(
63
- :id => 3,
64
- :created_at => Time.now.utc.xmlschema,
65
- :_links => {
66
- :self => {:href => "/users/#{hash[:login]}"},
67
- :favorites => {:href => "/users/#{hash[:login]}/favorites", :method => 'get,post'}
68
- }
69
- ), :pretty => true
70
- end
71
-
72
- get '/users/:login' do
73
- headers 'Content-Type' => app_type
74
- if hash = users.detect { |u| u[:login] == params[:login] }
75
- Yajl.dump hash, :pretty => true
76
- else
77
- halt 404
78
- end
79
- end
80
-
81
- get '/users/:login/favorites' do
82
- app_type
83
-
84
- case params[:login]
85
- when users[0][:login] then Yajl.dump([nigiri[0]], :pretty => true)
86
- when users[1][:login] then Yajl.dump([], :pretty => true)
87
- else halt 404
88
- end
89
- end
90
-
91
- post '/users/:login/favorites' do
92
- if params[:id].to_i > 0
93
- halt 201
94
- else
95
- halt 422
96
- end
97
- end
98
-
99
- get '/nigiri' do
100
- app_type
101
-
102
- Yajl.dump nigiri, :pretty => true
103
- end
104
-
105
- get '/nigiri/:name' do
106
- app_type
107
-
108
- if hash = nigiri.detect { |n| n[:name] == params[:name] }
109
- Yajl.dump hash, :pretty => true
110
- else
111
- halt(404)
112
- end
113
- end
114
-
@@ -1,51 +0,0 @@
1
- {
2
- "type": "object",
3
- "relations": [
4
- {"rel": "all", "href": "/users"},
5
- {"rel": "create", "href": "/users", "method": "post"},
6
- {"rel": "favorites", "schema": "/schema/nigiri"},
7
- {"rel": "favorites/create", "method": "post"}
8
- ],
9
- "properties": {
10
- "id": {
11
- "type": "integer",
12
- "minimum": 1,
13
- "readonly": true
14
- },
15
- "login": {
16
- "type": "string"
17
- },
18
- "created_at": {
19
- "type": "string",
20
- "pattern": "\\d{8}T\\d{6}Z",
21
- "readonly": true
22
- },
23
- "_links": {
24
- "type": "array",
25
- "items": {
26
- "type": "object",
27
- "properties": {
28
- "rel": {
29
- "type": "string"
30
- },
31
- "href": {
32
- "type": "string",
33
- "optional": true
34
- },
35
- "method": {
36
- "type": "string",
37
- "default": "get"
38
- },
39
- "schema": {
40
- "type": "string",
41
- "optional": true
42
- }
43
- },
44
- "additionalProperties": false
45
- },
46
- "additionalProperties": false,
47
- "readonly": true
48
- }
49
- }
50
- }
51
-