espn 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. checksums.yaml +8 -8
  2. data/README.md +91 -6
  3. data/lib/espn.rb +5 -0
  4. data/lib/espn/arguments.rb +41 -0
  5. data/lib/espn/client.rb +29 -5
  6. data/lib/espn/client/athletes.rb +39 -21
  7. data/lib/espn/client/audio.rb +20 -19
  8. data/lib/espn/client/headlines.rb +58 -27
  9. data/lib/espn/client/medals.rb +8 -0
  10. data/lib/espn/client/notes.rb +31 -21
  11. data/lib/espn/client/now.rb +11 -8
  12. data/lib/espn/client/scores.rb +33 -21
  13. data/lib/espn/client/sports.rb +20 -14
  14. data/lib/espn/client/standings.rb +20 -16
  15. data/lib/espn/client/teams.rb +39 -21
  16. data/lib/espn/client/video.rb +14 -17
  17. data/lib/espn/configuration.rb +29 -12
  18. data/lib/espn/error.rb +15 -3
  19. data/lib/espn/helpers.rb +39 -0
  20. data/lib/espn/mapper.rb +142 -0
  21. data/lib/espn/request.rb +86 -7
  22. data/lib/espn/version.rb +1 -1
  23. data/lib/faraday/response/raise_espn_error.rb +33 -11
  24. data/spec/espn/arguments_spec.rb +76 -0
  25. data/spec/espn/client/athletes_spec.rb +38 -47
  26. data/spec/espn/client/audio_spec.rb +30 -29
  27. data/spec/espn/client/headlines_spec.rb +80 -35
  28. data/spec/espn/client/medals_spec.rb +1 -1
  29. data/spec/espn/client/notes_spec.rb +39 -45
  30. data/spec/espn/client/now_spec.rb +1 -2
  31. data/spec/espn/client/scores_spec.rb +56 -39
  32. data/spec/espn/client/sports_spec.rb +56 -18
  33. data/spec/espn/client/standings_spec.rb +42 -19
  34. data/spec/espn/client/teams_spec.rb +61 -43
  35. data/spec/espn/client/video_spec.rb +9 -13
  36. data/spec/espn/helpers_spec.rb +31 -0
  37. data/spec/espn/mapper_spec.rb +136 -0
  38. data/spec/espn/request_spec.rb +65 -0
  39. data/spec/espn_spec.rb +11 -2
  40. data/spec/faraday/response/raise_espn_error_spec.rb +80 -0
  41. data/spec/responses/athletes.json +51 -0
  42. data/spec/responses/audio.json +101 -0
  43. data/spec/responses/headlines.json +174 -0
  44. data/spec/responses/medals.json +273 -0
  45. data/spec/responses/notes.json +100 -0
  46. data/spec/responses/now.json +44 -0
  47. data/spec/responses/scores.json +209 -0
  48. data/spec/responses/sports.json +164 -0
  49. data/spec/responses/standings.json +2689 -0
  50. data/spec/responses/teams.json +51 -0
  51. data/spec/responses/videos.json +28 -0
  52. data/spec/spec_helper.rb +11 -2
  53. metadata +37 -3
  54. data/lib/espn/connection.rb +0 -32
@@ -1,12 +1,24 @@
1
1
  module ESPN
2
2
 
3
- # Custom error class for rescuing from all ESPN errors
3
+ # Public: Custom error class for rescuing from all ESPN errors
4
4
  class Error < StandardError; end
5
5
 
6
- # 401 HTTP Status Code
6
+ # Public: 400 HTTP Status Code
7
+ class BadRequest < Error; end
8
+
9
+ # Public: 401 HTTP Status Code
7
10
  class Unauthorized < Error; end
8
11
 
9
- # 404 HTTP Status Code
12
+ # Public: 403 HTTP Status Code
13
+ class Forbidden < Error; end
14
+
15
+ # Public: 404 HTTP Status Code
10
16
  class NotFound < Error; end
11
17
 
18
+ # Public: 500 HTTP Status Code
19
+ class InternalServerError < Error; end
20
+
21
+ # Public: 504 HTTP Status Code
22
+ class GatewayTimeout < Error; end
23
+
12
24
  end
