strava-ruby-client 3.0.0.pre.2 → 3.1.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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +15 -2
  3. data/README.md +47 -58
  4. data/lib/strava/api/client.rb +69 -0
  5. data/lib/strava/api/config.rb +40 -0
  6. data/lib/strava/api/cursor.rb +32 -1
  7. data/lib/strava/api/endpoints/activities.rb +197 -57
  8. data/lib/strava/api/endpoints/athletes.rb +64 -8
  9. data/lib/strava/api/endpoints/clubs.rb +24 -59
  10. data/lib/strava/api/endpoints/gears.rb +11 -2
  11. data/lib/strava/api/endpoints/oauth.rb +45 -0
  12. data/lib/strava/api/endpoints/routes.rb +20 -13
  13. data/lib/strava/api/endpoints/segment_efforts.rb +15 -8
  14. data/lib/strava/api/endpoints/segments.rb +74 -21
  15. data/lib/strava/api/endpoints/streams.rb +22 -18
  16. data/lib/strava/api/endpoints/uploads.rb +12 -2
  17. data/lib/strava/api/pagination.rb +25 -0
  18. data/lib/strava/api/ratelimit.rb +34 -0
  19. data/lib/strava/deep_copyable.rb +20 -1
  20. data/lib/strava/errors/fault.rb +32 -0
  21. data/lib/strava/errors/ratelimit_error.rb +35 -0
  22. data/lib/strava/errors/upload_error.rb +55 -0
  23. data/lib/strava/logger.rb +27 -0
  24. data/lib/strava/models/achievement.rb +28 -1
  25. data/lib/strava/models/activity_stats.rb +47 -1
  26. data/lib/strava/models/activity_total.rb +25 -1
  27. data/lib/strava/models/activity_zone.rb +39 -2
  28. data/lib/strava/models/authorization.rb +13 -0
  29. data/lib/strava/models/base_stream.rb +22 -1
  30. data/lib/strava/models/club_event.rb +64 -1
  31. data/lib/strava/models/comment.rb +39 -1
  32. data/lib/strava/models/destination.rb +28 -1
  33. data/lib/strava/models/detailed_activity.rb +203 -2
  34. data/lib/strava/models/detailed_athlete.rb +104 -2
  35. data/lib/strava/models/detailed_club.rb +76 -2
  36. data/lib/strava/models/detailed_gear.rb +45 -2
  37. data/lib/strava/models/detailed_photo.rb +61 -1
  38. data/lib/strava/models/detailed_photos.rb +29 -1
  39. data/lib/strava/models/detailed_segment.rb +89 -2
  40. data/lib/strava/models/detailed_segment_effort.rb +92 -3
  41. data/lib/strava/models/explorer_segment.rb +55 -2
  42. data/lib/strava/models/heart_rate_zone_ranges.rb +29 -1
  43. data/lib/strava/models/kudoser.rb +29 -1
  44. data/lib/strava/models/lap.rb +62 -2
  45. data/lib/strava/models/lat_lng.rb +42 -4
  46. data/lib/strava/models/local_legend.rb +41 -1
  47. data/lib/strava/models/map.rb +31 -0
  48. data/lib/strava/models/meta_activity.rb +15 -2
  49. data/lib/strava/models/meta_athlete.rb +17 -2
  50. data/lib/strava/models/meta_club.rb +15 -1
  51. data/lib/strava/models/mixins/average_speed.rb +81 -3
  52. data/lib/strava/models/mixins/distance.rb +25 -0
  53. data/lib/strava/models/mixins/elapsed_time.rb +18 -0
  54. data/lib/strava/models/mixins/elevation_difference.rb +42 -0
  55. data/lib/strava/models/mixins/elevation_gain.rb +39 -0
  56. data/lib/strava/models/mixins/estimated_moving_time.rb +18 -0
  57. data/lib/strava/models/mixins/http_response.rb +36 -0
  58. data/lib/strava/models/mixins/moving_time.rb +18 -0
  59. data/lib/strava/models/mixins/sport_type.rb +39 -1
  60. data/lib/strava/models/mixins/start_date_local.rb +40 -24
  61. data/lib/strava/models/mixins/time_in_hours.rb +23 -0
  62. data/lib/strava/models/mixins/total_elevation_gain.rb +42 -0
  63. data/lib/strava/models/model.rb +14 -0
  64. data/lib/strava/models/photos_summary.rb +26 -2
  65. data/lib/strava/models/photos_summary_primary.rb +28 -2
  66. data/lib/strava/models/power_zone_ranges.rb +23 -1
  67. data/lib/strava/models/response.rb +18 -0
  68. data/lib/strava/models/route.rb +63 -2
  69. data/lib/strava/models/similar_activities.rb +57 -1
  70. data/lib/strava/models/split.rb +27 -2
  71. data/lib/strava/models/stats_visibility.rb +24 -1
  72. data/lib/strava/models/stream.rb +107 -0
  73. data/lib/strava/models/stream_set.rb +52 -1
  74. data/lib/strava/models/summary_activity.rb +145 -2
  75. data/lib/strava/models/summary_athlete.rb +54 -1
  76. data/lib/strava/models/summary_club.rb +61 -2
  77. data/lib/strava/models/summary_gear.rb +31 -2
  78. data/lib/strava/models/summary_pr_segment_effort.rb +32 -2
  79. data/lib/strava/models/summary_segment.rb +80 -2
  80. data/lib/strava/models/summary_segment_effort.rb +32 -1
  81. data/lib/strava/models/timed_zone_range.rb +24 -1
  82. data/lib/strava/models/token.rb +31 -0
  83. data/lib/strava/models/trend.rb +38 -1
  84. data/lib/strava/models/updatable_activity.rb +36 -1
  85. data/lib/strava/models/upload.rb +50 -1
  86. data/lib/strava/models/waypoint.rb +31 -1
  87. data/lib/strava/models/xoms.rb +35 -1
  88. data/lib/strava/models/zone_range.rb +20 -1
  89. data/lib/strava/models/zones.rb +32 -1
  90. data/lib/strava/oauth/client.rb +107 -19
  91. data/lib/strava/oauth/config.rb +41 -0
  92. data/lib/strava/version.rb +1 -1
  93. data/lib/strava/web/api_response.rb +28 -2
  94. data/lib/strava/web/client.rb +72 -2
  95. data/lib/strava/web/config.rb +40 -0
  96. data/lib/strava/web/connection.rb +39 -0
  97. data/lib/strava/web/raise_response_error.rb +47 -0
  98. data/lib/strava/web/request.rb +56 -0
  99. data/lib/strava/web/response.rb +18 -0
  100. data/lib/strava/webhooks/client.rb +108 -7
  101. data/lib/strava/webhooks/config.rb +44 -0
  102. data/lib/strava/webhooks/models/challenge.rb +40 -0
  103. data/lib/strava/webhooks/models/event.rb +50 -0
  104. data/lib/strava/webhooks/models/subscription.rb +27 -0
  105. data/lib/strava-ruby-client.rb +0 -2
  106. metadata +2 -4
  107. data/lib/strava/models/club_activity.rb +0 -22
  108. data/lib/strava/models/club_athlete.rb +0 -21
