ruby_garmin_connect 0.2.0 → 0.2.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/README.md +4 -2
- data/lib/garmin_connect/api/metrics.rb +1 -1
- data/lib/garmin_connect/client.rb +22 -3
- data/lib/garmin_connect/connection.rb +15 -3
- data/lib/garmin_connect/errors.rb +3 -0
- data/lib/garmin_connect/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9e992aa41910f85ae0a2f2b656e09d28c529a82c44a0118a09a53765826d49ce
|
|
4
|
+
data.tar.gz: ee0d8526867d3f7d85c2742cfaa3ad36a73102d43be6eba2149b687719365885
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d20db74f11c8102571dd3c08a3ffeec7e095075de9562035adbb7255d7d75a134d879e075010897557e143a2f28ab6392da82ecbedd2e86ffbebced25ae3e0ca
|
|
7
|
+
data.tar.gz: 8baf66019a7832c3d1579c9b164f01beef5a3271b91357120572b345cf7da0b71f331b7f295533bfcb8342b641794d6dc6cc63ae4ece8159d8efe9814a34c4b9
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.1 (2026-02-11)
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- `training_readiness` raising `TypeError: no implicit conversion of String into Integer` when Garmin returns a response with a UTF-8 BOM or unparseable JSON body
|
|
8
|
+
- `display_name` and `full_name` returning `nil` on login when the profile endpoint nests data under `socialProfile` or uses `userName` instead of `displayName`
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `ParseError` exception class — raised when a response claims `application/json` content-type but the body can't be parsed (instead of silently returning a raw string)
|
|
13
|
+
- UTF-8 BOM stripping in response parsing
|
|
14
|
+
- Fallback profile extraction: tries `displayName` → `socialProfile.displayName` → `userName`
|
|
15
|
+
|
|
16
|
+
### Improved
|
|
17
|
+
|
|
18
|
+
- Test coverage increased to 205 examples (up from 199)
|
|
19
|
+
|
|
3
20
|
## 0.2.0 (2026-02-11)
|
|
4
21
|
|
|
5
22
|
### Added
|
data/README.md
CHANGED
|
@@ -24,10 +24,12 @@ gem install garmin_connect
|
|
|
24
24
|
require "garmin_connect"
|
|
25
25
|
|
|
26
26
|
# Login with credentials (tokens are saved to ~/.garminconnect automatically)
|
|
27
|
-
client = GarminConnect.
|
|
27
|
+
client = GarminConnect::Client.new(email: "you@example.com", password: "your-password")
|
|
28
|
+
client.login
|
|
28
29
|
|
|
29
30
|
# Subsequent sessions resume from saved tokens (no re-login for ~1 year)
|
|
30
|
-
client = GarminConnect.
|
|
31
|
+
client = GarminConnect::Client.new
|
|
32
|
+
client.login
|
|
31
33
|
|
|
32
34
|
# Get today's stats
|
|
33
35
|
puts client.daily_summary
|
|
@@ -23,7 +23,7 @@ module GarminConnect
|
|
|
23
23
|
data = training_readiness(date)
|
|
24
24
|
return data unless data.is_a?(Array)
|
|
25
25
|
|
|
26
|
-
data.select { |entry| entry["calendarDate"] == format_date(date) }
|
|
26
|
+
data.select { |entry| entry.is_a?(Hash) && entry["calendarDate"] == format_date(date) }
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
# Get aggregated training status.
|
|
@@ -162,13 +162,32 @@ module GarminConnect
|
|
|
162
162
|
@unit_system = settings&.dig("userData", "measurementSystem")
|
|
163
163
|
|
|
164
164
|
profile = connection.get("/userprofile-service/userprofile/profile")
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
@user_profile_pk = profile
|
|
165
|
+
extract_profile_info(profile)
|
|
166
|
+
|
|
167
|
+
@user_profile_pk = extract_profile_pk(profile, settings)
|
|
168
168
|
rescue HTTPError
|
|
169
169
|
# Non-fatal: display_name may not be available
|
|
170
170
|
end
|
|
171
171
|
|
|
172
|
+
def extract_profile_info(profile)
|
|
173
|
+
return unless profile.is_a?(Hash)
|
|
174
|
+
|
|
175
|
+
@display_name = profile.dig("displayName") ||
|
|
176
|
+
profile.dig("socialProfile", "displayName") ||
|
|
177
|
+
profile.dig("userName")
|
|
178
|
+
|
|
179
|
+
@full_name = profile.dig("fullName") ||
|
|
180
|
+
profile.dig("socialProfile", "fullName")
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def extract_profile_pk(profile, settings)
|
|
184
|
+
return nil unless profile.is_a?(Hash) || settings.is_a?(Hash)
|
|
185
|
+
|
|
186
|
+
profile&.dig("profileId") ||
|
|
187
|
+
profile&.dig("socialProfile", "profileId") ||
|
|
188
|
+
settings&.dig("id")
|
|
189
|
+
end
|
|
190
|
+
|
|
172
191
|
# Exposed for API modules that need it.
|
|
173
192
|
def user_profile_pk
|
|
174
193
|
@user_profile_pk
|
|
@@ -124,11 +124,23 @@ module GarminConnect
|
|
|
124
124
|
|
|
125
125
|
def parse_response(resp)
|
|
126
126
|
return nil if resp.status == 204
|
|
127
|
-
return resp.body if resp.body.nil? || resp.body.empty?
|
|
128
127
|
|
|
129
|
-
|
|
128
|
+
body = resp.body
|
|
129
|
+
return body if body.nil? || body.empty?
|
|
130
|
+
|
|
131
|
+
# Strip UTF-8 BOM if present (some Garmin endpoints include it)
|
|
132
|
+
body = body.b.sub(/\A\xEF\xBB\xBF/n, "").force_encoding("UTF-8")
|
|
133
|
+
|
|
134
|
+
JSON.parse(body)
|
|
130
135
|
rescue JSON::ParserError
|
|
131
|
-
resp.
|
|
136
|
+
content_type = resp.headers["content-type"].to_s
|
|
137
|
+
# If the server said it was JSON but we can't parse it, raise rather than
|
|
138
|
+
# returning a raw string that callers will misuse.
|
|
139
|
+
if content_type.include?("application/json")
|
|
140
|
+
raise ParseError, "Failed to parse JSON response: #{body[0..200]}"
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
body
|
|
132
144
|
end
|
|
133
145
|
|
|
134
146
|
def handle_errors!(resp)
|
|
@@ -35,6 +35,9 @@ module GarminConnect
|
|
|
35
35
|
class TooManyRequestsError < HTTPError; end
|
|
36
36
|
class ServerError < HTTPError; end
|
|
37
37
|
|
|
38
|
+
# Response parsing errors
|
|
39
|
+
class ParseError < Error; end
|
|
40
|
+
|
|
38
41
|
# Maps HTTP status codes to error classes
|
|
39
42
|
HTTP_ERRORS = {
|
|
40
43
|
400 => BadRequestError,
|