Permalink
Browse files

Expand links statically using variables captured by matchers.

  • Loading branch information...
Avdi Grimm
Avdi Grimm committed Jul 21, 2012
1 parent 28a11dd commit 043ded2f6fb0369d1e1a7c592d8eb59ddbdfbac5
Showing with 960 additions and 3,644 deletions.
  1. +11 −5 lib/leadlight/errors.rb
  2. +9 −1 lib/leadlight/hyperlinkable.rb
  3. +17 −2 lib/leadlight/link.rb
  4. +4 −0 lib/leadlight/link_template.rb
  5. +9 −2 lib/leadlight/representation.rb
  6. +7 −2 lib/leadlight/request.rb
  7. +1 −3 lib/leadlight/service.rb
  8. +32 −2 lib/leadlight/tint_helper.rb
  9. +23 −22 spec/cassettes/Leadlight/authorized_GitHub_example/_user/has_the_expected_content.yml
  10. +20 −21 spec/cassettes/Leadlight/authorized_GitHub_example/_user/indicates_the_expected_oath_scopes.yml
  11. +106 −125 spec/cassettes/Leadlight/authorized_GitHub_example/adding_and_removing_team_members.yml
  12. +43 −43 spec/cassettes/Leadlight/authorized_GitHub_example/adding_and_removing_teams.yml
  13. +179 −0 spec/cassettes/Leadlight/authorized_GitHub_example/team_list/should_have_a_link_back_to_the_org.yml
  14. +53 −42 spec/cassettes/Leadlight/authorized_GitHub_example/test_team/.yml
  15. +4 −36 spec/cassettes/Leadlight/basic_GitHub_example/_root/.yml
  16. +4 −36 spec/cassettes/Leadlight/basic_GitHub_example/_root/__location__/.yml
  17. +2 −34 spec/cassettes/Leadlight/basic_GitHub_example/_root/should_be_a_204_no_content.yml
  18. +2 −34 spec/cassettes/Leadlight/tinted_GitHub_example/_root/.yml
  19. +4 −36 spec/cassettes/Leadlight/tinted_GitHub_example/_root/__location__/.yml
  20. +4 −36 spec/cassettes/Leadlight/tinted_GitHub_example/_root/should_be_a_204_no_content.yml
  21. +18 −50 spec/cassettes/Leadlight/tinted_GitHub_example/_user/has_the_expected_content.yml
  22. +10 −42 spec/cassettes/Leadlight/tinted_GitHub_example/bad_links/enables_custom_error_matching.yml
  23. +9 −41 spec/cassettes/Leadlight/tinted_GitHub_example/bad_links/should_raise_ResourceNotFound.yml
  24. +29 −61 spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/.yml
  25. +40 −73 spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/should_be_able_to_follow_next_link.yml
  26. +138 −170 spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/should_be_enumerable.yml
  27. +50 −82 ...ttes/Leadlight/tinted_GitHub_example/user_followers/should_be_enumerable_over_page_boundaries.yml
  28. +26 −59 spec/cassettes/Leadlight/tinted_GitHub_example/user_followers/should_have_next_and_last_links.yml
  29. +2 −34 spec/cassettes/Leadlight/tinted_GitHub_example/user_link/exists.yml
  30. +4 −36 spec/cassettes/Leadlight/tinted_GitHub_example/user_link/links_to_the_expected_URL.yml
  31. +0 −85 spec/cassettes/cassettes.old/Leadlight/authorized_GitHub_example/_user/has_the_expected_content.yml
  32. +0 −84 ...es/cassettes.old/Leadlight/authorized_GitHub_example/_user/indicates_the_expected_oath_scopes.yml
  33. +0 −401 .../cassettes/cassettes.old/Leadlight/authorized_GitHub_example/adding_and_removing_team_members.yml
  34. +0 −165 spec/cassettes/cassettes.old/Leadlight/authorized_GitHub_example/adding_and_removing_teams.yml
  35. +0 −169 spec/cassettes/cassettes.old/Leadlight/authorized_GitHub_example/test_team/.yml
  36. +0 −33 spec/cassettes/cassettes.old/Leadlight/basic_GitHub_example/_root/.yml
  37. +0 −33 spec/cassettes/cassettes.old/Leadlight/basic_GitHub_example/_root/__location__/.yml
  38. +0 −33 spec/cassettes/cassettes.old/Leadlight/basic_GitHub_example/_root/should_be_a_204_no_content.yml
  39. +0 −33 spec/cassettes/cassettes.old/Leadlight/tinted_GitHub_example/_root/.yml
  40. +0 −33 spec/cassettes/cassettes.old/Leadlight/tinted_GitHub_example/_root/__location__/.yml
  41. +0 −33 spec/cassettes/cassettes.old/Leadlight/tinted_GitHub_example/_root/should_be_a_204_no_content.yml
  42. +0 −75 spec/cassettes/cassettes.old/Leadlight/tinted_GitHub_example/_user/has_the_expected_content.yml
  43. +0 −67 ...ssettes/cassettes.old/Leadlight/tinted_GitHub_example/bad_links/enables_custom_error_matching.yml
  44. +0 −67 ...ssettes/cassettes.old/Leadlight/tinted_GitHub_example/bad_links/should_raise_ResourceNotFound.yml
  45. +0 −118 spec/cassettes/cassettes.old/Leadlight/tinted_GitHub_example/user_followers/.yml
  46. +0 −162 ...ssettes.old/Leadlight/tinted_GitHub_example/user_followers/should_be_able_to_follow_next_link.yml
  47. +0 −512 spec/cassettes/cassettes.old/Leadlight/tinted_GitHub_example/user_followers/should_be_enumerable.yml
  48. +0 −206 ....old/Leadlight/tinted_GitHub_example/user_followers/should_be_enumerable_over_page_boundaries.yml
  49. +0 −117 .../cassettes.old/Leadlight/tinted_GitHub_example/user_followers/should_have_next_and_last_links.yml
  50. +0 −33 spec/cassettes/cassettes.old/Leadlight/tinted_GitHub_example/user_link/exists.yml
  51. +0 −33 spec/cassettes/cassettes.old/Leadlight/tinted_GitHub_example/user_link/links_to_the_expected_URL.yml
  52. +15 −2 spec/leadlight/hyperlinkable_spec.rb
  53. +23 −12 spec/leadlight/link_spec.rb
  54. +15 −5 spec/leadlight/representation_spec.rb
  55. +36 −2 spec/leadlight/tint_helper_spec.rb
  56. +11 −1 spec/leadlight_spec.rb
