mirror of
https://github.com/kerberos-io/onvif.git
synced 2026-08-23 15:08:33 +00:00
Replaces the bare fmt.Errorf wrappers on the Errors channel with three
typed errors and adds an Event.AfterReconnect flag so consumers can
distinguish post-recreate replay events from live ones.
Typed errors
------------
ErrPullFailed, ErrRenewFailed, ErrRecreateFailed all implement
Unwrap() and Op() Op. Consumers can branch with errors.As without
parsing strings:
var pull ErrPullFailed
if errors.As(e, &pull) { /* transient; logged */ }
var recreate ErrRecreateFailed
if errors.As(e, &recreate) { /* alert: camera may be offline */ }
Op() returns OpPull / OpRenew / OpRecreate for cases where the caller
wants to log the operation name without unwrapping. Both addressed
the review's 'highest-leverage v1 change' concern about bare error on
the Errors channel.
AfterReconnect observability
----------------------------
ONVIF cameras replay each property's current value with
PropertyInitialized whenever a new pull-point subscription is
established (per the Event Service spec). A consumer doing edge
detection on motion = StateActive would otherwise see a phantom
'motion started' for every active property after every reconnect.
The pull loop now tracks an afterReconnect flag local to the
goroutine: set to true when attemptRecreate returns justRecreated,
applied to every emitted event, cleared on the first non-Initialized
event we see. This bounds the replay window naturally — once the
camera has finished sending current state, the next event tells us
we're live.
attemptRecreate now returns (justRecreated, cont) so the pull loop
knows whether the just-completed recreate succeeded vs. the call
returning due to ctx-cancel during backoff.
Test coverage
-------------
* errors_test.go: typed-error Unwrap/Op assertions plus
Stream-level proof that pull and recreate failures arrive on the
Errors channel wearing the right type.
* AfterReconnect flag: drives the stream through a failure, observes
the next event carries the flag and the one after does not.
43 lines
1.7 KiB
Go
43 lines
1.7 KiB
Go
package stream
|
|
|
|
import "fmt"
|
|
|
|
// Op identifies which Stream operation failed. Used by ErrPullFailed,
|
|
// ErrRenewFailed and ErrRecreateFailed so consumers can branch with
|
|
// errors.As without parsing the wrapped message.
|
|
type Op string
|
|
|
|
const (
|
|
OpPull Op = "pull"
|
|
OpRenew Op = "renew"
|
|
OpRecreate Op = "recreate"
|
|
)
|
|
|
|
// ErrPullFailed wraps a transient PullMessages failure. The pull loop
|
|
// surfaces it on the Errors channel and continues. Consumers can match
|
|
// with errors.As(err, &stream.ErrPullFailed{}) — see
|
|
// TestErrors_TypedAssertion.
|
|
type ErrPullFailed struct{ Err error }
|
|
|
|
func (e ErrPullFailed) Error() string { return fmt.Sprintf("pull messages: %v", e.Err) }
|
|
func (e ErrPullFailed) Unwrap() error { return e.Err }
|
|
func (ErrPullFailed) Op() Op { return OpPull }
|
|
|
|
// ErrRenewFailed wraps a Renew SOAP failure. Renew errors are usually
|
|
// recovered implicitly: the subscription dies, pull starts failing, and
|
|
// the reconnect logic recreates it.
|
|
type ErrRenewFailed struct{ Err error }
|
|
|
|
func (e ErrRenewFailed) Error() string { return fmt.Sprintf("renew pull point: %v", e.Err) }
|
|
func (e ErrRenewFailed) Unwrap() error { return e.Err }
|
|
func (ErrRenewFailed) Op() Op { return OpRenew }
|
|
|
|
// ErrRecreateFailed wraps a failed CreatePullPointSubscription during
|
|
// the reconnect path. The loop continues with exponential backoff;
|
|
// consumers seeing this repeatedly should consider the camera offline.
|
|
type ErrRecreateFailed struct{ Err error }
|
|
|
|
func (e ErrRecreateFailed) Error() string { return fmt.Sprintf("recreate pull point: %v", e.Err) }
|
|
func (e ErrRecreateFailed) Unwrap() error { return e.Err }
|
|
func (ErrRecreateFailed) Op() Op { return OpRecreate }
|