@@ -0,0 +1,39 @@
1
+ module ESPN
2
+
3
+ # Public: Commonly used Helpers are defined in this module.
4
+ #
5
+ # Examples
6
+ #
7
+ # class Client
8
+ # include ESPN::Helpers
9
+ # end
10
+ module Helpers
11
+
12
+ # Public: Determine if a given Object is blank? If it is a String, or anything
13
+ # that responds to :empty?, it will use that method. If it does not respond to
14
+ # :empty? it will check that the Object is not nil.
15
+ #
16
+ # object - The Object to check for blank.
17
+ #
18
+ # Examples
19
+ #
20
+ # blank?('')
21
+ # # => true
22
+ #
23
+ # blank?('foo')
24
+ # # => false
25
+ #
26
+ # blank?(nil)
27
+ # # => true
28
+ #
29
+ # blank?(Object.new)
30
+ # # => false
31
+ #
32
+ # Returns a Boolean.
33
+ def blank?(object)
34
+ object.respond_to?(:empty?) ? object.empty? : !object
35
+ end
36
+
37
+ end
38
+
39
+ end
@@ -0,0 +1,142 @@
1
+ module ESPN
2
+
3
+ # Public: A module that handles mapping to make the ESPN API a little easier
4
+ # to digest.
5
+ #
6
+ # Examples
7
+ #
8
+ # class Client
9
+ # include ESPN::Mapper
10
+ # end
11
+ module Mapper
12
+
13
+ # Public: Map all common leagues to sports.
14
+ LEAGUE_MAP = {
15
+ baseball: %w(mlb college-baseball),
16
+ basketball: %w(nba wnba mens-college-basketball womens-college-basketball),
17
+ football: %w(nfl college-football),
18
+ golf: %w(eur lpga pga sga web),
19
+ hockey: %w(nhl mens-college-hockey womens-college-hockey),
20
+ mma: %w(affliction bamma bfc ifl k1 lfc m1 mfc pancrase pride ringside
21
+ sharkfights shooto shootobrazil shootojapan shoxc sfl strikeforce
22
+ tpf ufc wec),
23
+ racing: %w(irl sprint nationwide truck f1 nhra),
24
+ soccer: %w(arg.1 uefa.champions fifa.world aut.1 bel.1 den.1 fra.1 ger.1
25
+ ned.1 ita.1 nor.1 por.1 esp.1 swe.1 sui.1 tur.1 usa.1 usa.w.1
26
+ mex.1 eng.1 eng.2 eng.3 eng.4 eng.5 eng.fa eng.worthington
27
+ eng.trophy eng.charity uefa.uefa sco.1 sco.2 sco.3 sco.4
28
+ sco.tennents sco.cis sco.challenge uefa.intertoto fifa.friendly
29
+ uefa.worldq uefa.euroq fifa.confederations
30
+ conmebol.libertadores concacaf.gold fifa.wwc fifa.worldq
31
+ fifa.worldq.afc fifa.worldq.caf fifa.worldq.concacaf
32
+ fifa.worldq.conmebol fifa.worldq.ofc fifa.worldq.uefa friendly
33
+ usa.open fifa.friendly.w fifa.olympics fifa.olympicsq fifa.wyc
34
+ uefa.euro intercontinental caf.nations fifa.w.olympicsq
35
+ fifa.w.algarve esp.copa_del_rey conmebol.america
36
+ fifa.w.olympics bra.1 chi.1 fra.2 ger.2 gre.1 ita.2 ned.2
37
+ rus.1 esp.2 wal.1 irl.1 fra.coupe_de_la_ligue
38
+ fra.coupe_de_france esp.super_cup nir.1 usa.misl
39
+ conmebol.sudamericana uefa.super_cup aus.1 col.1 uru.1 per.1
40
+ par.1 usa.ncaa.1 usa.ncaa.w.1 fifa.cwc ecu.1 ven.1 bol.1
41
+ arg.urba ger.dfb_pokal ita.coppa_italia ned.cup rsa.1 rsa.2
42
+ jpn.1 afc.champions afc.cup slv.1 crc.1 afc.cupq hon.1 gua.1
43
+ aff.championship conmebol.sudamericano_sub20
44
+ concacaf.champions_cup uefa.euro_u21 fifa.world.u20
45
+ usa.world_series concacaf.superliga fifa.world.u17 concacaf.u23
46
+ usa.usl.1 arg.2 col.2 uefa.euro.u19 concacaf.champions bra.2
47
+ uru.2 arg.4 par.2 ven.2 arg.5 per.2 mex.2 chi.2 arg.3
48
+ mex.interliga uefa.europa ecu.2 global.world_football_challenge
49
+ uefa.carling eng.w.1 ger.super_cup ita.super_cup rug.irb.world
50
+ panam.m panam.w arg.copa),
51
+ tennis: %w(atp wta),
52
+ boxing: %w(),
53
+ olympics: %w(),
54
+ :'horse-racing' => %w()
55
+ }
56
+
57
+ # Public: Map a league to a sport.
58
+ #
59
+ # league - The league (String or Symbol) to map to a sport.
60
+ #
61
+ # Examples
62
+ #
63
+ # map_league_to_sport(:nhl)
64
+ # # => { league: 'nhl', sport: 'hockey' }
65
+ # map_league_to_sport('mlb')
66
+ # # => { league: 'mlb', sport: 'baseball' }
67
+ # map_league_to_sport('some-random-league')
68
+ # # => { league: 'some-random-league', sport: '' }
69
+ #
70
+ # Returns a Hash.
71
+ def map_league_to_sport(league)
72
+ result = { league: league.to_s, sport: nil }
73
+
74
+ LEAGUE_MAP.each do |sport, leagues|
75
+ if leagues.include? result[:league]
76
+ result[:sport] = sport.to_s
77
+ break
78
+ end
79
+ end
80
+
81
+ result
82
+ end
83
+
84
+ # Public: Determine if the value is a valid sport.
85
+ #
86
+ # test - The String or Symbol to test.
87
+ #
88
+ # Returns a Boolean.
89
+ def sport?(test)
90
+ LEAGUE_MAP.keys.include?(test.to_sym)
91
+ end
92
+
93
+ # Public: Determine if the test value is a valid league.
94
+ #
95
+ # test - The String or Symbol to test.
96
+ #
97
+ # Returns a Boolean.
98
+ def league?(test)
99
+ LEAGUE_MAP.values.flatten.include?(test.to_s)
100
+ end
101
+
102
+ # Public: Get league and sport from args array. If sport or league is passed
103
+ # in the opts hash, they will override any mappings from the args Array.
104
+ #
105
+ # args - The Array to extract the league and sport from.
106
+ # opts - The Hash that will override all mappings if possible.
107
+ #
108
+ # Examples
109
+ #
110
+ # extract_sport_and_league([:mlb])
111
+ # # => 'baseball', 'mlb'
112
+ #
113
+ # extract_sport_and_league(['horse-racing'])
114
+ # # => 'horse-racing', ''
115
+ #
116
+ # extract_sport_and_league(['baseball', 'mlb'], sport: 'basketball')
117
+ # # => 'basketball', 'mlb'
118
+ #
119
+ # Returns two Strings.
120
+ def extract_sport_and_league(args, opts={})
121
+ sport, league = opts[:sport], opts[:league]
122
+
123
+ if args.size == 1 && league?(args[0])
124
+ map = map_league_to_sport(args[0])
125
+ sport ||= map[:sport].to_s
126
+ league ||= map[:league].to_s
127
+ elsif args.size == 1 && sport?(args[0])
128
+ sport ||= args[0].to_s
129
+ league ||= ''
130
+ elsif !opts[:league].to_s.empty?
131
+ map = map_league_to_sport(opts[:league])
132
+ sport ||= map[:sport].to_s
133
+ else
134
+ sport ||= args[0].to_s || ''
135
+ league ||= args[1].to_s || ''
136
+ end
137
+
138
+ return sport, league
139
+ end
140
+
141
+ end
142
+ end
@@ -1,24 +1,54 @@
1
1
  require 'multi_json'