@@ -3,12 +3,21 @@
3
3
  module Strava
4
4
  module Api
5
5
  module Endpoints
6
+ #
7
+ # API endpoints for Strava routes.
8
+ #
9
+ # Routes are manually-created paths on Strava that can be used for planning.
10
+ # Routes can be exported in GPX or TCX format and can include waypoints,
11
+ # elevation data, and other metadata.
12
+ #
13
+ # @see https://developers.strava.com/docs/reference/#api-Routes
14
+ #
6
15
  module Routes
7
16
  #
8
17
  # Returns a GPX file of the route.
9
18
  #
10
- # @option options [String] :id
11
- # Route id.
19
+ # @param id_or_options [String, Integer, Hash] Either a route ID or a hash of options including :id
20
+ # @param options [Hash] Additional options (if first parameter is an ID)
12
21
  #
13
22
  def export_route_gpx(id_or_options, options = {})
14
23
  id, options = parse_args(id_or_options, options)
@@ -16,10 +25,10 @@ module Strava
16
25
  end
17
26
 
18
27
  #
19
- # Returns a TCS file of the route.
28
+ # Returns a TCX file of the route.
20
29
  #
21
- # @option options [String] :id
22
- # Route id.
30
+ # @param id_or_options [String, Integer, Hash] Either a route ID or a hash of options including :id
31
+ # @param options [Hash] Additional options (if first parameter is an ID)
23
32
  #
