sawyer-0.6.0/0000755000004100000410000000000012466121537013066 5ustar www-datawww-datasawyer-0.6.0/Rakefile0000644000004100000410000000032612466121537014534 0ustar www-datawww-datarequire 'rubygems' require 'rake' task :default => :test require 'rake/testtask' Rake::TestTask.new(:test) do |test| test.libs << 'lib' << 'test' test.pattern = 'test/**/*_test.rb' test.verbose = true end sawyer-0.6.0/Gemfile0000644000004100000410000000006112466121537014356 0ustar www-datawww-datasource "http://rubygems.org" gemspec gem 'rake' sawyer-0.6.0/script/0000755000004100000410000000000012466121537014372 5ustar www-datawww-datasawyer-0.6.0/script/bootstrap0000755000004100000410000000005712466121537016337 0ustar www-datawww-data#!/bin/sh set -e bundle install --quiet "$@" sawyer-0.6.0/script/test0000755000004100000410000000016212466121537015276 0ustar www-datawww-data#!/usr/bin/env bash # Usage: script/test # Runs the library's test suite. script/bootstrap bundle exec rake test sawyer-0.6.0/script/release0000755000004100000410000000060112466121537015735 0ustar www-datawww-data#!/usr/bin/env bash # Usage: script/release # Build the package, tag a commit, push it to origin, and then release the # package publicly. set -e version="$(script/package | grep Version: | awk '{print $2}')" [ -n "$version" ] || exit 1 git commit --allow-empty -a -m "Release $version" git tag "v$version" git push origin git push origin "v$version" gem push pkg/*-${version}.gem sawyer-0.6.0/script/console0000755000004100000410000000025512466121537015764 0ustar www-datawww-data#!/usr/bin/env bash # Usage: script/console # Starts an IRB console with this library loaded. gemspec="$(ls *.gemspec | head -1)" exec bundle exec irb -r "${gemspec%.*}" sawyer-0.6.0/script/package0000755000004100000410000000023112466121537015707 0ustar www-datawww-data#!/usr/bin/env bash # Usage: script/gem # Updates the gemspec and builds a new gem in the pkg directory. mkdir -p pkg gem build *.gemspec mv *.gem pkg sawyer-0.6.0/LICENSE.md0000644000004100000410000000203612466121537014473 0ustar www-datawww-dataCopyright (c) 2011 rick olson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. sawyer-0.6.0/lib/0000755000004100000410000000000012466121537013634 5ustar www-datawww-datasawyer-0.6.0/lib/sawyer.rb0000644000004100000410000000040112466121537015466 0ustar www-datawww-datamodule Sawyer VERSION = "0.6.0" class Error < StandardError; end end require 'set' %w( resource relation response serializer agent link_parsers/hal link_parsers/simple ).each { |f| require File.expand_path("../sawyer/#{f}", __FILE__) } sawyer-0.6.0/lib/sawyer/0000755000004100000410000000000012466121537015146 5ustar www-datawww-datasawyer-0.6.0/lib/sawyer/response.rb0000644000004100000410000000321312466121537017330 0ustar www-datawww-datamodule Sawyer class Response attr_reader :agent, :status, :headers, :data, :rels # Builds a Response after a completed request. # # agent - The Sawyer::Agent that is managing the API connection. # res - A Faraday::Response. def initialize(agent, res, options = {}) @agent = agent @status = res.status @headers = res.headers @env = res.env @data = @headers[:content_type] =~ /json|msgpack/ ? process_data(@agent.decode_body(res.body)) : res.body @rels = process_rels @started = options[:sawyer_started] @ended = options[:sawyer_ended] end # Turns parsed contents from an API response into a Resource or # collection of Resources. # # data - Either an Array or Hash parsed from JSON. # # Returns either a Resource or Array of Resources. def process_data(data) case data when Hash then Resource.new(agent, data) when Array then data.map { |hash| process_data(hash) } when nil then nil else data end end # Finds link relations from 'Link' response header # # Returns an array of Relations def process_rels links = ( @headers["Link"] || "" ).split(', ').map do |link| href, name = link.match(/<(.*?)>; rel="(\w+)"/).captures [name.to_sym, Relation.from_link(@agent, name, :href => href)] end Hash[*links.flatten] end def timing @timing ||= @ended - @started end def time @ended end def inspect %(#<#{self.class}: #{@status} @rels=#{@rels.inspect} @data=#{@data.inspect}>) end end end sawyer-0.6.0/lib/sawyer/agent.rb0000644000004100000410000001057312466121537016577 0ustar www-datawww-datarequire 'faraday' require 'addressable/template' module Sawyer class Agent NO_BODY = Set.new([:get, :head]) attr_accessor :links_parser attr_accessor :allow_undefined_methods class << self attr_writer :serializer end def self.serializer @serializer ||= Serializer.any_json end def self.encode(data) serializer.encode(data) end def self.decode(data) serializer.decode(data) end # Agents handle making the requests, and passing responses to # Sawyer::Response. # # endpoint - String URI of the API entry point. # options - Hash of options. # :allow_undefined_methods - Allow relations to call all the HTTP verbs, # not just the ones defined. # :faraday - Optional Faraday::Connection to use. # :links_parser - Optional parser to parse link relations # Defaults: Sawyer::LinkParsers::Hal.new # :serializer - Optional serializer Class. Defaults to # self.serializer_class. # # Yields the Faraday::Connection if a block is given. def initialize(endpoint, options = nil) @endpoint = endpoint @conn = (options && options[:faraday]) || Faraday.new @serializer = (options && options[:serializer]) || self.class.serializer @links_parser = (options && options[:links_parser]) || Sawyer::LinkParsers::Hal.new @allow_undefined_methods = (options && options[:allow_undefined_methods]) @conn.url_prefix = @endpoint yield @conn if block_given? end # Public: Retains a reference to the root relations of the API. # # Returns a Sawyer::Relation::Map. def rels @rels ||= root.data._rels end # Public: Retains a reference to the root response of the API. # # Returns a Sawyer::Response. def root @root ||= start end # Public: Hits the root of the API to get the initial actions. # # Returns a Sawyer::Response. def start call :get, @endpoint end # Makes a request through Faraday. # # method - The Symbol name of an HTTP method. # url - The String URL to access. This can be relative to the Agent's # endpoint. # data - The Optional Hash or Resource body to be sent. :get or :head # requests can have no body, so this can be the options Hash # instead. # options - Hash of option to configure the API request. # :headers - Hash of API headers to set. # :query - Hash of URL query params to set. # # Returns a Sawyer::Response. def call(method, url, data = nil, options = nil) if NO_BODY.include?(method) options ||= data data = nil end options ||= {} url = expand_url(url, options[:uri]) started = nil res = @conn.send method, url do |req| if data req.body = data.is_a?(String) ? data : encode_body(data) end if params = options[:query] req.params.update params end if headers = options[:headers] req.headers.update headers end started = Time.now end Response.new self, res, :sawyer_started => started, :sawyer_ended => Time.now end # Encodes an object to a string for the API request. # # data - The Hash or Resource that is being sent. # # Returns a String. def encode_body(data) @serializer.encode(data) end # Decodes a String response body to a resource. # # str - The String body from the response. # # Returns an Object resource (Hash by default). def decode_body(str) @serializer.decode(str) end def parse_links(data) @links_parser.parse(data) end def expand_url(url, options = nil) tpl = url.respond_to?(:expand) ? url : Addressable::Template.new(url.to_s) tpl.expand(options || {}).to_s end def allow_undefined_methods? !!@allow_undefined_methods end def inspect %(<#{self.class} #{@endpoint}>) end # private def to_yaml_properties [:@endpoint] end def marshal_dump [@endpoint] end def marshal_load(dumped) @endpoint = *dumped.shift(1) end end end sawyer-0.6.0/lib/sawyer/link_parsers/0000755000004100000410000000000012466121537017642 5ustar www-datawww-datasawyer-0.6.0/lib/sawyer/link_parsers/simple.rb0000644000004100000410000000112212466121537021454 0ustar www-datawww-datamodule Sawyer module LinkParsers class Simple LINK_REGEX = /_?url$/ # Public: Parses simple *_url style links on resources # # data - Hash of resource data # # Returns a Hash of data with separate links Hash def parse(data) links = {} inline_links = data.keys.select {|k| k.to_s[LINK_REGEX] } inline_links.each do |key| rel_name = key.to_s == 'url' ? 'self' : key.to_s.gsub(LINK_REGEX, '') links[rel_name.to_sym] = data[key] end return data, links end end end end sawyer-0.6.0/lib/sawyer/link_parsers/hal.rb0000644000004100000410000000025012466121537020730 0ustar www-datawww-datamodule Sawyer module LinkParsers class Hal def parse(data) links = data.delete(:_links) return data, links end end end end sawyer-0.6.0/lib/sawyer/relation.rb0000644000004100000410000002062412466121537017314 0ustar www-datawww-datamodule Sawyer class Relation class Map # Tracks the available next actions for a resource, and # issues requests for them. def initialize @map = {} end # Adds a Relation to the map. # # rel - A Relation. # # Returns nothing. def <<(rel) @map[rel.name] = rel if rel end # Gets the raw Relation by its name. # # key - The Symbol name of the Relation. # # Returns a Relation. def [](key) @map[key.to_sym] end # Gets the number of mapped Relations. # # Returns an Integer. def size @map.size end # Gets a list of the Relation names. # # Returns an Array of Symbols in no specific order. def keys @map.keys end def to_hash pairs = @map.map do |k, v| [(k.to_s + "_url").to_sym, v.href] end Hash[pairs] end alias :to_h :to_hash def inspect hash = to_hash hash.respond_to?(:pretty_inspect) ? hash.pretty_inspect : hash.inspect end end attr_reader :agent, :name, :href_template, :method, :available_methods # Public: Builds an index of Relations from the value of a `_links` # property in a resource. :get is the default method. Any links with # multiple specified methods will get multiple relations created. # # index - The Hash mapping Relation names to the Hash Relation # options. # rels - A Relation::Map to store the Relations. # # Returns a Relation::Map def self.from_links(agent, index, rels = Map.new) if index.is_a?(Array) raise ArgumentError, "Links must be a hash of rel => {_href => '...'}: #{index.inspect}" end index.each do |name, options| rels << from_link(agent, name, options) end if index rels end # Public: Builds a single Relation from the given options. These are # usually taken from a `_links` property in a resource. # # agent - The Sawyer::Agent that made the request. # name - The Symbol name of the Relation. # options - A Hash containing the other Relation properties. # :href - The String URL of the next action's location. # :method - The optional String HTTP method. # # Returns a Relation. def self.from_link(agent, name, options) case options when Hash new agent, name, options[:href], options[:method] when String new agent, name, options end end # A Relation represents an available next action for a resource. # # agent - The Sawyer::Agent that made the request. # name - The Symbol name of the relation. # href - The String URL of the location of the next action. # method - The Symbol HTTP method. Default: :get def initialize(agent, name, href, method = nil) @agent = agent @name = name.to_sym @href = href @href_template = Addressable::Template.new(href.to_s) methods = nil if method.is_a? String if method.size.zero? method = nil else method.downcase! methods = method.split(',').map! do |m| m.strip! m.to_sym end method = methods.first end end @method = (method || :get).to_sym @available_methods = Set.new methods || [@method] end # Public: Makes an API request with the curent Relation using HEAD. # # data - The Optional Hash or Resource body to be sent. :get or :head # requests can have no body, so this can be the options Hash # instead. # options - Hash of option to configure the API request. # :headers - Hash of API headers to set. # :query - Hash of URL query params to set. # :method - Symbol HTTP method. # # Returns a Sawyer::Response. def head(options = nil) options ||= {} options[:method] = :head call options end # Public: Makes an API request with the curent Relation using GET. # # data - The Optional Hash or Resource body to be sent. :get or :head # requests can have no body, so this can be the options Hash # instead. # options - Hash of option to configure the API request. # :headers - Hash of API headers to set. # :query - Hash of URL query params to set. # :method - Symbol HTTP method. # # Returns a Sawyer::Response. def get(options = nil) options ||= {} options[:method] = :get call options end # Public: Makes an API request with the curent Relation using POST. # # data - The Optional Hash or Resource body to be sent. # options - Hash of option to configure the API request. # :headers - Hash of API headers to set. # :query - Hash of URL query params to set. # :method - Symbol HTTP method. # # Returns a Sawyer::Response. def post(data = nil, options = nil) options ||= {} options[:method] = :post call data, options end # Public: Makes an API request with the curent Relation using PUT. # # data - The Optional Hash or Resource body to be sent. # options - Hash of option to configure the API request. # :headers - Hash of API headers to set. # :query - Hash of URL query params to set. # :method - Symbol HTTP method. # # Returns a Sawyer::Response. def put(data = nil, options = nil) options ||= {} options[:method] = :put call data, options end # Public: Makes an API request with the curent Relation using PATCH. # # data - The Optional Hash or Resource body to be sent. # options - Hash of option to configure the API request. # :headers - Hash of API headers to set. # :query - Hash of URL query params to set. # :method - Symbol HTTP method. # # Returns a Sawyer::Response. def patch(data = nil, options = nil) options ||= {} options[:method] = :patch call data, options end # Public: Makes an API request with the curent Relation using DELETE. # # data - The Optional Hash or Resource body to be sent. # options - Hash of option to configure the API request. # :headers - Hash of API headers to set. # :query - Hash of URL query params to set. # :method - Symbol HTTP method. # # Returns a Sawyer::Response. def delete(data = nil, options = nil) options ||= {} options[:method] = :delete call data, options end # Public: Makes an API request with the curent Relation using OPTIONS. # # data - The Optional Hash or Resource body to be sent. # options - Hash of option to configure the API request. # :headers - Hash of API headers to set. # :query - Hash of URL query params to set. # :method - Symbol HTTP method. # # Returns a Sawyer::Response. def options(data = nil, opt = nil) opt ||= {} opt[:method] = :options call data, opt end def href(options = nil) return @href if @href_template.nil? @href_template.expand(options || {}).to_s end # Public: Makes an API request with the curent Relation. # # data - The Optional Hash or Resource body to be sent. :get or :head # requests can have no body, so this can be the options Hash # instead. # options - Hash of option to configure the API request. # :headers - Hash of API headers to set. # :query - Hash of URL query params to set. # :method - Symbol HTTP method. # # Raises ArgumentError if the :method value is not in @available_methods. # Returns a Sawyer::Response. def call(data = nil, options = nil) m = options && options[:method] if m && !@agent.allow_undefined_methods? && !@available_methods.include?(m == :head ? :get : m) raise ArgumentError, "method #{m.inspect} is not available: #{@available_methods.to_a.inspect}" end @agent.call m || @method, @href_template, data, options end def inspect %(#<#{self.class}: #{@name}: #{@method} #{@href_template}>) end end end sawyer-0.6.0/lib/sawyer/serializer.rb0000644000004100000410000000560312466121537017650 0ustar www-datawww-datarequire 'date' require 'time' module Sawyer class Serializer def self.any_json yajl || multi_json || json end def self.yajl require 'yajl' new(Yajl) rescue LoadError end def self.json require 'json' new(JSON) rescue LoadError end def self.multi_json require 'multi_json' new(MultiJson) rescue LoadError end def self.message_pack require 'msgpack' new(MessagePack, :pack, :unpack) rescue LoadError end # Public: Wraps a serialization format for Sawyer. Nested objects are # prepared for serialization (such as changing Times to ISO 8601 Strings). # Any serialization format that responds to #dump and #load will work. def initialize(format, dump_method_name = nil, load_method_name = nil) @format = format @dump = @format.method(dump_method_name || :dump) @load = @format.method(load_method_name || :load) end # Public: Encodes an Object (usually a Hash or Array of Hashes). # # data - Object to be encoded. # # Returns an encoded String. def encode(data) @dump.call(encode_object(data)) end alias dump encode # Public: Decodes a String into an Object (usually a Hash or Array of # Hashes). # # data - An encoded String. # # Returns a decoded Object. def decode(data) return nil if data.nil? || data.strip.empty? decode_object(@load.call(data)) end alias load decode def encode_object(data) case data when Hash then encode_hash(data) when Array then data.map { |o| encode_object(o) } else data end end def encode_hash(hash) hash.keys.each do |key| case value = hash[key] when Date then hash[key] = value.to_time.utc.xmlschema when Time then hash[key] = value.utc.xmlschema when Hash then hash[key] = encode_hash(value) end end hash end def decode_object(data) case data when Hash then decode_hash(data) when Array then data.map { |o| decode_object(o) } else data end end def decode_hash(hash) hash.keys.each do |key| hash[key.to_sym] = decode_hash_value(key, hash.delete(key)) end hash end def decode_hash_value(key, value) if time_field?(key, value) if value.is_a?(String) begin Time.parse(value) rescue ArgumentError value end elsif value.is_a?(Integer) || value.is_a?(Float) Time.at(value) else value end elsif value.is_a?(Hash) decode_hash(value) elsif value.is_a?(Array) value.map { |o| decode_hash_value(key, o) } else value end end def time_field?(key, value) value && (key =~ /_(at|on)\z/ || key =~ /(\A|_)date\z/) end end end sawyer-0.6.0/lib/sawyer/resource.rb0000644000004100000410000000775012466121537017333 0ustar www-datawww-datamodule Sawyer class Resource SPECIAL_METHODS = Set.new(%w(agent rels fields)) attr_reader :_agent, :_rels, :_fields attr_reader :attrs alias to_hash attrs alias to_h attrs include Enumerable # Initializes a Resource with the given data. # # agent - The Sawyer::Agent that made the API request. # data - Hash of key/value properties. def initialize(agent, data = {}) @_agent = agent data, links = agent.parse_links(data) @_rels = Relation.from_links(agent, links) @_fields = Set.new @_metaclass = (class << self; self; end) @attrs = {} data.each do |key, value| @_fields << key @attrs[key.to_sym] = process_value(value) end @_metaclass.send(:attr_accessor, *data.keys) end # Processes an individual value of this resource. Hashes get exploded # into another Resource, and Arrays get their values processed too. # # value - An Object value of a Resource's data. # # Returns an Object to set as the value of a Resource key. def process_value(value) case value when Hash then self.class.new(@_agent, value) when Array then value.map { |v| process_value(v) } else value end end # Checks to see if the given key is in this resource. # # key - A Symbol key. # # Returns true if the key exists, or false. def key?(key) @_fields.include? key end # Allow fields to be retrieved via Hash notation # # method - key name # # Returns the value from attrs if exists def [](method) send(method.to_sym) rescue NoMethodError nil end # Allow fields to be set via Hash notation # # method - key name # value - value to set for the attr key # # Returns - value def []=(method, value) send("#{method}=", value) rescue NoMethodError nil end ATTR_SETTER = '='.freeze ATTR_PREDICATE = '?'.freeze # Provides access to a resource's attributes. def method_missing(method, *args) attr_name, suffix = method.to_s.scan(/([a-z0-9\_]+)(\?|\=)?$/i).first if suffix == ATTR_SETTER @_metaclass.send(:attr_accessor, attr_name) @_fields << attr_name.to_sym send(method, args.first) elsif attr_name && @_fields.include?(attr_name.to_sym) value = @attrs[attr_name.to_sym] case suffix when nil @_metaclass.send(:attr_accessor, attr_name) value when ATTR_PREDICATE then !!value end elsif suffix.nil? && SPECIAL_METHODS.include?(attr_name) instance_variable_get "@_#{attr_name}" elsif attr_name && !@_fields.include?(attr_name.to_sym) nil else super end end # Wire up accessor methods to pull from attrs def self.attr_accessor(*attrs) attrs.each do |attribute| class_eval do define_method attribute do @attrs[attribute.to_sym] end define_method "#{attribute}=" do |value| @attrs[attribute.to_sym] = value end define_method "#{attribute}?" do !!@attrs[attribute.to_sym] end end end end def inspect to_attrs.respond_to?(:pretty_inspect) ? to_attrs.pretty_inspect : to_attrs.inspect end def each(&block) @attrs.each(&block) end # private def to_yaml_properties [:@attrs, :@_fields, :@_rels] end def to_attrs hash = self.attrs.clone hash.keys.each do |k| if hash[k].is_a?(Sawyer::Resource) hash[k] = hash[k].to_attrs elsif hash[k].is_a?(Array) && hash[k].all?{|el| el.is_a?(Sawyer::Resource)} hash[k] = hash[k].collect{|el| el.to_attrs} end end hash end def marshal_dump [@attrs, @_fields, @_rels] end def marshal_load(dumped) @attrs, @_fields, @_rels = *dumped.shift(3) @_metaclass = (class << self; self; end) end end end sawyer-0.6.0/metadata.yml0000644000004100000410000000430612466121537015374 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: sawyer version: !ruby/object:Gem::Version version: 0.6.0 platform: ruby authors: - Rick Olson - Wynn Netherland autorequire: bindir: bin cert_chain: [] date: 2014-12-02 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: faraday requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.8' - - "<" - !ruby/object:Gem::Version version: '0.10' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.8' - - "<" - !ruby/object:Gem::Version version: '0.10' - !ruby/object:Gem::Dependency name: addressable requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 2.3.5 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: 2.3.5 description: email: technoweenie@gmail.com executables: [] extensions: [] extra_rdoc_files: [] files: - Gemfile - LICENSE.md - README.md - Rakefile - lib/sawyer.rb - lib/sawyer/agent.rb - lib/sawyer/link_parsers/hal.rb - lib/sawyer/link_parsers/simple.rb - lib/sawyer/relation.rb - lib/sawyer/resource.rb - lib/sawyer/response.rb - lib/sawyer/serializer.rb - sawyer.gemspec - script/bootstrap - script/console - script/package - script/release - script/test - test/agent_test.rb - test/helper.rb - test/relation_test.rb - test/resource_test.rb - test/response_test.rb homepage: https://github.com/lostisland/sawyer licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.3.5 requirements: [] rubyforge_project: rubygems_version: 2.2.2 signing_key: specification_version: 2 summary: Secret User Agent of HTTP test_files: [] has_rdoc: sawyer-0.6.0/test/0000755000004100000410000000000012466121537014045 5ustar www-datawww-datasawyer-0.6.0/test/helper.rb0000644000004100000410000000022712466121537015652 0ustar www-datawww-datarequire 'test/unit' require File.expand_path('../../lib/sawyer', __FILE__) class Sawyer::TestCase < Test::Unit::TestCase def default_test end end sawyer-0.6.0/test/resource_test.rb0000644000004100000410000001235612466121537017267 0ustar www-datawww-datarequire File.expand_path("../helper", __FILE__) module Sawyer class ResourceTest < TestCase def setup @stubs = Faraday::Adapter::Test::Stubs.new @agent = Sawyer::Agent.new "http://foo.com/a/" do |conn| conn.builder.handlers.delete(Faraday::Adapter::NetHttp) conn.adapter :test, @stubs end end def test_accessible_keys res = Resource.new @agent, :a => 1, :_links => {:self => {:href => '/'}} assert_equal 1, res.a assert res.rels[:self] assert_equal @agent, res.agent assert_equal 1, res.fields.size assert res.fields.include?(:a) end def test_clashing_keys res = Resource.new @agent, :agent => 1, :rels => 2, :fields => 3, :_links => {:self => {:href => '/'}} assert_equal 1, res.agent assert_equal 2, res.rels assert_equal 3, res.fields assert res._rels[:self] assert_equal @agent, res._agent assert_equal 3, res._fields.size [:agent, :rels, :fields].each do |f| assert res._fields.include?(f) end end def test_nested_object res = Resource.new @agent, :user => {:id => 1, :_links => {:self => {:href => '/users/1'}}}, :_links => {:self => {:href => '/'}} assert_equal '/', res.rels[:self].href assert_kind_of Resource, res.user assert_equal 1, res.user.id assert_equal '/users/1', res.user.rels[:self].href end def test_nested_collection res = Resource.new @agent, :users => [{:id => 1, :_links => {:self => {:href => '/users/1'}}}], :_links => {:self => {:href => '/'}} assert_equal '/', res.rels[:self].href assert_kind_of Array, res.users assert user = res.users.first assert_kind_of Resource, user assert_equal 1, user.id assert_equal '/users/1', user.rels[:self].href end def test_attribute_predicates res = Resource.new @agent, :a => 1, :b => true, :c => nil, :d => false assert res.a? assert res.b? assert !res.c? assert !res.d? end def test_attribute_setter res = Resource.new @agent, :a => 1 assert_equal 1, res.a assert !res.key?(:b) res.b = 2 assert_equal 2, res.b assert res.key?(:b) end def test_dynamic_attribute_methods_from_getter res = Resource.new @agent, :a => 1 assert res.key?(:a) assert res.respond_to?(:a) assert res.respond_to?(:a=) assert_equal 1, res.a assert res.respond_to?(:a) assert res.respond_to?(:a=) end def test_nillable_attribute_getters res = Resource.new @agent, :a => 1 assert !res.key?(:b) assert !res.respond_to?(:b) assert !res.respond_to?(:b=) assert_nil res.b assert_nothing_raised do res.b end end def test_dynamic_attribute_methods_from_setter res = Resource.new @agent, :a => 1 assert !res.key?(:b) assert !res.respond_to?(:b) assert !res.respond_to?(:b=) res.b = 1 assert res.key?(:b) assert res.respond_to?(:b) assert res.respond_to?(:b=) end def test_attrs res = Resource.new @agent, :a => 1 hash = {:a => 1 } assert_equal hash, res.attrs end def test_to_h res = Resource.new @agent, :a => 1 hash = {:a => 1 } assert_equal hash, res.to_h end def test_to_attrs_for_sawyer_resource_arrays res = Resource.new @agent, :a => 1, :b => [Resource.new(@agent, :a => 2)] hash = {:a => 1, :b => [{:a => 2}]} assert_equal hash, res.to_attrs end def test_handle_hash_notation_with_string_key res = Resource.new @agent, :a => 1 assert_equal 1, res['a'] res[:b] = 2 assert_equal 2, res.b end def test_simple_rel_parsing @agent.links_parser = Sawyer::LinkParsers::Simple.new res = Resource.new @agent, :url => '/', :user => { :id => 1, :url => '/users/1', :followers_url => '/users/1/followers' } assert_equal '/', res.rels[:self].href assert_kind_of Resource, res.user assert_equal '/', res.url assert_equal 1, res.user.id assert_equal '/users/1', res.user.rels[:self].href assert_equal '/users/1', res.user.url assert_equal '/users/1/followers', res.user.rels[:followers].href assert_equal '/users/1/followers', res.user.followers_url end def test_handle_yaml_dump assert_nothing_raised do require 'yaml' res = Resource.new @agent, :a => 1 YAML.dump(res) end end def test_handle_marshal_dump assert_nothing_raised do dump = Marshal.dump(Resource.new(@agent, :a => 1)) resource = Marshal.load(dump) assert_equal 1, resource.a end end def test_inspect resource = Resource.new @agent, :a => 1 assert_equal "{:a=>1}\n", resource.inspect end def test_each resource = Resource.new @agent, { :a => 1, :b => 2 } output = [] resource.each { |k,v| output << [k,v] } assert_equal [[:a, 1], [:b, 2]], output end def test_enumerable resource = Resource.new @agent, { :a => 1, :b => 2 } enum = resource.map assert_equal Enumerator, enum.class end end end sawyer-0.6.0/test/response_test.rb0000644000004100000410000000420412466121537017267 0ustar www-datawww-datarequire File.expand_path("../helper", __FILE__) module Sawyer class ResponseTest < TestCase def setup @now = Time.now @stubs = Faraday::Adapter::Test::Stubs.new @agent = Sawyer::Agent.new "http://foo.com" do |conn| conn.builder.handlers.delete(Faraday::Adapter::NetHttp) conn.adapter :test, @stubs do |stub| stub.get '/' do [200, { 'Content-Type' => 'application/json', 'Link' => '; rel="next", ; rel="last"' }, Sawyer::Agent.encode( :a => 1, :_links => { :self => {:href => '/a', :method => 'POST'} } )] end stub.get '/emails' do emails = %w(rick@example.com technoweenie@example.com) [200, {'Content-Type' => 'application/json'}, Sawyer::Agent.encode(emails)] end end end @res = @agent.start assert_kind_of Sawyer::Response, @res end def test_gets_status assert_equal 200, @res.status end def test_gets_headers assert_equal 'application/json', @res.headers['content-type'] end def test_gets_body assert_equal 1, @res.data.a assert_equal [:a], @res.data.fields.to_a end def test_gets_rels assert_equal '/starred?page=2', @res.rels[:next].href assert_equal :get, @res.rels[:next].method assert_equal '/starred?page=19', @res.rels[:last].href assert_equal :get, @res.rels[:next].method assert_equal '/a', @res.data.rels[:self].href assert_equal :post, @res.data.rels[:self].method end def test_gets_response_timing assert @res.timing > 0 assert @res.time >= @now end def test_makes_request_from_relation @stubs.post '/a' do [201, {'Content-Type' => 'application/json'}, ""] end res = @res.data.rels[:self].call assert_equal 201, res.status assert_nil res.data end def test_handles_arrays_of_strings res = @agent.call(:get, '/emails') assert_equal 'rick@example.com', res.data.first end end end sawyer-0.6.0/test/relation_test.rb0000644000004100000410000001226512466121537017254 0ustar www-datawww-datarequire File.expand_path("../helper", __FILE__) module Sawyer class RelationTest < TestCase def test_builds_relation_from_hash hash = {:href => '/users/1', :method => 'post'} rel = Sawyer::Relation.from_link(nil, :self, hash) assert_equal :self, rel.name assert_equal '/users/1', rel.href assert_equal :post, rel.method assert_equal [:post], rel.available_methods.to_a end def test_builds_multiple_rels_from_multiple_methods index = { 'comments' => {:href => '/comments', :method => 'get,post'} } rels = Sawyer::Relation.from_links(nil, index) assert_equal 1, rels.size assert_equal [:comments], rels.keys assert rel = rels[:comments] assert_equal '/comments', rel.href assert_equal :get, rel.method assert_equal [:get, :post], rel.available_methods.to_a assert_kind_of Addressable::Template, rel.href_template end def test_builds_rels_from_hash index = { 'self' => '/users/1' } rels = Sawyer::Relation.from_links(nil, index) assert_equal 1, rels.size assert_equal [:self], rels.keys assert rel = rels[:self] assert_equal :self, rel.name assert_equal '/users/1', rel.href assert_equal :get, rel.method assert_equal [:get], rel.available_methods.to_a assert_kind_of Addressable::Template, rel.href_template end def test_builds_rels_from_hash_index index = { 'self' => {:href => '/users/1'} } rels = Sawyer::Relation.from_links(nil, index) assert_equal 1, rels.size assert_equal [:self], rels.keys assert rel = rels[:self] assert_equal :self, rel.name assert_equal '/users/1', rel.href assert_equal :get, rel.method assert_equal [:get], rel.available_methods.to_a assert_kind_of Addressable::Template, rel.href_template end def test_builds_rels_from_nil rels = Sawyer::Relation.from_links nil, nil assert_equal 0, rels.size assert_equal [], rels.keys end def test_relation_api_calls agent = Sawyer::Agent.new "http://foo.com/a/" do |conn| conn.builder.handlers.delete(Faraday::Adapter::NetHttp) conn.adapter :test do |stubs| stubs.get '/a/1' do [200, {}, '{}'] end stubs.delete '/a/1' do [204, {}, '{}'] end end end rel = Sawyer::Relation.new agent, :self, "/a/1", "get,put,delete" assert_equal :get, rel.method [:get, :put, :delete].each do |m| assert rel.available_methods.include?(m), "#{m.inspect} is not available: #{rel.available_methods.inspect}" end assert_equal 200, rel.call.status assert_equal 200, rel.call(:method => :head).status assert_equal 204, rel.call(nil, :method => :delete).status assert_raises ArgumentError do rel.call nil, :method => :post end assert_equal 200, rel.head.status assert_equal 200, rel.get.status assert_equal 204, rel.delete.status assert_raises ArgumentError do rel.post end end def test_relation_api_calls_with_uri_tempate agent = Sawyer::Agent.new "http://foo.com/a" do |conn| conn.builder.handlers.delete(Faraday::Adapter::NetHttp) conn.adapter :test do |stubs| stubs.get '/octocat/hello' do |env| assert_equal "a=1&b=2", env[:url].query [200, {}, '{}'] end stubs.get '/a' do [404, {}, '{}'] end end end rel = Sawyer::Relation.new agent, :repo, "{/user,repo}{?a,b}" assert_equal '', rel.href assert_equal '/octocat', rel.href(:user => :octocat) assert_equal 404, rel.get.status assert_equal 200, rel.get(:uri => {'user' => 'octocat', 'repo' => 'hello', 'a' => 1, 'b' => 2}).status end def test_handles_invalid_uri hash = {:href => '/this has spaces', :method => 'post'} rel = Sawyer::Relation.from_link(nil, :self, hash) assert_equal :self, rel.name assert_equal '/this has spaces', rel.href end def test_allows_all_methods_when_not_in_strict_mode agent = Sawyer::Agent.new "http://foo.com/a/", :allow_undefined_methods => true do |conn| conn.builder.handlers.delete(Faraday::Adapter::NetHttp) conn.adapter :test do |stubs| stubs.get '/a/1' do [200, {}, '{}'] end stubs.delete '/a/1' do [204, {}, '{}'] end stubs.post '/a/1' do [200, {}, '{}'] end stubs.put '/a/1' do [204, {}, '{}'] end end end rel = Sawyer::Relation.new agent, :self, "/a/1" assert_equal 200, rel.get.status assert_equal 200, rel.post.status assert_equal 204, rel.put.status assert_equal 204, rel.delete.status end def test_map_inspect map = Sawyer::Relation::Map.new hash = {:href => '/users/1', :method => 'post'} rel = Sawyer::Relation.from_link(nil, :self, hash) map << rel assert_equal "{:self_url=>\"/users/1\"}\n", map.inspect end end end sawyer-0.6.0/test/agent_test.rb0000644000004100000410000001345412466121537016536 0ustar www-datawww-datarequire File.expand_path("../helper", __FILE__) require 'faraday/adapter/test' module Sawyer class AgentTest < TestCase class InlineRelsParser def parse(data) links = {} data.keys.select {|k| k[/_url$/] }.each {|k| links[k.to_s.gsub(/_url$/, '')] = data.delete(k) } return data, links end end def setup @stubs = Faraday::Adapter::Test::Stubs.new @agent = Sawyer::Agent.new "http://foo.com/a/" do |conn| conn.builder.handlers.delete(Faraday::Adapter::NetHttp) conn.adapter :test, @stubs end end def test_accesses_root_relations @stubs.get '/a/' do |env| assert_equal 'foo.com', env[:url].host [200, {'Content-Type' => 'application/json'}, Sawyer::Agent.encode( :_links => { :users => {:href => '/users'}})] end assert_equal 200, @agent.root.status assert_equal '/users', @agent.rels[:users].href assert_equal :get, @agent.rels[:users].method end def test_allows_custom_rel_parsing @stubs.get '/a/' do |env| assert_equal 'foo.com', env[:url].host [200, {'Content-Type' => 'application/json'}, Sawyer::Agent.encode( :url => '/', :users_url => '/users', :repos_url => '/repos')] end agent = Sawyer::Agent.new "http://foo.com/a/" do |conn| conn.builder.handlers.delete(Faraday::Adapter::NetHttp) conn.adapter :test, @stubs end agent.links_parser = InlineRelsParser.new assert_equal 200, agent.root.status assert_equal '/users', agent.rels[:users].href assert_equal :get, agent.rels[:users].method assert_equal '/repos', agent.rels[:repos].href assert_equal :get, agent.rels[:repos].method end def test_saves_root_endpoint @stubs.get '/a/' do |env| [200, {}, '{}'] end assert_kind_of Sawyer::Response, @agent.root assert_not_equal @agent.root.time, @agent.start.time end def test_starts_a_session @stubs.get '/a/' do |env| assert_equal 'foo.com', env[:url].host [200, {'Content-Type' => 'application/json'}, Sawyer::Agent.encode( :_links => { :users => {:href => '/users'}})] end res = @agent.start assert_equal 200, res.status assert_kind_of Sawyer::Resource, resource = res.data assert_equal '/users', resource.rels[:users].href assert_equal :get, resource.rels[:users].method end def test_requests_with_body_and_options @stubs.post '/a/b/c' do |env| assert_equal '{"a":1}', env[:body] assert_equal 'abc', env[:request_headers]['x-test'] assert_equal 'foo=bar', env[:url].query [200, {}, "{}"] end res = @agent.call :post, 'b/c' , {:a => 1}, :headers => {"X-Test" => "abc"}, :query => {:foo => 'bar'} assert_equal 200, res.status end def test_requests_with_body_and_options_to_get @stubs.get '/a/b/c' do |env| assert_nil env[:body] assert_equal 'abc', env[:request_headers]['x-test'] assert_equal 'foo=bar', env[:url].query [200, {}, "{}"] end res = @agent.call :get, 'b/c' , {:a => 1}, :headers => {"X-Test" => "abc"}, :query => {:foo => 'bar'} assert_equal 200, res.status end def test_encodes_and_decodes_times time = Time.at(Time.now.to_i) data = { :a => 1, :b => true, :c => 'c', :created_at => time, :published_at => nil, :updated_at => "An invalid date", :pub_date => time, :subscribed_at => time.to_i, :lost_at => time.to_f, :first_date => false, :validate => true } data = [data.merge(:foo => [data])] encoded = Sawyer::Agent.encode(data) decoded = Sawyer::Agent.decode(encoded) 2.times do assert_equal 1, decoded.size decoded = decoded.shift assert_equal 1, decoded[:a] assert_equal true, decoded[:b] assert_equal 'c', decoded[:c] assert_equal time, decoded[:created_at], "Did not parse created_at as Time" assert_nil decoded[:published_at] assert_equal "An invalid date", decoded[:updated_at] assert_equal time, decoded[:pub_date], "Did not parse pub_date as Time" assert_equal true, decoded[:validate] assert_equal time, decoded[:subscribed_at], "Did not parse subscribed_at as Time" assert_equal time, decoded[:lost_at], "Did not parse lost_at as Time" assert_equal false, decoded[:first_date], "Parsed first_date" decoded = decoded[:foo] end end def test_does_not_encode_non_json_content_types @stubs.get '/a/' do |env| assert_equal 'foo.com', env[:url].host [200, {'Content-Type' => 'text/plain'}, "This is plain text"] end res = @agent.call :get, '/a/', :headers => {"Accept" => "text/plain"} assert_equal 200, res.status assert_equal "This is plain text", res.data end def test_handle_yaml_dump_and_load assert_nothing_raised do require 'yaml' res = Agent.new 'http://example.com', :a => 1 YAML.load(YAML.dump(res)) end end def test_handle_marshal_dump_and_load assert_nothing_raised do res = Agent.new 'http://example.com', :a => 1 Marshal.load(Marshal.dump(res)) end end def test_blank_response_doesnt_raise @stubs.get "/a/" do |env| assert_equal "foo.com", env[:url].host [200, { "Content-Type" => "application/json" }, " "] end agent = Sawyer::Agent.new "http://foo.com/a/" do |conn| conn.adapter :test, @stubs end assert_nothing_raised do assert_equal 200, agent.root.status end end end end sawyer-0.6.0/sawyer.gemspec0000644000004100000410000000215012466121537015743 0ustar www-datawww-datalib = "sawyer" lib_file = File.expand_path("../lib/#{lib}.rb", __FILE__) File.read(lib_file) =~ /\bVERSION\s*=\s*["'](.+?)["']/ version = $1 Gem::Specification.new do |spec| spec.specification_version = 2 if spec.respond_to? :specification_version= spec.required_rubygems_version = Gem::Requirement.new(">= 1.3.5") if spec.respond_to? :required_rubygems_version= spec.name = lib spec.version = version spec.summary = "Secret User Agent of HTTP" spec.authors = ["Rick Olson", "Wynn Netherland"] spec.email = 'technoweenie@gmail.com' spec.homepage = 'https://github.com/lostisland/sawyer' spec.licenses = ['MIT'] spec.add_dependency 'faraday', ['~> 0.8', '< 0.10'] spec.add_dependency 'addressable', ['~> 2.3.5'] spec.files = %w(Gemfile LICENSE.md README.md Rakefile) spec.files << "#{lib}.gemspec" spec.files += Dir.glob("lib/**/*.rb") spec.files += Dir.glob("test/**/*.rb") spec.files += Dir.glob("script/*") dev_null = File.exist?('/dev/null') ? '/dev/null' : 'NUL' git_files = `git ls-files -z 2>#{dev_null}` spec.files &= git_files.split("\0") if $?.success? end sawyer-0.6.0/README.md0000644000004100000410000000111612466121537014344 0ustar www-datawww-data# Sawyer Sawyer is an experimental secret user agent built on top of [Faraday][faraday]. [faraday]: https://github.com/lostisland/faraday ![](http://techno-weenie.net/sawyer/images/faraday.jpeg) Think of Faraday as the nerdy scientist behind an HTTP API. Sure, he knows the technical details of how to communicate with an application. But he also gets overly obsessive about alternate timelines to be of much use. ![](http://techno-weenie.net/sawyer/images/sawyer.jpeg) Sawyer knows what needs to be done. He gets in there, assesses the situation, and figures out the next action.