2
+ require 'faraday_middleware'
3
+ require 'faraday/response/raise_espn_error'
2
4
 
3
5
  module ESPN
6
+
7
+ # Public: Make the request magic happen. This module gets included and only
8
+ # exposes one method to the public interface: #get.
9
+ #
10
+ # Examples
11
+ #
12
+ # class Client
13
+ # include ESPN::Request
14
+ # end
4
15
  module Request
5
- def get(path, options={})
6
- request(:get, path, options)
16
+
17
+ # Public: Make an HTTP GET Request to the path, passing the opts as params
18
+ # in the query.
19
+ #
20
+ # pattern - A String to build a URL from.
21
+ # opts - A Hash to send as query parameters (default: {}).
22
+ #
23
+ # Returns a String.
24
+ def get(pattern, opts={})
25
+ path = build_url(pattern, opts)
26
+ request(:get, path, opts)
7
27
  end
8
28
 
9
29
  private
10
30
 
11
- def request(method, path, options)
31
+ # Internal: Use the Faraday::Connection from lib/espn/connection.rb and
32
+ # make the HTTP Request to the path.
33
+ #
34
+ # method - A Symbol specifying the HTTP method to use.
35
+ # path - The URL to send the request to.
36
+ # opts - The Hash options to send as query parameters.
37
+ #
38
+ # Returns a String.
39
+ def request(method, path, opts)
12
40
 