24
33
  def export_route_tcx(id_or_options, options = {})
25
34
  id, options = parse_args(id_or_options, options)
@@ -29,8 +38,8 @@ module Strava
29
38
  #
30
39
  # Returns a route using its identifier.
31
40
  #
32
- # @option options [String] :id
33
- # Route id.
41
+ # @param id_or_options [String, Integer, Hash] Either a route ID or a hash of options including :id
42
+ # @param options [Hash] Additional options (if first parameter is an ID)
34
43
  #
35
44
  def route(id_or_options, options = {})
36
45
  id, options = parse_args(id_or_options, options)
@@ -40,12 +49,10 @@ module Strava
40
49
  #
41
50
  # Returns a list of the routes created by the authenticated athlete using their athlete ID.
42
51
  #
43
- # @option options [Integer] :id
44
- # The identifier of the athlete.
45
- # @option options [Integer] :page
46
- # Page number.
47
- # @option options [Integer] :per_page
48
- # Number of items per page. Defaults to 30.
52
+ # @param id_or_options [String, Integer, Hash] Either an athlete ID or a hash of options including :id
53
+ # @param options [Hash] Additional options (if first parameter is an ID)
54
+ # @option options [Integer] :page Page number
55
+ # @option options [Integer] :per_page Number of items per page. Defaults to 30
49
56
  #
50
57
  def athlete_routes(id_or_options, options = {}, &block)
51
58
  id, options = parse_args(id_or_options, options)
@@ -3,12 +3,21 @@
3
3
  module Strava
4
4
  module Api
5
5
  module Endpoints
6
+ #
7
+ # API endpoints for Strava segment efforts.
8
+ #
9
+ # Segment efforts represent an athlete's attempt on a specific segment during an
10
+ # activity. Each time an activity passes through a segment, a segment effort is
11
+ # created with details like elapsed time, distance, and ranking information.
12
+ #
13
+ # @see https://developers.strava.com/docs/reference/#api-SegmentEfforts
14
+ #
6
15
  module SegmentEfforts
7
16
  #
8
17
  # Returns a segment effort from an activity that is owned by the authenticated athlete.
9
18
  #
10
- # @option options [String] :id
11
- # The identifier of the segment effort.
19
+ # @param id_or_options [String, Integer, Hash] Either a segment effort ID or a hash of options including :id
20
+ # @param options [Hash] Additional options (if first parameter is an ID)
12
21
  #
13
22
  def segment_effort(id_or_options, options = {})
14
23
  id, options = parse_args(id_or_options, options)
@@ -18,12 +27,10 @@ module Strava
18
27
  #
19
28
  # Returns a set of the authenticated athlete's segment efforts for a given segment.
20
29
  #
21
- # @option options [Integer] :id
22
- # The identifier of the segment.
23
- # @option options [Integer] :page
24
- # Page number.
25
- # @option options [Integer] :per_page
26
- # Number of items per page. Defaults to 30.
30
+ # @param id_or_options [String, Integer, Hash] Either a segment ID or a hash of options including :id
31
+ # @param options [Hash] Additional options (if first parameter is an ID)
32
+ # @option options [Integer] :page Page number
33
+ # @option options [Integer] :per_page Number of items per page. Defaults to 30
27
34
  #
28
35
  def segment_efforts(id_or_options, options = {}, &block)
29
36
  id, options = parse_args(id_or_options, options)
@@ -3,21 +3,40 @@
3
3
  module Strava
4
4
  module Api
5
5
  module Endpoints
6
+ #
7
+ # API endpoints for Strava segments.
8
+ #
9
+ # Segments are specific sections of road or trail where athletes can compete for time.
10
+ # This module provides methods for exploring, retrieving, and starring segments.
11
+ #
12
+ # @see https://developers.strava.com/docs/reference/#api-Segments
13
+ #
6
14
  module Segments
7
15
  #
8
- # Returns the top 10 segments matching a specified query.
16
+ # Explore segments in a geographic area.
9
17
  #
