package operations import ( "context" "errors" "reflect" "sort" "time" "proxy-pool/internal/config" "proxy-pool/internal/controller/admin" "proxy-pool/internal/domain/activitypool" ) var ( ErrInvalidReader = errors.New("invalid operational status reader") ErrUnavailable = errors.New("operational status unavailable") ) type ConfigurationReader interface { Current() *config.Config } type Reader struct { configuration ConfigurationReader inventory activitypool.StateInventoryReader now func() time.Time } var _ admin.OperationalStatusReader = (*Reader)(nil) func NewReader( configuration ConfigurationReader, inventory activitypool.StateInventoryReader, now func() time.Time, ) (*Reader, error) { if nilInterface(configuration) || nilInterface(inventory) || now == nil { return nil, ErrInvalidReader } return &Reader{configuration: configuration, inventory: inventory, now: now}, nil } func (reader *Reader) ReadOperationalStatus(ctx context.Context) (admin.OperationalStatus, error) { if ctx == nil || reader == nil { return admin.OperationalStatus{}, ErrInvalidReader } if err := ctx.Err(); err != nil { return admin.OperationalStatus{}, err } configuration := reader.configuration.Current() if configuration == nil { return admin.OperationalStatus{}, ErrUnavailable } upstreamIDs := make([]string, 0, len(configuration.Upstreams)) for upstreamID := range configuration.Upstreams { upstreamIDs = append(upstreamIDs, upstreamID) } sort.Strings(upstreamIDs) inventories, err := reader.inventory.ReadStateInventory(ctx, upstreamIDs, reader.now().UTC()) if err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return admin.OperationalStatus{}, err } return admin.OperationalStatus{}, errors.Join(ErrUnavailable, err) } if len(inventories) != len(upstreamIDs) { return admin.OperationalStatus{}, ErrUnavailable } status := admin.OperationalStatus{Upstreams: make([]admin.UpstreamActivity, len(inventories))} for index, inventory := range inventories { if inventory.UpstreamID != upstreamIDs[index] || invalidInventory(inventory) { return admin.OperationalStatus{}, ErrUnavailable } status.Upstreams[index] = admin.UpstreamActivity{ Name: inventory.UpstreamID, Available: inventory.Available, Checking: inventory.Checking, Suspect: inventory.Suspect, Draining: inventory.Draining, Extracted: inventory.Extracted, } } return status, nil } func invalidInventory(inventory activitypool.StateInventory) bool { return inventory.Fetched < 0 || inventory.Checking < 0 || inventory.Available < 0 || inventory.Suspect < 0 || inventory.Draining < 0 || inventory.Unhealthy < 0 || inventory.Extracted < 0 } func nilInterface(value any) bool { if value == nil { return true } reflected := reflect.ValueOf(value) switch reflected.Kind() { case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: return reflected.IsNil() default: return false } }