View
@@ -6,13 +6,19 @@ class CredentialsRequiredError < Error; end
class HttpError < Error
extend Forwardable
- attr_reader :response
+ attr_reader :request
- def_delegator :response, :status
+ def_delegators :response, :status, :response
- def initialize(response, message=response.status.to_s)
- @response = response
- super(message)
+ def initialize(request, message=response.status.to_s)
+ @request = request
+ super(amplify_message(message))
+ end
+
+ private
+
+ def amplify_message(message)
+ "#{message} (#{request.http_method.upcase} #{request.location})"
end
end
class ClientError < HttpError; end
@@ -29,7 +29,9 @@ def link(key, *expand_args, &fallback)
end
def add_link(url, rel=nil, title=rel, options={})
- link = Link.new(__service__, url, rel, title, options)
+ template = LinkTemplate.new(__service__, url, rel, title, options)
+ link = template.expand(captures_for_variables(__captures__,
+ template.variables))
define_link_helper(rel) if rel
links << link
end
@@ -123,5 +125,11 @@ def define_link_set_helper(rel, name)
end
end
end
+
+ def captures_for_variables(captures, variables)
+ variables.each_with_object({}) do |key, h|
+ h[key] = captures[key] if captures[key]
+ end
+ end
end
end
View
@@ -1,3 +1,5 @@
+require 'addressable/uri'
+require 'addressable/template'
require 'leadlight/param_hash'
module Leadlight
@@ -34,11 +36,24 @@ def initialize(service, href, rel=nil, title=rel, options={})
self.expansion_params = options.fetch(:expansion_params) { {} }
end
- HTTP_METHODS.each do |name|
+ HTTP_METHODS_WITHOUT_BODY.each do |name|
define_method(name) do |*args, &block|
request_options = args.last.is_a?(Hash) ? args.pop : {}
request_options[:link] = self
- service.public_send(name, href, *args, request_options, &block)
+ service.public_send(name, href, nil, *args, request_options, &block)
+ end
+ end
+
+ HTTP_METHODS_WITH_BODY.each do |name|
+ define_method(name) do |*args, &block|
+ request_options = if args.size > 1 && args.last.is_a?(Hash)
+ args.pop
+ else
+ {}
+ end
+ body = args.shift
+ request_options[:link] = self
+ service.public_send(name, href, body, *args, request_options, &block)
end
end
@@ -1,8 +1,12 @@
+require 'forwardable'
require 'leadlight/link'
require 'addressable/template'
module Leadlight
class LinkTemplate < Link
+ extend Forwardable
+
+ def_delegators :href_template, :variables
def href_template
@href_template ||= Addressable::Template.new(href.to_s)
@@ -1,5 +1,6 @@
require 'forwardable'
require 'addressable/uri'
+require 'fattr'
require 'leadlight/link'
require 'leadlight/errors'
@@ -12,6 +13,8 @@ module Representation
attr_accessor :__response__
attr_accessor :__request__
+ #fattr(:__captures__) { {} }
+
def initialize_representation(service, location, response, request)
self.__service__ = service
self.__location__ = location
@@ -32,7 +35,7 @@ def exception(message=exception_message)
when 404 then ResourceNotFound
when (400..499) then ClientError
when (500..599) then ServerError
- end.new(__response__, exception_message)
+ end.new(__request__, exception_message)
end
def exception_message
@@ -49,10 +52,14 @@ def __link__
__request__.link
end
- def __params__
+ def __request_params__
__request__.params
end
+ def __captures__
+ @__captures__ ||= {}
+ end
+
private
def __apply_tint__
View
@@ -116,10 +116,9 @@ def represent(env)
content_type = env[:response_headers]['Content-Type']
content_type = clean_content_type(content_type)
representation = type_map.to_native(content_type, env[:body])
- location = Addressable::URI.parse(env[:response_headers].fetch('location'){ env[:url] })
representation.
extend(Representation).
- initialize_representation(env[:leadlight_service], location, env[:response], self).
+ initialize_representation(env[:leadlight_service], location(env), env[:response], self).
extend(Hyperlinkable).
apply_all_tints
end
@@ -128,6 +127,12 @@ def params
link_params.merge(request_params)
end
+ def location(env=@env)
+ env ||= {}
+ url = env.fetch(:response_headers){{}}.fetch('location'){ env.fetch(:url){ self.url } }
+ Addressable::URI.parse(url)
+ end
+
private
fattr(:faraday_request) {
View
@@ -55,9 +55,7 @@ def get_representation!(*args, &block)
private
- def perform_request(url, http_method, body_or_options=nil, options=nil, &representation_handler)
- options ||= body_or_options.is_a?(Hash) ? body_or_options : {}
- body ||= body_or_options.is_a?(Hash) ? nil : body_or_options
+ def perform_request(url, http_method, body=nil, options={}, &representation_handler)
req = request_class.new(self, connection, url, http_method, body, options)
if representation_handler
req.submit_and_wait(&representation_handler)
@@ -22,14 +22,20 @@ def match(*matchers, &block_matcher)
end
def match_path(pattern)
- match{ pattern === __location__.path }
+ matcher = path_matcher(pattern)
+ match{ matcher.call(pattern, __location__.path, __captures__) }
end
def match_template(path_template)
path_url = Addressable::URI.parse(path_template)
full_url = __location__ + path_url
template = Addressable::Template.new(full_url.to_s)
- match { template.match(__location__) }
+ match {
+ match_data = template.match(__location__)
+ if match_data
+ __captures__.merge!(match_data.mapping)
+ end
+ }
end
def match_content_type(pattern)
@@ -86,5 +92,29 @@ def expand_status_patterns(*patterns)
end
}
end
+
+ def path_matcher(object)
+ case object
+ when Regexp then method(:match_path_with_regexp)
+ else method(:match_path_generic)
+ end
+ end
+
+ def match_path_with_regexp(pattern, path, captures)
+ capture_names = pattern.names
+ match_data = pattern.match(path)
+ if match_data
+ capture_names.each do |name|
+ value = match_data[name]
+ captures[name] = value if value
+ end
+ end
+ end
+
+ def match_path_generic(pattern, path, captures)
+ # We can't capture any values if we don't know what kind of
+ # matcher this is
+ pattern === path
+ end
end
end

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
Oops, something went wrong.

0 comments on commit 043ded2

Please sign in to comment.