13
41
  # TODO: Decide if I want to delete these or not. There is probably
14
42
  # a better way to do this, if so, by filtering them out.
15
- %w( sport league method section team_id headline_id category_id clip_id
16
- athlete_id event_id note_id podcast_id recording_id ).each do |k|
17
- options.delete(k.to_sym)
43
+ %w(
44
+ sport league method section id team_id headline_id category_id
45
+ clip_id athlete_id event_id note_id podcast_id recording_id
46
+ ).each do |k|
47
+ opts.delete(k.to_sym)
18
48
  end
19
49
 
20
50
  response = connection.send(method) do |request|
21
- request.url(path, options)
51
+ request.url(path, opts)
22
52
  request.options[:timeout] = timeout
23
53
  request.options[:open_timeout] = open_timeout
24
54
  end
@@ -26,5 +56,54 @@ module ESPN
26
56
  response.body
27
57
  end
28
58
 
59
+ # Internal: Build a new instance of Faraday with some default options and
60
+ # return it.
61
+ #
62
+ # Returns a Faraday::Connection.
63
+ def connection
64
+ options = { proxy: proxy, ssl: { verify: false }, url: api_url }
65
+ options.merge!(params: { apikey: api_key }) if authed?
66
+
67
+ connection = Faraday.new(options) do |builder|
68
+ builder.use Faraday::Response::RaiseESPNError
69
+ builder.use FaradayMiddleware::Mashify
70
+ builder.use FaradayMiddleware::ParseJson
71
+ builder.adapter adapter
72
+ end
73
+
74
+ connection
75
+ end
76
+
77
+ # Internal: Takes a pattern and a Hash of fragments and constructs a url.
78
+ # If the fragments include a league but not a sport, the league will be
79
+ # deleted from the Hash so it does not get sent along.
80
+ #
81
+ # pattern - The pattern to match fragments against.
82
+ # fragments - The Array of fragments to apply to the pattern.
83
+ #
84
+ # Returns a String.
85
+ def build_url(pattern, fragments)
86
+
87
+ # Only allow league if sport is passed in.
88
+ fragments.delete(:league) if fragments[:sport].to_s.empty?
89
+
90
+ # Remove the /headlines section if fragments does not include a :method.
91
+ # This is due to the HEADLINES API being a little strange.
92
+ unless fragments.include? :method
93
+ pattern.gsub!('headlines', '')
94
+ end
95
+
96
+ template = URITemplate.new(:colon, pattern)
97
+ url = template.expand(fragments)
98
+
99
+ # Remove duplicate slashes (if any)
100
+ url = url.gsub(/\/{2,}/,'/')
101
+
102
+ # Remove trailing slash (if any)
103
+ url = url.gsub(/\/+$/, '')
104
+
105
+ url
106
+ end
107
+
29
108
  end
30
109
  end
@@ -1,6 +1,6 @@
1
1
  module ESPN
2
2
 
3
3
  # Public: The version of the ESPN gem.
4
- VERSION = '0.1.3'.freeze
4
+ VERSION = '0.2.0'.freeze
5
5
 
6
6
  end
@@ -1,28 +1,50 @@
1
1
  require 'faraday'
2
2
  require 'multi_json'
3
3
 
4
- # @api private
4
+ # Internal: Faraday module that the response error middleware is added to.
5
5
  module Faraday
6
+
7
+ # Internal: Faraday Middleware that deals with errors coming back from the API.
8
+ #
9
+ # Examples
10
+ #
11
+ # conn = Faraday.new({}) do |builder|
12
+ # builder.use Faraday::Response::RaiseESPNError
13
+ # end
6
14
  class Response::RaiseESPNError < Response::Middleware
15
+
16
+ # Internal: Hook into the complete callback for the client. When the result
17
+ # comes back, if it is a status code that we want to raise an error for, we
18
+ # will do that.
19
+ #
20
+ # Raises ESPN::Unauthorized, ESPN::Forbidden and ESPN::Notfound.
21
+ # Returns nothing.
7
22
  def on_complete(response)