10
- # @option options [Array[Float]] :bounds
11
- # The latitude and longitude for two points describing a rectangular boundary for the search: [southwest corner latitude, southwest corner longitude, northeast corner latitude, northeast corner longitude].
12
- # @option options [String] :activity_type
13
- # Desired activity type. May take one of the following values: running, riding.
14
- # @option options [Integer] :min_cat
15
- # The minimum climbing category.
16
- # @option options [Integer] :max_cat
17
- # The maximum climbing category.
18
+ # Returns the top 10 segments matching a specified query within a rectangular
19
+ # geographic boundary. Useful for discovering popular segments in an area.
20
+ #
21
+ # @param [Hash] options Search parameters
22
+ # @option options [Array<Float>] :bounds Required. Rectangular boundary as [sw_lat, sw_lng, ne_lat, ne_lng]
23
+ # @option options [String] :activity_type Activity type: 'running' or 'riding'
24
+ # @option options [Integer] :min_cat Minimum climbing category (0-5)
25
+ # @option options [Integer] :max_cat Maximum climbing category (0-5)
26
+ #
27
+ # @return [Array<Strava::Models::ExplorerSegment>] Array of segments
28
+ #
29
+ # @example Explore segments in an area
30
+ # segments = client.explore_segments(
31
+ # bounds: [40.7,-74.0,40.8,-73.9],
32
+ # activity_type: 'running'
33
+ # )
34
+ #
35
+ # @see https://developers.strava.com/docs/reference/#api-Segments-exploreSegments
18
36
  #
19
37
  def explore_segments(options = {})
20
- throw ArgumentError.new('Required argument :bounds missing') if options[:bounds].nil?
38
+ raise ArgumentError, 'Required argument :bounds missing' if options[:bounds].nil?
39
+
21
40
  bounds = options[:bounds]
22
41
  bounds = bounds.map(&:to_s).join(',') if bounds.is_a?(Array)
23
42
  get('segments/explore', options.merge(bounds: bounds))['segments'].map do |row|
@@ -28,10 +47,21 @@ module Strava
28
47
  #
29
48
  # List of the authenticated athlete's starred segments.
30
49
  #
31
- # @option options [Integer] :page
32
- # Page number.
33
- # @option options [Integer] :per_page
34
- # Number of items per page. Defaults to 30.
50
+ # Returns a paginated list of segments that the authenticated athlete has starred.
51
+ # Starred segments are favorites that the athlete wants to track their performance on.
52
+ #
53
+ # @param options [Hash] Pagination options
54
+ # @option options [Integer] :page Page number
55
+ # @option options [Integer] :per_page Number of items per page. Defaults to 30
56
+ #
57
+ # @yield [SummarySegment] Yields each segment in the paginated results
58
+ # @return [Array<SummarySegment>, Cursor] Array of starred segments or Cursor for iteration
59
+ #
60
+ # @example List all starred segments
61
+ # starred = client.starred_segments
62
+ # starred.each do |segment|
63
+ # puts "#{segment.name}: #{segment.distance_s}"
64
+ # end
35
65
  #
36
66
  def starred_segments(options = {}, &block)
37
67
  paginate 'segments/starred', options, Strava::Models::SummarySegment, &block
@@ -40,8 +70,17 @@ module Strava
40
70
  #
41
71
  # Returns the specified segment.
42
72
  #
43
- # @option options [String] :id
44
- # The identifier of the segment.
73
+ # Retrieves detailed information about a specific segment including location,
74
+ # elevation profile, and the athlete's personal records on that segment.
75
+ #
76
+ # @param id_or_options [String, Integer, Hash] Either a segment ID or a hash of options including :id
77
+ # @param options [Hash] Additional options (if first parameter is an ID)
78
+ #
79
+ # @return [DetailedSegment] The detailed segment information
80
+ #
81
+ # @example Get a segment by ID
82
+ # segment = client.segment(229781)
83
+ # puts "#{segment.name}: #{segment.distance_s} with #{segment.average_grade}% grade"
45
84
  #
46
85
  def segment(id_or_options, options = {})
47
86
  id, options = parse_args(id_or_options, options)
@@ -51,14 +90,28 @@ module Strava
51
90
  #
52
91
  # Stars/Unstars the given segment for the authenticated athlete.
53
92
  #
