package extraction import ( "errors" domain "proxy-pool/internal/domain/extraction" ) // MetricResult is a fixed, low-cardinality classification of one extraction // request after application-level validation, admission, and storage handling. type MetricResult string const ( MetricComplete MetricResult = "complete" MetricPartial MetricResult = "partial" MetricEmpty MetricResult = "empty" MetricInsufficient MetricResult = "insufficient" MetricIdempotencyConflict MetricResult = "idempotency_conflict" MetricRateLimited MetricResult = "rate_limited" MetricUnavailable MetricResult = "unavailable" MetricInvalid MetricResult = "invalid" MetricError MetricResult = "error" ) // MetricsEvent contains only aggregate request and response counts. It must // never contain a Client, Proxy, Upstream, filter, identifier, or error text. type MetricsEvent struct { Result MetricResult Requested int Returned int } // MetricsObserver receives one completed extraction request. Implementations // must return promptly and must not perform network or persistent storage I/O. type MetricsObserver interface { ObserveExtraction(MetricsEvent) } func classifyMetricsEvent(response Response, resultErr error) MetricsEvent { event := MetricsEvent{Requested: response.Requested, Returned: response.Returned} if resultErr == nil { switch { case response.Returned == response.Requested: event.Result = MetricComplete case response.Returned > 0 && response.Returned < response.Requested: event.Result = MetricPartial case response.Returned == 0: event.Result = MetricEmpty default: event.Result = MetricError } return event } switch { case errors.Is(resultErr, domain.ErrInsufficientProxies): event.Result = MetricInsufficient case errors.Is(resultErr, domain.ErrIdempotencyConflict): event.Result = MetricIdempotencyConflict case errors.Is(resultErr, ErrAdmissionRejected): event.Result = MetricRateLimited case errors.Is(resultErr, ErrUnavailable): event.Result = MetricUnavailable case errors.Is(resultErr, ErrInvalidRequest), errors.Is(resultErr, ErrCountExceeded), errors.Is(resultErr, ErrInvalidFulfillment), errors.Is(resultErr, domain.ErrInvalidCommand): event.Result = MetricInvalid default: event.Result = MetricError } return event } func validMetricResult(result MetricResult) bool { switch result { case MetricComplete, MetricPartial, MetricEmpty, MetricInsufficient, MetricIdempotencyConflict, MetricRateLimited, MetricUnavailable, MetricInvalid, MetricError: return true default: return false } }