8
- case response[:status].to_i
23
+ return if response[:body].nil?
24
+
25
+ case response[:body][:code].to_i
26
+ when 400
27
+ raise ESPN::BadRequest, error_message(response)
9
28
  when 401
10
29
  raise ESPN::Unauthorized, error_message(response)
30
+ when 403
31
+ raise ESPN::Forbidden, error_message(response)
11
32
  when 404
12
33
  raise ESPN::NotFound, error_message(response)
34
+ when 500
35
+ raise ESPN::InternalServerError, error_message(response)
36
+ when 504
37
+ raise ESPN::GatewayTimeout, error_message(response)
13
38
  end
14
39
  end
15
40
 
41
+ # Internal: Parse the response and return a human friendly String
42
+ # representing the error that we received.
43
+ #
44
+ # Returns a String.
16
45
  def error_message(response)
17
- message = if (body = response[:body]) && !body.empty?
18
- if body.is_a?(String)
19
- body = MultiJson.load(body, :symbolize_keys => true)
20
- end
21
- ": #{body[:error] || body[:message] || ''}"
22
- else
23
- ''
24
- end
25
- "#{response[:method].to_s.upcase} #{response[:url].to_s}: #{response[:status]}#{message}"
46
+ "#{response[:method].to_s.upcase} #{response[:url].to_s}: #{response[:body][:status]} - #{response[:body][:message]}"
26
47
  end
27
48
  end
49
+
28
50
  end
@@ -0,0 +1,76 @@
1
+ require 'spec_helper'
2
+
3
+ describe ESPN::Arguments do
4
+ before do
5
+ @arguments = ESPN::Arguments.new([])
6
+ end
7
+
8
+ it 'should have an options reader' do
9
+ @arguments.respond_to?(:options).should be_true
10
+ end
11
+
12
+ it 'should not have an options writer' do
13
+ @arguments.respond_to?(:options=).should be_false
14
+ end
15
+
16
+ it 'should default options to an empty hash' do
17
+ @arguments.options.delete(:sport)
18
+ @arguments.options.delete(:league)
19
+ @arguments.options.should eq({})
20
+ end
21
+
22
+ it 'should extract options' do
23
+ @arguments = ESPN::Arguments.new([{ foo: 'bar' }])
24
+ @arguments.options.delete(:sport)
25
+ @arguments.options.delete(:league)
26
+ @arguments.options.should eq({foo: 'bar'})
27
+ end
28
+
29
+ it 'should extract sport' do
30
+ @arguments = ESPN::Arguments.new([:baseball])
31
+ @arguments.options[:sport].should eq('baseball')
32
+ end
33
+
34
+ it 'should extract league mapped to a sport' do
35
+ @arguments = ESPN::Arguments.new([:mlb])
36
+ @arguments.options.should eq({sport: 'baseball', league: 'mlb'})
37
+ end
38
+
39
+ it 'should extract sport and league' do
40
+ @arguments = ESPN::Arguments.new([:football, :mlb])
41
+ @arguments.options.should eq({sport: 'football', league: 'mlb'})
42
+ end
43
+
44
+ context 'when passing default values' do
45
+ it 'should set default values' do
46
+ @arguments = ESPN::Arguments.new([{sport: 'baseball'}], league: 'mlb')
47
+ @arguments.options.should eq({sport: 'baseball', league: 'mlb'})
48
+ end
49
+
50
+ it 'should not override non-nil values' do
51
+ @arguments = ESPN::Arguments.new([{sport: 'baseball'}], sport: 'football')
52
+ @arguments.options[:sport].should eq('baseball')
53
+ end
54
+ end
55
+
56
+ context 'when passing required fields' do
57
+ it 'should raise an exception if the field is blank' do
58
+ expect {
59
+ ESPN::Arguments.new([], {}, [:league])
60
+ }.to raise_error(ArgumentError, 'You must supply a valid league.')
61
+ end
62
+
63
+ it 'should not raise an exception if the field is not blank' do
64
+ expect {
65
+ ESPN::Arguments.new([:mlb], {}, [:league])
66
+ }.not_to raise_error(ArgumentError)
67
+ end
68
+
69
+ it 'should accept a string' do
70
+ expect {
71
+ ESPN::Arguments.new([:mlb], {}, ['league'])
72
+ }.not_to raise_error(ArgumentError)
73
+ end
74
+ end
75
+
76
+ end