54
- # @option options [String] :id
55
- # The identifier of the segment to star.
56
- # @option options [Boolean] :starred
57
- # If true, star the segment; if false, unstar the segment.
93
+ # Adds or removes a segment from the athlete's starred/favorite segments.
94
+ # Starred segments appear in the athlete's starred segments list and can
95
+ # be used to track performance over time.
96
+ #
97
+ # @param id_or_options [String, Integer, Hash] Either a segment ID or a hash of options including :id
98
+ # @param options [Hash] Additional options (if first parameter is an ID)
99
+ # @option options [Boolean] :starred If true, star the segment; if false, unstar the segment (required)
100
+ #
101
+ # @return [DetailedSegment] The updated segment with new starred status
102
+ #
103
+ # @raise [ArgumentError] If :starred option is not provided
104
+ #
105
+ # @example Star a segment
106
+ # segment = client.star_segment(229781, starred: true)
107
+ #
108
+ # @example Unstar a segment
109
+ # segment = client.star_segment(229781, starred: false)
58
110
  #
59
111
  def star_segment(id_or_options, options = {})
60
112
  id, options = parse_args(id_or_options, options)
61
- throw ArgumentError.new('Required argument :starred missing') if options[:starred].nil?
113
+ raise ArgumentError, 'Required argument :starred missing' if options[:starred].nil?
114
+
62
115
  Strava::Models::DetailedSegment.new(put("segments/#{id}/starred", options))
63
116
  end
64
117
  end
@@ -3,16 +3,24 @@
3
3
  module Strava
4
4
  module Api
5
5
  module Endpoints
6
+ #
7
+ # API endpoints for Strava streams.
8
+ #
9
+ # Streams represent the raw data of uploaded activities. They can be thought of
10
+ # as a collection of time series data, where each data point is sampled at a
11
+ # specific time or distance. Available stream types include altitude, cadence,
12
+ # distance, heartrate, latlng, moving, power, temp, time, and velocity.
13
+ #
14
+ # @see https://developers.strava.com/docs/reference/#api-Streams
15
+ #
6
16
  module Streams
7
17
  #
8
18
  # Returns the given activity's streams.
9
19
  #
10
- # @option options [String] :id
11
- # The identifier of the activity.
12
- # @option options [Array[String]] :keys
13
- # Desired stream types.
14
- # @option options [Boolean] :key_by_type
15
- # Must be true.
20
+ # @param id_or_options [String, Integer, Hash] Either an activity ID or a hash of options including :id
21
+ # @param options [Hash] Additional options (if first parameter is an ID)
22
+ # @option options [Array<String>] :keys Desired stream types (e.g., ['time', 'latlng', 'distance', 'altitude'])
23
+ # @option options [Boolean] :key_by_type Must be true
16
24
  #
17
25
  def activity_streams(id_or_options, options = {})
18
26
  id, options = parse_args(id_or_options, options)
@@ -25,12 +33,10 @@ module Strava
25
33
  #
26
34
  # Returns a set of streams for a segment effort completed by the authenticated athlete.
27
35
  #
28
- # @option options [String] :id
29
- # The identifier of the segment effort.
30
- # @option options [Array[String]] :keys
31
- # The types of streams to return.
32
- # @option options [Boolean] :key_by_type
33
- # Must be true.
36
+ # @param id_or_options [String, Integer, Hash] Either a segment effort ID or a hash of options including :id
37
+ # @param options [Hash] Additional options (if first parameter is an ID)
38
+ # @option options [Array<String>] :keys The types of streams to return
39
+ # @option options [Boolean] :key_by_type Must be true
34
40
  #
35
41
  def segment_effort_streams(id_or_options, options = {})
36
42
  id, options = parse_args(id_or_options, options)
@@ -43,12 +49,10 @@ module Strava
43
49
  #
44
50
  # Returns the given segment's streams.
45
51
  #
46
- # @option options [String] :id
47
- # The identifier of the segment.
48
- # @option options [Array[String]] :keys
49
- # The types of streams to return.
50
- # @option options [Boolean] :key_by_type
51
- # Must be true.
52
+ # @param id_or_options [String, Integer, Hash] Either a segment ID or a hash of options including :id
53
+ # @param options [Hash] Additional options (if first parameter is an ID)
54
+ # @option options [Array<String>] :keys The types of streams to return
55
+ # @option options [Boolean] :key_by_type Must be true
52
56
  #
53
57
  def segment_streams(id_or_options, options = {})
54
58
  id, options = parse_args(id_or_options, options)
@@ -3,6 +3,16 @@
3
3
  module Strava
