Skip to content

Commit

Permalink
feat(hass): ✨ add support to allow some requests to be retried
Browse files Browse the repository at this point in the history
- fix default request retry logic to retry requests with 429 responses
- add ability to specify a retry flag for a request, which will retry the request on any response error using a exponential backoff mechanism
- add support for specifying retry flag for sensors and events
  • Loading branch information
joshuar committed Dec 21, 2024
1 parent 401fe01 commit b103679
Show file tree
Hide file tree
Showing 5 changed files with 52 additions and 2 deletions.
20 changes: 20 additions & 0 deletions internal/hass/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const (
var (
client *resty.Client

// defaultRetryFunc defines how we retry requests. By default, requests are
// only retried when Home Assistant responds with 429.
defaultRetryFunc = func(r *resty.Response, _ error) bool {
return r.StatusCode() == http.StatusTooManyRequests
}
Expand All @@ -32,11 +34,15 @@ var (
func init() {
client = resty.New().
SetTimeout(defaultTimeout).
SetRetryCount(3).
SetRetryWaitTime(5 * time.Second).
SetRetryMaxWaitTime(20 * time.Second).
AddRetryCondition(defaultRetryFunc)
}

type Request interface {
RequestBody() any
Retry() bool
}

// Authenticated represents a request that requires passing an authentication
Expand Down Expand Up @@ -95,6 +101,20 @@ func Send[T any](ctx context.Context, url string, details Request) (T, error) {
}
}

if details.Retry() {
// If request needs to be retried, retry the request on any error.
logging.FromContext(ctx).Debug("Will retry requests.", slog.Any("body", details))
requestClient = requestClient.AddRetryCondition(
func(r *resty.Response, err error) bool {
if err != nil {
logging.FromContext(ctx).Debug("Retrying request.", slog.Any("body", details))
return true
}
return false
},
)
}

requestClient.SetBody(details.RequestBody())
responseObj, err := requestClient.Post(url)

Expand Down
4 changes: 4 additions & 0 deletions internal/hass/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,7 @@ func (c *configRequest) RequestBody() any {
Type: "get_config",
}
}

func (c *configRequest) Retry() bool {
return false
}
9 changes: 7 additions & 2 deletions internal/hass/event/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ const (
)

type Event struct {
EventData any `json:"event_data" validate:"required"`
EventType string `json:"event_type" validate:"required"`
EventData any `json:"event_data" validate:"required"`
EventType string `json:"event_type" validate:"required"`
RetryRequest bool
}

func (e *Event) Validate() error {
Expand All @@ -37,3 +38,7 @@ func (e *Event) RequestBody() any {
Data: e,
}
}

func (e *Event) Retry() bool {
return e.RetryRequest
}
4 changes: 4 additions & 0 deletions internal/hass/registration.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ func (r *registrationRequest) RequestBody() any {
return r.Device
}

func (r *registrationRequest) Retry() bool {
return true
}

func newRegistrationRequest(thisDevice *device.Device, token string) *registrationRequest {
return &registrationRequest{
Device: thisDevice,
Expand Down
17 changes: 17 additions & 0 deletions internal/hass/sensor/entities.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,17 @@ type Request struct {
RequestType string `json:"type"`
}

type RequestMetadata struct {
RetryRequest bool
}

type State struct {
Value any `json:"state" validate:"required"`
Attributes map[string]any `json:"attributes,omitempty" validate:"omitempty"`
Icon string `json:"icon,omitempty" validate:"omitempty,startswith=mdi:"`
ID string `json:"unique_id" validate:"required"`
EntityType types.SensorType `json:"type" validate:"omitempty"`
RequestMetadata
}

func (s *State) Validate() error {
Expand All @@ -49,6 +54,10 @@ func (s *State) RequestBody() any {
}
}

func (s *State) Retry() bool {
return s.RetryRequest
}

//nolint:wrapcheck
func (s *State) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Expand Down Expand Up @@ -91,6 +100,10 @@ func (e *Entity) RequestBody() any {
}
}

func (e *Entity) Retry() bool {
return e.RetryRequest
}

//nolint:wrapcheck
func (e *Entity) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
Expand Down Expand Up @@ -147,3 +160,7 @@ func (l *Location) RequestBody() any {
Data: l,
}
}

func (l *Location) Retry() bool {
return false
}

0 comments on commit b103679

Please sign in to comment.