<?php
/**
 * Provider-request-failed provider exception.
 *
 * Concrete {@see ProviderException} subclass that signals the
 * provider implementation could not construct a valid request to
 * the upstream API: required parameters were missing, supplied
 * values violated the provider's input contract, or the request
 * shape itself was rejected before any meaningful authentication or
 * dispatch could occur.
 *
 * Field semantics match `specifications/provider-api-contracts.md`
 * §10.2 exactly:
 *
 *   - {@see self::ERROR_CODE} is `provider_request_failed`, the
 *     stable §10.2 code that REST error envelopes, log lines, and
 *     admin diagnostics views surface as the machine-readable
 *     identifier for this failure mode.
 *
 *   - Per §10.1 the exception message MUST NOT carry secret
 *     material; the provider implementation that constructs the
 *     exception is responsible for redacting before passing the
 *     summary in.
 *
 * Typical raise sites:
 *
 *   - Provider implementation receives a
 *     {@see \Affilun\Domain\Search\SearchQuery} whose
 *     fields are individually valid at the cross-provider DTO layer
 *     but violate this provider's narrower input contract (page
 *     size above the per-provider cap, sort discriminator the
 *     provider does not recognize, locale the provider does not
 *     serve).
 *
 *   - Provider implementation receives a lookup request for a
 *     {@see \Affilun\Domain\Provider\ProviderProductId}
 *     whose value half does not match the provider's native
 *     identifier grammar (Amazon ASIN length / charset, 楽天
 *     itemCode shape) before the request is dispatched.
 *
 *   - Provider HTTP client receives a 4xx response (typically 400
 *     "Bad Request") that indicates the request shape itself was
 *     rejected, distinct from a 401 / 403 credential rejection or a
 *     404 "not found" response.
 *
 * Distinct from {@see AuthenticationException}: that exception
 * fires when the request is structurally valid but the credentials
 * attached to it are rejected; this exception fires when the
 * request itself is malformed regardless of credentials.
 *
 * Distinct from {@see ProviderResponseException}: that exception
 * fires when the upstream returned a response whose contents
 * violate the cross-provider contract; this exception fires before
 * a meaningful response is received, on the request-construction
 * side of the wire.
 *
 * Distinct from {@see UnsupportedFeatureException}: that exception
 * fires when the request asked for a feature this provider does
 * not support at all (per
 * {@see \Affilun\Domain\Provider\ProviderCapabilities});
 * this exception fires when the requested feature is supported but
 * the specific parameters supplied are invalid for it.
 *
 * @package Affilun\Domain\Provider\Exception
 */

declare(strict_types=1);

namespace Affilun\Domain\Provider\Exception;

use Throwable;

/**
 * Provider exception for malformed requests rejected by the
 * provider implementation or by the upstream API on contract
 * grounds.
 *
 * See the file-level docblock for the mapping to
 * `provider-api-contracts.md` §10.2, the typical raise sites, and
 * the distinctions versus {@see AuthenticationException},
 * {@see ProviderResponseException}, and
 * {@see UnsupportedFeatureException}.
 */
final class ProviderRequestException extends ProviderException {

	/**
	 * Stable §10.2 error code for this failure mode.
	 *
	 * Exposed as a class constant so call sites can reference the
	 * exact token without duplicating the literal, and so a future
	 * spec change has a single location to update.
	 */
	public const ERROR_CODE = 'provider_request_failed';

	/**
	 * Construct a provider-request-failed exception.
	 *
	 * Forwards {@see self::ERROR_CODE} to the
	 * {@see ProviderException} base so every malformed-request raise
	 * carries the same §10.2 code regardless of which provider
	 * implementation surfaced it. The remaining cross-cutting fields
	 * (`provider`, `message`, `context`, `previous`) follow the
	 * base's contract verbatim — see {@see ProviderException} for
	 * the per-field validation rules and the secret-material rule
	 * that binds the message body.
	 *
	 * @param string               $provider §4 provider key that raised the exception.
	 * @param string               $message  Human-readable, non-secret summary.
	 * @param array<string, mixed> $context  Structured diagnostic context.
	 * @param Throwable|null       $previous Underlying cause, or null.
	 */
	public function __construct(
		string $provider,
		string $message = '',
		array $context = array(),
		?Throwable $previous = null
	) {
		parent::__construct( self::ERROR_CODE, $provider, $message, $context, $previous );
	}
}
