# Generated by smithplates HTTP codegen. Do not edit.

from __future__ import annotations

from dataclasses import dataclass
from typing import Protocol


@dataclass(frozen=True)
class AuthCredential:
    scheme_id: str
    value: str


class AuthProvider(Protocol):
    def resolve_auth(self, scheme_id: str) -> AuthCredential | None: ...


@dataclass(frozen=True)
class AuthSchemeBinding:
    scheme_id: str
    location: str
    name: str
    prefix: str | None


@dataclass(frozen=True)
class OperationAuthBinding:
    alternatives: tuple[str, ...]
    allows_anonymous: bool


AUTH_SCHEME_BINDINGS: dict[str, AuthSchemeBinding] = {
    "smithy.api#httpApiKeyAuth": AuthSchemeBinding(
        scheme_id="smithy.api#httpApiKeyAuth",
        location="query",
        name="api_key",
        prefix=None,
    ),
}


OPERATION_AUTH_BINDINGS: dict[str, OperationAuthBinding] = {
    "query_api_key": OperationAuthBinding(
        alternatives=("smithy.api#httpApiKeyAuth",),
        allows_anonymous=False,
    ),
    "QueryApiKey": OperationAuthBinding(
        alternatives=("smithy.api#httpApiKeyAuth",),
        allows_anonymous=False,
    ),
}


def _set_header(headers: dict[str, str], name: str, value: str) -> None:
    existing_key = next((key for key in headers if key.lower() == name.lower()), None)
    if existing_key is not None and existing_key != name:
        del headers[existing_key]
    headers[name] = value


def apply_operation_auth(
    binding: OperationAuthBinding,
    auth_provider: AuthProvider | None,
    headers: dict[str, str],
    query_params: list[tuple[str, str]],
) -> None:
    allowed_scheme_ids = {scheme_id for scheme_id in binding.alternatives if scheme_id != "smithy.api#noAuth"}
    for scheme_id in binding.alternatives:
        if scheme_id == "smithy.api#noAuth":
            return
        if auth_provider is None:
            continue
        credential = auth_provider.resolve_auth(scheme_id)
        if credential is None:
            continue
        if credential.scheme_id not in allowed_scheme_ids:
            msg = f"Auth provider returned scheme {credential.scheme_id!r}, which is not allowed by the operation"
            raise ValueError(msg)
        if credential.scheme_id != scheme_id:
            msg = f"Auth provider returned scheme {credential.scheme_id!r} while resolving {scheme_id!r}"
            raise ValueError(msg)
        if not credential.value:
            continue

        scheme = AUTH_SCHEME_BINDINGS[credential.scheme_id]
        value = f"{scheme.prefix} {credential.value}" if scheme.prefix else credential.value
        if scheme.location == "header":
            _set_header(headers, scheme.name, value)
        elif scheme.location == "query":
            query_params.append((scheme.name, value))
        elif scheme.location == "cookie":
            if any(ord(character) < 0x21 or ord(character) > 0x7E or character in '\\",;' for character in value):
                raise ValueError("Cookie authentication credential contains an invalid character")
            cookie = f"{scheme.name}={value}"
            existing_cookie_key = next((key for key in headers if key.lower() == "cookie"), None)
            if existing_cookie_key is None:
                headers["Cookie"] = cookie
            else:
                headers[existing_cookie_key] = f"{headers[existing_cookie_key]}; {cookie}"
        else:
            msg = f"Unsupported auth location {scheme.location!r}"
            raise ValueError(msg)
        return

    if not binding.allows_anonymous:
        raise RuntimeError("No usable authentication credential is available for the operation")


@dataclass(frozen=True)
class ResponseVariantBinding:
    status_code: int
    media_type: str | None
    header_bindings: tuple[tuple[str, str], ...]
    static_headers: tuple[tuple[str, str], ...]


@dataclass(frozen=True)
class ClientOperationHttpBinding:
    variants_by_type_name: dict[str, ResponseVariantBinding]
    variants_by_status: dict[int, str]

    def variant_for_status(self, status_code: int) -> ResponseVariantBinding:
        type_name = self.variants_by_status.get(status_code)
        if type_name is None:
            msg = f"No HTTP binding registered for response status {status_code}"
            raise KeyError(msg)
        binding = self.variants_by_type_name.get(type_name)
        if binding is None:
            msg = f"No HTTP binding registered for response type {type_name!r}"
            raise KeyError(msg)
        return binding

    def type_name_for_status(self, status_code: int) -> str:
        type_name = self.variants_by_status.get(status_code)
        if type_name is None:
            msg = f"No HTTP binding registered for response status {status_code}"
            raise KeyError(msg)
        return type_name


OPERATION_HTTP_BINDINGS: dict[str, ClientOperationHttpBinding] = {
    "query_api_key": ClientOperationHttpBinding(
        variants_by_type_name={
            "AuthOutput": ResponseVariantBinding(
                status_code=200,
                media_type="application/json",
                header_bindings=(),
                static_headers=(),
            ),
        },
        variants_by_status={
            200: "AuthOutput",
        },
    ),
    "QueryApiKey": ClientOperationHttpBinding(
        variants_by_type_name={
            "AuthOutput": ResponseVariantBinding(
                status_code=200,
                media_type="application/json",
                header_bindings=(),
                static_headers=(),
            ),
        },
        variants_by_status={
            200: "AuthOutput",
        },
    ),
}
