espn 0.1.3 → 0.2.0

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