4
4
  module Api
5
5
  module Endpoints
6
+ #
7
+ # API endpoints for Strava uploads.
8
+ #
9
+ # Uploads allow you to create activities by uploading activity files in various
10
+ # formats such as FIT, TCX, and GPX. After uploading, the file is processed
11
+ # asynchronously and you can check the upload status to determine when the
12
+ # activity has been created.
13
+ #
14
+ # @see https://developers.strava.com/docs/reference/#api-Uploads
15
+ #
6
16
  module Uploads
7
17
  #
8
18
  # Uploads a new data file to create an activity from.
@@ -29,8 +39,8 @@ module Strava
29
39
  #
30
40
  # Returns an upload for a given identifier.
31
41
  #
32
- # @option options [String] :id
33
- # The identifier of the upload.
42
+ # @param id_or_options [String, Integer, Hash] Either an upload ID or a hash of options including :id
43
+ # @param options [Hash] Additional options (if first parameter is an ID)
34
44
  #
35
45
  def upload(id_or_options, options = {})
36
46
  id, options = parse_args(id_or_options, options)
@@ -41,16 +41,41 @@ module Strava
41
41
  @collection.size
42
42
  end
43
43
 
44
+ #
45
+ # Iterates over each item in the paginated collection.
46
+ #
47
+ # @yield [Object] Yields each item in the collection
48
+ # @return [void]
49
+ #
44
50
  def each
45
51
  @collection.each { |c| yield c if block_given? }
46
52
  end
47
53
 
48
54
  private
49
55
 
56
+ #
57
+ # Delegates missing methods to the underlying collection.
58
+ #
59
+ # Allows the Pagination object to respond to array methods by
60
+ # forwarding them to the internal collection array.
61
+ #
62
+ # @api private
63
+ # @param method_symbol [Symbol] The method name to forward
64
+ # @param args [Array] Arguments to pass to the method
65
+ # @return [Object] Result from the delegated method
66
+ #
50
67
  def method_missing(method_symbol, ...)
51
68
  @collection.send(method_symbol, ...)
52
69
  end
53
70
 
71
+ #
72
+ # Checks if the underlying collection responds to a method.
73
+ #
74
+ # @api private
75
+ # @param method_name [Symbol] The method name to check
76
+ # @param include_private [Boolean] Whether to include private methods
77
+ # @return [Boolean] true if the collection responds to the method
78
+ #
54
79
  def respond_to_missing?(method_name, include_private = false)
55
80
  super
56
81
  end
@@ -2,7 +2,41 @@
2
2
 
3
3
  module Strava
4
4
  module Api
5
+ #
6
+ # Handles Strava API rate limit information from HTTP responses.
7
+ #
8
+ # Strava enforces rate limits on API requests with two windows:
9
+ # - 15-minute window: 600 requests per 15 minutes (short-term limit)
10
+ # - Daily window: 30,000 requests per day (long-term limit)
11
+ #
12
+ # This class extracts and provides easy access to rate limit data from
13
+ # HTTP response headers.
14
+ #
15
+ # @see Strava::Web::ApiResponse
16
+ # @see https://developers.strava.com/docs/rate-limits/ Strava API Rate Limiting documentation
17
+ #
18
+ # @example Checking rate limits after an API call
19
+ # activity = client.activity(1234567890)
20
+ # ratelimit = activity.http_response.ratelimit
21
+ #
22
+ # puts "15-minute limit: #{ratelimit.fifteen_minutes_limit}"
23
+ # puts "15-minute usage: #{ratelimit.fifteen_minutes_usage}"
24
+ # puts "15-minute remaining: #{ratelimit.fifteen_minutes_remaining}"
25
+ #
26
+ # puts "Daily limit: #{ratelimit.total_day}"
27
+ # puts "Daily usage: #{ratelimit.total_day_usage}"
28
+ # puts "Daily remaining: #{ratelimit.total_day_remaining}"
29
+ #
30
+ # if ratelimit.exceeded?
31
+ # puts "Rate limit exceeded!"
32
+ # end
33
+ #
5
34
  class Ratelimit
35
+ #
36
+ # Initialize a new Ratelimit instance from an HTTP response.
37
+ #
38
+ # @param response [Faraday::Response] HTTP response containing rate limit headers
39
+ #
6
40
  def initialize(response)
