mirror of
https://github.com/sstent/go-garth.git
synced 2025-12-05 23:51:42 +00:00
630 lines
19 KiB
Markdown
630 lines
19 KiB
Markdown
# High Priority Endpoints Implementation Guide
|
|
|
|
## Overview
|
|
This guide covers implementing the most commonly requested Garmin Connect API endpoints that are currently missing from your codebase. We'll focus on the high-priority endpoints that provide detailed health and fitness data.
|
|
|
|
## 1. Detailed Sleep Data Implementation
|
|
|
|
### Files to Create/Modify
|
|
|
|
#### A. Create `internal/data/sleep_detailed.go`
|
|
```go
|
|
package data
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"go-garth/internal/api/client"
|
|
"go-garth/internal/types"
|
|
)
|
|
|
|
// SleepLevel represents different sleep stages
|
|
type SleepLevel struct {
|
|
StartGMT time.Time `json:"startGmt"`
|
|
EndGMT time.Time `json:"endGmt"`
|
|
ActivityLevel float64 `json:"activityLevel"`
|
|
SleepLevel string `json:"sleepLevel"` // "deep", "light", "rem", "awake"
|
|
}
|
|
|
|
// SleepMovement represents movement during sleep
|
|
type SleepMovement struct {
|
|
StartGMT time.Time `json:"startGmt"`
|
|
EndGMT time.Time `json:"endGmt"`
|
|
ActivityLevel float64 `json:"activityLevel"`
|
|
}
|
|
|
|
// SleepScore represents detailed sleep scoring
|
|
type SleepScore struct {
|
|
Overall int `json:"overall"`
|
|
Composition SleepScoreBreakdown `json:"composition"`
|
|
Revitalization SleepScoreBreakdown `json:"revitalization"`
|
|
Duration SleepScoreBreakdown `json:"duration"`
|
|
DeepPercentage float64 `json:"deepPercentage"`
|
|
LightPercentage float64 `json:"lightPercentage"`
|
|
RemPercentage float64 `json:"remPercentage"`
|
|
RestfulnessValue float64 `json:"restfulnessValue"`
|
|
}
|
|
|
|
type SleepScoreBreakdown struct {
|
|
QualifierKey string `json:"qualifierKey"`
|
|
OptimalStart float64 `json:"optimalStart"`
|
|
OptimalEnd float64 `json:"optimalEnd"`
|
|
Value float64 `json:"value"`
|
|
IdealStartSecs *int `json:"idealStartInSeconds"`
|
|
IdealEndSecs *int `json:"idealEndInSeconds"`
|
|
}
|
|
|
|
// DetailedSleepData represents comprehensive sleep data
|
|
type DetailedSleepData struct {
|
|
UserProfilePK int `json:"userProfilePk"`
|
|
CalendarDate time.Time `json:"calendarDate"`
|
|
SleepStartTimestampGMT time.Time `json:"sleepStartTimestampGmt"`
|
|
SleepEndTimestampGMT time.Time `json:"sleepEndTimestampGmt"`
|
|
SleepStartTimestampLocal time.Time `json:"sleepStartTimestampLocal"`
|
|
SleepEndTimestampLocal time.Time `json:"sleepEndTimestampLocal"`
|
|
UnmeasurableSleepSeconds int `json:"unmeasurableSleepSeconds"`
|
|
DeepSleepSeconds int `json:"deepSleepSeconds"`
|
|
LightSleepSeconds int `json:"lightSleepSeconds"`
|
|
RemSleepSeconds int `json:"remSleepSeconds"`
|
|
AwakeSleepSeconds int `json:"awakeSleepSeconds"`
|
|
DeviceRemCapable bool `json:"deviceRemCapable"`
|
|
SleepLevels []SleepLevel `json:"sleepLevels"`
|
|
SleepMovement []SleepMovement `json:"sleepMovement"`
|
|
SleepScores *SleepScore `json:"sleepScores"`
|
|
AverageSpO2Value *float64 `json:"averageSpO2Value"`
|
|
LowestSpO2Value *int `json:"lowestSpO2Value"`
|
|
HighestSpO2Value *int `json:"highestSpO2Value"`
|
|
AverageRespirationValue *float64 `json:"averageRespirationValue"`
|
|
LowestRespirationValue *float64 `json:"lowestRespirationValue"`
|
|
HighestRespirationValue *float64 `json:"highestRespirationValue"`
|
|
AvgSleepStress *float64 `json:"avgSleepStress"`
|
|
BaseData
|
|
}
|
|
|
|
// NewDetailedSleepData creates a new DetailedSleepData instance
|
|
func NewDetailedSleepData() *DetailedSleepData {
|
|
sleep := &DetailedSleepData{}
|
|
sleep.GetFunc = sleep.get
|
|
return sleep
|
|
}
|
|
|
|
func (d *DetailedSleepData) get(day time.Time, client *client.Client) (interface{}, error) {
|
|
dateStr := day.Format("2006-01-02")
|
|
path := fmt.Sprintf("/wellness-service/wellness/dailySleepData/%s?date=%s&nonSleepBufferMinutes=60",
|
|
client.Username, dateStr)
|
|
|
|
data, err := client.ConnectAPI(path, "GET", nil, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get detailed sleep data: %w", err)
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
var response struct {
|
|
DailySleepDTO *DetailedSleepData `json:"dailySleepDTO"`
|
|
SleepMovement []SleepMovement `json:"sleepMovement"`
|
|
RemSleepData bool `json:"remSleepData"`
|
|
SleepLevels []SleepLevel `json:"sleepLevels"`
|
|
SleepRestlessMoments []interface{} `json:"sleepRestlessMoments"`
|
|
RestlessMomentsCount int `json:"restlessMomentsCount"`
|
|
WellnessSpO2SleepSummaryDTO interface{} `json:"wellnessSpO2SleepSummaryDTO"`
|
|
WellnessEpochSPO2DataDTOList []interface{} `json:"wellnessEpochSPO2DataDTOList"`
|
|
WellnessEpochRespirationDataDTOList []interface{} `json:"wellnessEpochRespirationDataDTOList"`
|
|
SleepStress interface{} `json:"sleepStress"`
|
|
}
|
|
|
|
if err := json.Unmarshal(data, &response); err != nil {
|
|
return nil, fmt.Errorf("failed to parse detailed sleep response: %w", err)
|
|
}
|
|
|
|
if response.DailySleepDTO == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
// Populate additional data
|
|
response.DailySleepDTO.SleepMovement = response.SleepMovement
|
|
response.DailySleepDTO.SleepLevels = response.SleepLevels
|
|
|
|
return response.DailySleepDTO, nil
|
|
}
|
|
|
|
// GetSleepEfficiency calculates sleep efficiency percentage
|
|
func (d *DetailedSleepData) GetSleepEfficiency() float64 {
|
|
totalTime := d.SleepEndTimestampGMT.Sub(d.SleepStartTimestampGMT).Seconds()
|
|
sleepTime := float64(d.DeepSleepSeconds + d.LightSleepSeconds + d.RemSleepSeconds)
|
|
if totalTime == 0 {
|
|
return 0
|
|
}
|
|
return (sleepTime / totalTime) * 100
|
|
}
|
|
|
|
// GetTotalSleepTime returns total sleep time in hours
|
|
func (d *DetailedSleepData) GetTotalSleepTime() float64 {
|
|
totalSeconds := d.DeepSleepSeconds + d.LightSleepSeconds + d.RemSleepSeconds
|
|
return float64(totalSeconds) / 3600.0
|
|
}
|
|
```
|
|
|
|
#### B. Add methods to `internal/api/client/client.go`
|
|
```go
|
|
// GetDetailedSleepData retrieves comprehensive sleep data for a date
|
|
func (c *Client) GetDetailedSleepData(date time.Time) (*types.DetailedSleepData, error) {
|
|
sleepData := data.NewDetailedSleepData()
|
|
result, err := sleepData.Get(date, c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if result == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
detailedSleep, ok := result.(*types.DetailedSleepData)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unexpected sleep data type")
|
|
}
|
|
|
|
return detailedSleep, nil
|
|
}
|
|
```
|
|
|
|
## 2. Heart Rate Variability (HRV) Implementation
|
|
|
|
#### A. Update `internal/data/hrv.go` (extend existing)
|
|
Add these methods to your existing HRV implementation:
|
|
|
|
```go
|
|
// HRVStatus represents HRV status and baseline
|
|
type HRVStatus struct {
|
|
Status string `json:"status"` // "BALANCED", "UNBALANCED", "POOR"
|
|
FeedbackPhrase string `json:"feedbackPhrase"`
|
|
BaselineLowUpper int `json:"baselineLowUpper"`
|
|
BalancedLow int `json:"balancedLow"`
|
|
BalancedUpper int `json:"balancedUpper"`
|
|
MarkerValue float64 `json:"markerValue"`
|
|
}
|
|
|
|
// DailyHRVData represents comprehensive daily HRV data
|
|
type DailyHRVData struct {
|
|
UserProfilePK int `json:"userProfilePk"`
|
|
CalendarDate time.Time `json:"calendarDate"`
|
|
WeeklyAvg *float64 `json:"weeklyAvg"`
|
|
LastNightAvg *float64 `json:"lastNightAvg"`
|
|
LastNight5MinHigh *float64 `json:"lastNight5MinHigh"`
|
|
Baseline HRVBaseline `json:"baseline"`
|
|
Status string `json:"status"`
|
|
FeedbackPhrase string `json:"feedbackPhrase"`
|
|
CreateTimeStamp time.Time `json:"createTimeStamp"`
|
|
HRVReadings []HRVReading `json:"hrvReadings"`
|
|
StartTimestampGMT time.Time `json:"startTimestampGmt"`
|
|
EndTimestampGMT time.Time `json:"endTimestampGmt"`
|
|
StartTimestampLocal time.Time `json:"startTimestampLocal"`
|
|
EndTimestampLocal time.Time `json:"endTimestampLocal"`
|
|
SleepStartTimestampGMT time.Time `json:"sleepStartTimestampGmt"`
|
|
SleepEndTimestampGMT time.Time `json:"sleepEndTimestampGmt"`
|
|
BaseData
|
|
}
|
|
|
|
type HRVBaseline struct {
|
|
LowUpper int `json:"lowUpper"`
|
|
BalancedLow int `json:"balancedLow"`
|
|
BalancedUpper int `json:"balancedUpper"`
|
|
MarkerValue float64 `json:"markerValue"`
|
|
}
|
|
|
|
// Update the existing get method in hrv.go
|
|
func (h *DailyHRVData) get(day time.Time, client *client.Client) (interface{}, error) {
|
|
dateStr := day.Format("2006-01-02")
|
|
path := fmt.Sprintf("/wellness-service/wellness/dailyHrvData/%s?date=%s",
|
|
client.Username, dateStr)
|
|
|
|
data, err := client.ConnectAPI(path, "GET", nil, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get HRV data: %w", err)
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
var response struct {
|
|
HRVSummary DailyHRVData `json:"hrvSummary"`
|
|
HRVReadings []HRVReading `json:"hrvReadings"`
|
|
}
|
|
|
|
if err := json.Unmarshal(data, &response); err != nil {
|
|
return nil, fmt.Errorf("failed to parse HRV response: %w", err)
|
|
}
|
|
|
|
// Combine summary and readings
|
|
response.HRVSummary.HRVReadings = response.HRVReadings
|
|
return &response.HRVSummary, nil
|
|
}
|
|
```
|
|
|
|
## 3. Body Battery Detailed Implementation
|
|
|
|
#### A. Update `internal/data/body_battery.go`
|
|
Add these structures and methods:
|
|
|
|
```go
|
|
// BodyBatteryEvent represents events that impact Body Battery
|
|
type BodyBatteryEvent struct {
|
|
EventType string `json:"eventType"` // "sleep", "activity", "stress"
|
|
EventStartTimeGMT time.Time `json:"eventStartTimeGmt"`
|
|
TimezoneOffset int `json:"timezoneOffset"`
|
|
DurationInMilliseconds int `json:"durationInMilliseconds"`
|
|
BodyBatteryImpact int `json:"bodyBatteryImpact"`
|
|
FeedbackType string `json:"feedbackType"`
|
|
ShortFeedback string `json:"shortFeedback"`
|
|
}
|
|
|
|
// DetailedBodyBatteryData represents comprehensive Body Battery data
|
|
type DetailedBodyBatteryData struct {
|
|
UserProfilePK int `json:"userProfilePk"`
|
|
CalendarDate time.Time `json:"calendarDate"`
|
|
StartTimestampGMT time.Time `json:"startTimestampGmt"`
|
|
EndTimestampGMT time.Time `json:"endTimestampGmt"`
|
|
StartTimestampLocal time.Time `json:"startTimestampLocal"`
|
|
EndTimestampLocal time.Time `json:"endTimestampLocal"`
|
|
MaxStressLevel int `json:"maxStressLevel"`
|
|
AvgStressLevel int `json:"avgStressLevel"`
|
|
BodyBatteryValuesArray [][]interface{} `json:"bodyBatteryValuesArray"`
|
|
StressValuesArray [][]int `json:"stressValuesArray"`
|
|
Events []BodyBatteryEvent `json:"bodyBatteryEvents"`
|
|
BaseData
|
|
}
|
|
|
|
func NewDetailedBodyBatteryData() *DetailedBodyBatteryData {
|
|
bb := &DetailedBodyBatteryData{}
|
|
bb.GetFunc = bb.get
|
|
return bb
|
|
}
|
|
|
|
func (d *DetailedBodyBatteryData) get(day time.Time, client *client.Client) (interface{}, error) {
|
|
dateStr := day.Format("2006-01-02")
|
|
|
|
// Get main Body Battery data
|
|
path1 := fmt.Sprintf("/wellness-service/wellness/dailyStress/%s", dateStr)
|
|
data1, err := client.ConnectAPI(path1, "GET", nil, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get Body Battery stress data: %w", err)
|
|
}
|
|
|
|
// Get Body Battery events
|
|
path2 := fmt.Sprintf("/wellness-service/wellness/bodyBattery/%s", dateStr)
|
|
data2, err := client.ConnectAPI(path2, "GET", nil, nil)
|
|
if err != nil {
|
|
// Events might not be available, continue without them
|
|
data2 = []byte("[]")
|
|
}
|
|
|
|
var result DetailedBodyBatteryData
|
|
if len(data1) > 0 {
|
|
if err := json.Unmarshal(data1, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse Body Battery data: %w", err)
|
|
}
|
|
}
|
|
|
|
var events []BodyBatteryEvent
|
|
if len(data2) > 0 {
|
|
if err := json.Unmarshal(data2, &events); err == nil {
|
|
result.Events = events
|
|
}
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
// GetCurrentLevel returns the most recent Body Battery level
|
|
func (d *DetailedBodyBatteryData) GetCurrentLevel() int {
|
|
if len(d.BodyBatteryValuesArray) == 0 {
|
|
return 0
|
|
}
|
|
|
|
readings := ParseBodyBatteryReadings(d.BodyBatteryValuesArray)
|
|
if len(readings) == 0 {
|
|
return 0
|
|
}
|
|
|
|
return readings[len(readings)-1].Level
|
|
}
|
|
|
|
// GetDayChange returns the Body Battery change for the day
|
|
func (d *DetailedBodyBatteryData) GetDayChange() int {
|
|
readings := ParseBodyBatteryReadings(d.BodyBatteryValuesArray)
|
|
if len(readings) < 2 {
|
|
return 0
|
|
}
|
|
|
|
return readings[len(readings)-1].Level - readings[0].Level
|
|
}
|
|
```
|
|
|
|
## 4. Training Status & Load Implementation
|
|
|
|
#### A. Create `internal/data/training.go`
|
|
```go
|
|
package data
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"time"
|
|
|
|
"go-garth/internal/api/client"
|
|
)
|
|
|
|
// TrainingStatus represents current training status
|
|
type TrainingStatus struct {
|
|
CalendarDate time.Time `json:"calendarDate"`
|
|
TrainingStatusKey string `json:"trainingStatusKey"` // "DETRAINING", "RECOVERY", "MAINTAINING", "PRODUCTIVE", "PEAKING", "OVERREACHING", "UNPRODUCTIVE", "NONE"
|
|
TrainingStatusTypeKey string `json:"trainingStatusTypeKey"`
|
|
TrainingStatusValue int `json:"trainingStatusValue"`
|
|
LoadRatio float64 `json:"loadRatio"`
|
|
BaseData
|
|
}
|
|
|
|
// TrainingLoad represents training load data
|
|
type TrainingLoad struct {
|
|
CalendarDate time.Time `json:"calendarDate"`
|
|
AcuteTrainingLoad float64 `json:"acuteTrainingLoad"`
|
|
ChronicTrainingLoad float64 `json:"chronicTrainingLoad"`
|
|
TrainingLoadRatio float64 `json:"trainingLoadRatio"`
|
|
TrainingEffectAerobic float64 `json:"trainingEffectAerobic"`
|
|
TrainingEffectAnaerobic float64 `json:"trainingEffectAnaerobic"`
|
|
BaseData
|
|
}
|
|
|
|
// FitnessAge represents fitness age calculation
|
|
type FitnessAge struct {
|
|
FitnessAge int `json:"fitnessAge"`
|
|
ChronologicalAge int `json:"chronologicalAge"`
|
|
VO2MaxRunning float64 `json:"vo2MaxRunning"`
|
|
LastUpdated time.Time `json:"lastUpdated"`
|
|
}
|
|
|
|
func NewTrainingStatus() *TrainingStatus {
|
|
ts := &TrainingStatus{}
|
|
ts.GetFunc = ts.get
|
|
return ts
|
|
}
|
|
|
|
func (t *TrainingStatus) get(day time.Time, client *client.Client) (interface{}, error) {
|
|
dateStr := day.Format("2006-01-02")
|
|
path := fmt.Sprintf("/metrics-service/metrics/trainingStatus/%s", dateStr)
|
|
|
|
data, err := client.ConnectAPI(path, "GET", nil, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get training status: %w", err)
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
var result TrainingStatus
|
|
if err := json.Unmarshal(data, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse training status: %w", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
func NewTrainingLoad() *TrainingLoad {
|
|
tl := &TrainingLoad{}
|
|
tl.GetFunc = tl.get
|
|
return tl
|
|
}
|
|
|
|
func (t *TrainingLoad) get(day time.Time, client *client.Client) (interface{}, error) {
|
|
dateStr := day.Format("2006-01-02")
|
|
endDate := day.AddDate(0, 0, 6).Format("2006-01-02") // Get week of data
|
|
path := fmt.Sprintf("/metrics-service/metrics/trainingLoad/%s/%s", dateStr, endDate)
|
|
|
|
data, err := client.ConnectAPI(path, "GET", nil, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get training load: %w", err)
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
var results []TrainingLoad
|
|
if err := json.Unmarshal(data, &results); err != nil {
|
|
return nil, fmt.Errorf("failed to parse training load: %w", err)
|
|
}
|
|
|
|
if len(results) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
return &results[0], nil
|
|
}
|
|
```
|
|
|
|
## 5. Client Methods Integration
|
|
|
|
#### Add these methods to `internal/api/client/client.go`:
|
|
|
|
```go
|
|
// GetTrainingStatus retrieves current training status
|
|
func (c *Client) GetTrainingStatus(date time.Time) (*types.TrainingStatus, error) {
|
|
trainingStatus := data.NewTrainingStatus()
|
|
result, err := trainingStatus.Get(date, c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if result == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
status, ok := result.(*types.TrainingStatus)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unexpected training status type")
|
|
}
|
|
|
|
return status, nil
|
|
}
|
|
|
|
// GetTrainingLoad retrieves training load data
|
|
func (c *Client) GetTrainingLoad(date time.Time) (*types.TrainingLoad, error) {
|
|
trainingLoad := data.NewTrainingLoad()
|
|
result, err := trainingLoad.Get(date, c)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if result == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
load, ok := result.(*types.TrainingLoad)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unexpected training load type")
|
|
}
|
|
|
|
return load, nil
|
|
}
|
|
|
|
// GetFitnessAge retrieves fitness age calculation
|
|
func (c *Client) GetFitnessAge() (*types.FitnessAge, error) {
|
|
path := "/fitness-service/fitness/fitnessAge"
|
|
|
|
data, err := c.ConnectAPI(path, "GET", nil, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get fitness age: %w", err)
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
var fitnessAge types.FitnessAge
|
|
if err := json.Unmarshal(data, &fitnessAge); err != nil {
|
|
return nil, fmt.Errorf("failed to parse fitness age: %w", err)
|
|
}
|
|
|
|
fitnessAge.LastUpdated = time.Now()
|
|
return &fitnessAge, nil
|
|
}
|
|
```
|
|
|
|
## Implementation Steps
|
|
|
|
### Phase 1: Sleep Data (Week 1)
|
|
1. Create `internal/data/sleep_detailed.go`
|
|
2. Update `internal/types/garmin.go` with sleep types
|
|
3. Add client methods
|
|
4. Create tests
|
|
5. Test with real data
|
|
|
|
### Phase 2: HRV Enhancement (Week 2)
|
|
1. Update existing `internal/data/hrv.go`
|
|
2. Add new HRV types to types file
|
|
3. Enhance client methods
|
|
4. Create comprehensive tests
|
|
|
|
### Phase 3: Body Battery Details (Week 3)
|
|
1. Update `internal/data/body_battery.go`
|
|
2. Add event tracking
|
|
3. Add convenience methods
|
|
4. Create tests
|
|
|
|
### Phase 4: Training Metrics (Week 4)
|
|
1. Create `internal/data/training.go`
|
|
2. Add training types
|
|
3. Implement client methods
|
|
4. Create tests and validation
|
|
|
|
## Testing Strategy
|
|
|
|
Create test files for each new data type:
|
|
|
|
```go
|
|
// Example test structure
|
|
func TestDetailedSleepData_Get(t *testing.T) {
|
|
// Mock response from API
|
|
mockResponse := `{
|
|
"dailySleepDTO": {
|
|
"userProfilePk": 12345,
|
|
"calendarDate": "2023-06-15",
|
|
"deepSleepSeconds": 7200,
|
|
"lightSleepSeconds": 14400,
|
|
"remSleepSeconds": 3600,
|
|
"awakeSleepSeconds": 1800
|
|
},
|
|
"sleepMovement": [],
|
|
"sleepLevels": []
|
|
}`
|
|
|
|
// Create mock client
|
|
server := testutils.MockJSONResponse(http.StatusOK, mockResponse)
|
|
defer server.Close()
|
|
|
|
// Test implementation
|
|
// ... test logic
|
|
}
|
|
```
|
|
|
|
## Error Handling Patterns
|
|
|
|
For each endpoint, implement consistent error handling:
|
|
|
|
```go
|
|
func (d *DataType) get(day time.Time, client *client.Client) (interface{}, error) {
|
|
data, err := client.ConnectAPI(path, "GET", nil, nil)
|
|
if err != nil {
|
|
// Log the error but don't fail completely
|
|
fmt.Printf("Warning: Failed to get %s data: %v\n", "datatype", err)
|
|
return nil, nil // Return nil data, not error for missing data
|
|
}
|
|
|
|
if len(data) == 0 {
|
|
return nil, nil // No data available
|
|
}
|
|
|
|
// Parse and validate
|
|
var result DataType
|
|
if err := json.Unmarshal(data, &result); err != nil {
|
|
return nil, fmt.Errorf("failed to parse %s data: %w", "datatype", err)
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
```
|
|
|
|
## Usage Examples
|
|
|
|
After implementation, users can access the data like this:
|
|
|
|
```go
|
|
// Get detailed sleep data
|
|
sleepData, err := client.GetDetailedSleepData(time.Now().AddDate(0, 0, -1))
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if sleepData != nil {
|
|
fmt.Printf("Sleep efficiency: %.1f%%\n", sleepData.GetSleepEfficiency())
|
|
fmt.Printf("Total sleep: %.1f hours\n", sleepData.GetTotalSleepTime())
|
|
}
|
|
|
|
// Get training status
|
|
status, err := client.GetTrainingStatus(time.Now())
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
if status != nil {
|
|
fmt.Printf("Training Status: %s\n", status.TrainingStatusKey)
|
|
fmt.Printf("Load Ratio: %.2f\n", status.LoadRatio)
|
|
}
|
|
```
|
|
|
|
This implementation guide provides a comprehensive foundation for adding the most requested Garmin Connect API endpoints to your Go client.
|