sawyer 0.0.4 → 0.0.7

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