7
41
  @response = response
8
42
  @headers = response.headers
@@ -1,9 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Strava
4
+ #
5
+ # Mixin module for deep copying objects.
6
+ #
7
+ # This module provides a helper method for creating deep copies of objects
8
+ # using Ruby's Marshal serialization. Unlike shallow cloning (which only
9
+ # duplicates the top-level object), deep copying recursively duplicates all
10
+ # nested objects and collections.
11
+ #
12
+ # This is particularly useful when working with model objects that contain
13
+ # nested hashes or arrays, ensuring modifications to the copy don't affect
14
+ # the original object.
15
+ #
16
+ # @example Deep copying a nested hash
17
+ # include Strava::DeepCopyable
18
+ # original = {a: {b: [1, 2, 3]}}
19
+ # copy = deep_copy(original)
20
+ # copy[:a][:b] << 4
21
+ # original[:a][:b] # => [1, 2, 3] (unchanged)
22
+ #
4
23
  module DeepCopyable
5
24
  #
6
- # Ruby's way of creating a true deep copy/clone
25
+ # Ruby's way of creating a true deep copy/clone.
7
26
  #
8
27
  # @param [Object] obj of any kind
9
28
  #
@@ -2,15 +2,47 @@
2
2
 
3
3
  module Strava
4
4
  module Errors
5
+ #
6
+ # Base error class for Strava API client errors.
7
+ #
8
+ # This exception is raised when the Strava API returns an error response.
9
+ # It extends Faraday::ClientError and provides convenient access to the
10
+ # error message, response headers, and detailed error information from
11
+ # the API response body.
12
+ #
13
+ # @see Faraday::ClientError
14
+ #
5
15
  class Fault < ::Faraday::ClientError
16
+ #
17
+ # Returns the error message from the API response.
18
+ #
19
+ # Extracts the message from the response body if available,
20
+ # otherwise falls back to the parent class message.
21
+ #
22
+ # @return [String] The error message
23
+ #
6
24
  def message
7
25
  response[:body]['message'] || super
8
26
  end
9
27
 
28
+ #
29
+ # Returns the HTTP response headers.
30
+ #
31
+ # @return [Hash] The response headers hash
32
+ #
10
33
  def headers
11
34
  response[:headers]
12
35
  end
13
36
 
37
+ #
38
+ # Returns detailed error information from the API response.
39
+ #
40
+ # The Strava API may return an 'errors' array in the response body
41
+ # containing detailed information about validation errors or other
42
+ # specific error conditions.
43
+ #
44
+ # @return [Array, nil] Array of error details, or nil if not present
45
+ #
14
46
  def errors
15
47
  response[:body]['errors']
16
48
  end
@@ -2,22 +2,57 @@
2
2
 
3
3
  module Strava
4
4
  module Errors
5
+ #
6
+ # Exception raised when API rate limits are exceeded.
7
+ #
8
+ # Strava enforces rate limits on API requests to prevent abuse. When your
9
+ # application exceeds these limits (either per 15 minutes or per day), the
10
+ # API returns a 429 Too Many Requests response, which triggers this exception.
11
+ #
12
+ # This error includes rate limit information extracted from the response headers,
13
+ # allowing you to determine current usage and when limits will reset.
14
+ #
15
+ # @see Strava::Api::Ratelimit
16
+ # @see https://developers.strava.com/docs/rate-limits/
17
+ #
5
18
  class RatelimitError < ::Faraday::ClientError
19
+ # @return [Strava::Api::Ratelimit] Rate limit information from response headers
6
20
  attr_reader :ratelimit
7
21
 
22
+ #
23
+ # Initialize a new rate limit error.
24
+ #
25
+ # @param env [Faraday::Env] The Faraday request environment
26
+ # @param response [Hash] The HTTP response hash
27
+ #
8
28
  def initialize(env, response)
9
29
  @ratelimit = Strava::Api::Ratelimit.new(env.response)
10
30
  super(response)
11
31
  end
12
32
 
33
+ #
34
+ # Returns the error message from the API response.
35
+ #
36
+ # @return [String] The error message
37
+ #
13
38
  def message
14
39
  response[:body]['message'] || super
15
40
  end
16
41
 
42
+ #
43
+ # Returns the HTTP response headers.
44
+ #
45
+ # @return [Hash] The response headers hash
46
+ #
17
47
  def headers
18
48
  response[:headers]
19
49
  end
20
50
 
51
+ #
52
+ # Returns detailed error information from the API response.
53
+ #
54
+ # @return [Array, nil] Array of error details, or nil if not present
55
+ #
21
56
  def errors
22
57
  response[:body]['errors']
23
58
  end
@@ -2,29 +2,84 @@
2
2
 
3
3
  module Strava
4
4
  module Errors
5
+ #
6
+ # Exception raised when an activity upload fails.
7
+ #
8
+ # When uploading activity files (FIT, TCX, GPX) to Strava, the API processes
9
+ # the file asynchronously and may encounter errors during processing. This
10
+ # exception is raised when the upload fails, and includes information about
11
+ # the upload status and error details.
12
+ #
13
+ # The error provides access to the upload model, which contains detailed
14
+ # information about what went wrong during the upload processing.
15
+ #
16
+ # @see Strava::Models::Upload
17
+ # @see https://developers.strava.com/docs/reference/#api-Uploads
18
+ #
5
19
  class UploadError < ::Faraday::ClientError
20
+ #
21
+ # Returns the HTTP response status code.
22
+ #
23
+ # @return [Integer] The HTTP status code
24
+ #
6
25
  def status
7
26
  response[:status]
8
27
  end
9
28
 
29
+ #
30
+ # Returns the HTTP response headers.
31
+ #
32
+ # @return [Hash] The response headers hash
33
+ #
10
34
  def headers
11
35
  response[:headers]
12
36
  end
13
37
 
38
+ #
39
+ # Returns the error message from the upload response.
40
+ #
41
+ # Extracts the error message from the response body's 'error' field,
42
+ # or falls back to the parent class message if not available.
43
+ #
44
+ # @return [String] The error message
45
+ #
14
46
  def message
15
47
  body[:error] || super
16
48
  end
17
49
 
50
+ #
51
+ # Returns the upload processing status from the response.
52
+ #
53
+ # The upload status indicates where in the processing pipeline the
54
+ # upload failed (e.g., 'error', 'processing', 'ready').
55
+ #
56
+ # @return [String] The upload status
57
+ #
18
58
  def error_status
19
59
  body[:status]
20
60
  end
21
61
 
62
+ #
63
+ # Returns the upload model containing detailed upload information.
64
+ #
65
+ # The upload model includes fields like id, external_id, error,
66
+ # status, and activity_id (if the upload succeeded before encountering
67
+ # an error).
68
+ #
69
+ # @return [Strava::Models::Upload] The upload model
70
+ #
22
71
  def upload
23
72
  @upload ||= Strava::Models::Upload.new(body)
24
73
  end
25
74
 
26
75
  private
27
76
 
77
+ #
78
+ # Returns the response body with indifferent access.
79
+ #
80
+ # @api private
81
+ # @return [HashWithIndifferentAccess] The response body hash
82
+ #
28
83
  def body
29
84
  (response[:body] || {}).with_indifferent_access
30
85
  end
data/lib/strava/logger.rb CHANGED
@@ -3,7 +3,34 @@
3
3
  require 'logger'
4
4
 
5
5
  module Strava
6
+ #
7
+ # Default logger for the Strava client library.
8
+ #
9
+ # Extends the standard Ruby Logger class and provides a default logger
10
+ # instance configured to output warnings and errors to STDOUT. The logger
11
+ # can be customized via the Web configuration to control HTTP request/response
12
+ # logging.
13
+ #
14
+ # @example Using the default logger
15
+ # Strava::Logger.logger.info('Making API request')
16
+ #
17
+ # @example Customizing the logger
18
+ # Strava::Web.configure do |config|
19
+ # config.logger = Logger.new('strava.log')
20
+ # end
21
+ #
22
+ # @see Logger
23
+ #
6
24
  class Logger < ::Logger
25
+ #
26
+ # Returns the default logger instance.
27
+ #
28
+ # Creates a memoized logger that outputs to STDOUT with a default
29
+ # log level of WARN. This logger is used by default for HTTP request
30
+ # logging unless overridden in the configuration.
31
+ #
32
+ # @return [Strava::Logger] The default logger instance
33
+ #
7
34
  def self.logger
8
35
  @logger ||= begin
9
36
  logger = new $stdout