// File: crates/router_env/src/logger/types.rs

//! Types.

use serde::Deserialize;
use strum::{Display, EnumString};
pub use tracing::{
    field::{Field, Visit},
    Level, Value,
};

/// Category and tag of log event.
///
/// Don't hesitate to add your variant if it is missing here.
#[derive(Debug, Default, Deserialize, Clone, Display, EnumString)]
pub enum Tag {
    /// General.
    #[default]
    General,

    /// Redis: get.
    RedisGet,
    /// Redis: set.
    RedisSet,

    /// API: incoming web request.
    ApiIncomingRequest,
    /// API: outgoing web request.
    ApiOutgoingRequest,

    /// Data base: create.
    DbCreate,
    /// Data base: read.
    DbRead,
    /// Data base: updare.
    DbUpdate,
    /// Data base: delete.
    DbDelete,
    /// Begin Request
    BeginRequest,
    /// End Request
    EndRequest,

    /// Call initiated to connector.
    InitiatedToConnector,

    /// Event: general.
    Event,

    /// Compatibility Layer Request
    CompatibilityLayerRequest,
}

/// API Flow
#[derive(Debug, Display, Clone, PartialEq, Eq)]
pub enum Flow {
    /// Health check
    HealthCheck,
    /// Deep health Check
    DeepHealthCheck,
    /// Organization create flow
    OrganizationCreate,
    /// Organization retrieve flow
    OrganizationRetrieve,
    /// Organization update flow
    OrganizationUpdate,
    /// Merchants account create flow.
    MerchantsAccountCreate,
    /// Merchants account retrieve flow.
    MerchantsAccountRetrieve,
    /// Merchants account update flow.
    MerchantsAccountUpdate,
    /// Merchants account delete flow.
    MerchantsAccountDelete,
    /// Merchant Connectors create flow.
    MerchantConnectorsCreate,
    /// Merchant Connectors retrieve flow.
    MerchantConnectorsRetrieve,
    /// Merchant account list
    MerchantAccountList,
    /// Merchant Connectors update flow.
    MerchantConnectorsUpdate,
    /// Merchant Connectors delete flow.
    MerchantConnectorsDelete,
    /// Merchant Connectors list flow.
    MerchantConnectorsList,
    /// Merchant Transfer Keys
    MerchantTransferKey,
    /// ConfigKey create flow.
    ConfigKeyCreate,
    /// ConfigKey fetch flow.
    ConfigKeyFetch,
    /// Enable platform account flow.
    EnablePlatformAccount,
    /// ConfigKey Update flow.
    ConfigKeyUpdate,
    /// ConfigKey Delete flow.
    ConfigKeyDelete,
    /// Customers create flow.
    CustomersCreate,
    /// Customers retrieve flow.
    CustomersRetrieve,
    /// Customers update flow.
    CustomersUpdate,
    /// Customers delete flow.
    CustomersDelete,
    /// Customers get mandates flow.
    CustomersGetMandates,
    /// Create an Ephemeral Key.
    EphemeralKeyCreate,
    /// Delete an Ephemeral Key.
    EphemeralKeyDelete,
    /// Mandates retrieve flow.
    MandatesRetrieve,
    /// Mandates revoke flow.
    MandatesRevoke,
    /// Mandates list flow.
    MandatesList,
    /// Payment methods create flow.
    PaymentMethodsCreate,
    /// Payment methods migrate flow.
    PaymentMethodsMigrate,
    /// Payment methods batch update flow.
    PaymentMethodsBatchUpdate,
    /// Payment methods list flow.
    PaymentMethodsList,
    /// Payment method save flow
    PaymentMethodSave,
    /// Customer payment methods list flow.
    CustomerPaymentMethodsList,
    /// Payment methods token data get flow.
    GetPaymentMethodTokenData,
    /// List Customers for a merchant
    CustomersList,
    ///List Customers for a merchant with constraints.
    CustomersListWithConstraints,
    /// Retrieve countries and currencies for connector and payment method
    ListCountriesCurrencies,
    /// Payment method create collect link flow.
    PaymentMethodCollectLink,
    /// Payment methods retrieve flow.
    PaymentMethodsRetrieve,
    /// Payment methods update flow.
    PaymentMethodsUpdate,
    /// Payment methods delete flow.
    PaymentMethodsDelete,
    /// Network token status check flow.
    NetworkTokenStatusCheck,
    /// Default Payment method flow.
    DefaultPaymentMethodsSet,
    /// Payments create flow.
    PaymentsCreate,
    /// Payments Retrieve flow.
    PaymentsRetrieve,
    /// Payments Retrieve force sync flow.
    PaymentsRetrieveForceSync,
    /// Payments Retrieve using merchant reference id
    PaymentsRetrieveUsingMerchantReferenceId,
    /// Payments update flow.
    PaymentsUpdate,
    /// Payments confirm flow.
    PaymentsConfirm,
    /// Payments capture flow.
    PaymentsCapture,
    /// Payments cancel flow.
    PaymentsCancel,
    /// Payments cancel post capture flow.
    PaymentsCancelPostCapture,
    /// Payments approve flow.
    PaymentsApprove,
    /// Payments reject flow.
    PaymentsReject,
    /// Payments Session Token flow
    PaymentsSessionToken,
    /// Payments start flow.
    PaymentsStart,
    /// Payments list flow.
    PaymentsList,
    /// Payments filters flow
    PaymentsFilters,
    /// Payments aggregates flow
    PaymentsAggregate,
    /// Payments Create Intent flow
    PaymentsCreateIntent,
    /// Payments Get Intent flow
    PaymentsGetIntent,
    /// Payments Update Intent flow
    PaymentsUpdateIntent,
    /// Payments confirm intent flow
    PaymentsConfirmIntent,
    /// Payments create and confirm intent flow
    PaymentsCreateAndConfirmIntent,
    /// Payment attempt list flow
    PaymentAttemptsList,
    #[cfg(feature = "payouts")]
    /// Payouts create flow
    PayoutsCreate,
    #[cfg(feature = "payouts")]
    /// Payouts retrieve flow.
    PayoutsRetrieve,
    #[cfg(feature = "payouts")]
    /// Payouts update flow.
    PayoutsUpdate,
    /// Payouts confirm flow.
    PayoutsConfirm,
    #[cfg(feature = "payouts")]
    /// Payouts cancel flow.
    PayoutsCancel,
    #[cfg(feature = "payouts")]
    /// Payouts fulfill flow.
    PayoutsFulfill,
    #[cfg(feature = "payouts")]
    /// Payouts list flow.
    PayoutsList,
    #[cfg(feature = "payouts")]
    /// Payouts filter flow.
    PayoutsFilter,
    /// Payouts accounts flow.
    PayoutsAccounts,
    /// Payout link initiate flow
    PayoutLinkInitiate,
    /// Payments Redirect flow
    PaymentsRedirect,
    /// Payemnts Complete Authorize Flow
    PaymentsCompleteAuthorize,
    /// Refunds create flow.
    RefundsCreate,
    /// Refunds retrieve flow.
    RefundsRetrieve,
    /// Refunds retrieve force sync flow.
    RefundsRetrieveForceSync,
    /// Refunds update flow.
    RefundsUpdate,
    /// Refunds list flow.
    RefundsList,
    /// Refunds filters flow
    RefundsFilters,
    /// Refunds aggregates flow
    RefundsAggregate,
    // Retrieve forex flow.
    RetrieveForexFlow,
    /// Toggles recon service for a merchant.
    ReconMerchantUpdate,
    /// Recon token request flow.
    ReconTokenRequest,
    /// Initial request for recon service.
    ReconServiceRequest,
    /// Recon token verification flow
    ReconVerifyToken,
    /// Routing create flow,
    RoutingCreateConfig,
    /// Routing link config
    RoutingLinkConfig,
    /// Routing link config
    RoutingUnlinkConfig,
    /// Routing retrieve config
    RoutingRetrieveConfig,
    /// Routing retrieve active config
    RoutingRetrieveActiveConfig,
    /// Routing retrieve default config
    RoutingRetrieveDefaultConfig,
    /// Routing retrieve dictionary
    RoutingRetrieveDictionary,
    /// Rule migration for decision-engine
    DecisionEngineRuleMigration,
    /// Routing update config
    RoutingUpdateConfig,
    /// Routing update default config
    RoutingUpdateDefaultConfig,
    /// Routing delete config
    RoutingDeleteConfig,
    /// Subscription create flow,
    CreateSubscription,
    /// Subscription get plans flow,
    GetPlansForSubscription,
    /// Subscription confirm flow,
    ConfirmSubscription,
    /// Subscription create and confirm flow,
    CreateAndConfirmSubscription,
    /// Get Subscription flow
    GetSubscription,
    /// Update Subscription flow
    UpdateSubscription,
    /// Get Subscription estimate flow
    GetSubscriptionEstimate,
    /// Pause Subscription flow
    PauseSubscription,
    /// Resume Subscription flow
    ResumeSubscription,
    /// Cancel Subscription flow
    CancelSubscription,
    /// Create dynamic routing
    CreateDynamicRoutingConfig,
    /// Toggle dynamic routing
    ToggleDynamicRouting,
    /// Update dynamic routing config
    UpdateDynamicRoutingConfigs,
    /// Add record to blocklist
    AddToBlocklist,
    /// Delete record from blocklist
    DeleteFromBlocklist,
    /// List entries from blocklist
    ListBlocklist,
    /// Toggle blocklist for merchant
    ToggleBlocklistGuard,
    /// Incoming Webhook Receive
    IncomingWebhookReceive,
    /// Recovery incoming webhook receive
    RecoveryIncomingWebhookReceive,
    /// Validate payment method flow
    ValidatePaymentMethod,
    /// API Key create flow
    ApiKeyCreate,
    /// API Key retrieve flow
    ApiKeyRetrieve,
    /// API Key update flow
    ApiKeyUpdate,
    /// API Key revoke flow
    ApiKeyRevoke,
    /// API Key list flow
    ApiKeyList,
    /// Dispute Retrieve flow
    DisputesRetrieve,
    /// Dispute List flow
    DisputesList,
    /// Dispute Filters flow
    DisputesFilters,
    /// Cards Info flow
    CardsInfo,
    /// Create File flow
    CreateFile,
    /// Delete File flow
    DeleteFile,
    /// Retrieve File flow
    RetrieveFile,
    /// Dispute Evidence submission flow
    DisputesEvidenceSubmit,
    /// Create Config Key flow
    CreateConfigKey,
    /// Attach Dispute Evidence flow
    AttachDisputeEvidence,
    /// Delete Dispute Evidence flow
    DeleteDisputeEvidence,
    /// Disputes aggregate flow
    DisputesAggregate,
    /// Retrieve Dispute Evidence flow
    RetrieveDisputeEvidence,
    /// Invalidate cache flow
    CacheInvalidate,
    /// Payment Link Retrieve flow
    PaymentLinkRetrieve,
    /// payment Link Initiate flow
    PaymentLinkInitiate,
    /// payment Link Initiate flow
    PaymentSecureLinkInitiate,
    /// Payment Link List flow
    PaymentLinkList,
    /// Payment Link Status
    PaymentLinkStatus,
    /// Create a profile
    ProfileCreate,
    /// Update a profile
    ProfileUpdate,
    /// Retrieve a profile
    ProfileRetrieve,
    /// Delete a profile
    ProfileDelete,
    /// List all the profiles for a merchant
    ProfileList,
    /// Different verification flows
    Verification,
    /// Rust locker migration
    RustLockerMigration,
    /// Gsm Rule Creation flow
    GsmRuleCreate,
    /// Gsm Rule Retrieve flow
    GsmRuleRetrieve,
    /// Gsm Rule Update flow
    GsmRuleUpdate,
    /// Apple pay certificates migration
    ApplePayCertificatesMigration,
    /// Gsm Rule Delete flow
    GsmRuleDelete,
    /// Get data from embedded flow
    GetDataFromHyperswitchAiFlow,
    // List all chat interactions
    ListAllChatInteractions,
    /// User Sign Up
    UserSignUp,
    /// User Sign Up
    UserSignUpWithMerchantId,
    /// User Sign In
    UserSignIn,
    /// User transfer key
    UserTransferKey,
    /// User connect account
    UserConnectAccount,
    /// Upsert Decision Manager Config
    DecisionManagerUpsertConfig,
    /// Delete Decision Manager Config
    DecisionManagerDeleteConfig,
    /// Retrieve Decision Manager Config
    DecisionManagerRetrieveConfig,
    /// Manual payment fulfillment acknowledgement
    FrmFulfillment,
    /// Get connectors feature matrix
    FeatureMatrix,
    /// Change password flow
    ChangePassword,
    /// Signout flow
    Signout,
    /// Set Dashboard Metadata flow
    SetDashboardMetadata,
    /// Get Multiple Dashboard Metadata flow
    GetMultipleDashboardMetadata,
    /// Payment Connector Verify
    VerifyPaymentConnector,
    /// Internal user signup
    InternalUserSignup,
    /// Create tenant level user
    TenantUserCreate,
    /// Switch org
    SwitchOrg,
    /// Switch merchant v2
    SwitchMerchantV2,
    /// Switch profile
    SwitchProfile,
    /// Get permission info
    GetAuthorizationInfo,
    /// Get Roles info
    GetRolesInfo,
    /// Get Parent Group Info
    GetParentGroupInfo,
    /// List roles v2
    ListRolesV2,
    /// List invitable roles at entity level
    ListInvitableRolesAtEntityLevel,
    /// List updatable roles at entity level
    ListUpdatableRolesAtEntityLevel,
    /// Get role
    GetRole,
    /// Get parent info for role
    GetRoleV2,
    /// Get role from token
    GetRoleFromToken,
    /// Get resources and groups for role from token
    GetRoleFromTokenV2,
    /// Get parent groups info for role from token
    GetParentGroupsInfoForRoleFromToken,
    /// Update user role
    UpdateUserRole,
    /// Create merchant account for user in a org
    UserMerchantAccountCreate,
    /// Create Platform
    CreatePlatformAccount,
    /// Create Org in a given tenancy
    UserOrgMerchantCreate,
    /// Generate Sample Data
    GenerateSampleData,
    /// Delete Sample Data
    DeleteSampleData,
    /// Get details of a user
    GetUserDetails,
    /// Get details of a user role in a merchant account
    GetUserRoleDetails,
    /// PaymentMethodAuth Link token create
    PmAuthLinkTokenCreate,
    /// PaymentMethodAuth Exchange token create
    PmAuthExchangeToken,
    /// Get reset password link
    ForgotPassword,
    /// Reset password using link
    ResetPassword,
    /// Force set or force change password
    RotatePassword,
    /// Invite multiple users
    InviteMultipleUser,
    /// Reinvite user
    ReInviteUser,
    /// Accept invite from email
    AcceptInviteFromEmail,
    /// Delete user role
    DeleteUserRole,
    /// Incremental Authorization flow
    PaymentsIncrementalAuthorization,
    /// Extend Authorization flow
    PaymentsExtendAuthorization,
    /// Get action URL for connector onboarding
    GetActionUrl,
    /// Sync connector onboarding status
    SyncOnboardingStatus,
    /// Reset tracking id
    ResetTrackingId,
    /// Verify email Token
    VerifyEmail,
    /// Send verify email
    VerifyEmailRequest,
    /// Update user account details
    UpdateUserAccountDetails,
    /// Accept user invitation using entities
    AcceptInvitationsV2,
    /// Accept user invitation using entities before user login
    AcceptInvitationsPreAuth,
    /// Initiate external authentication for a payment
    PaymentsExternalAuthentication,
    /// Authorize the payment after external 3ds authentication
    PaymentsAuthorize,
    /// Create Role
    CreateRole,
    /// Create Role V2
    CreateRoleV2,
    /// Update Role
    UpdateRole,
    /// User email flow start
    UserFromEmail,
    /// Begin TOTP
    TotpBegin,
    /// Reset TOTP
    TotpReset,
    /// Verify TOTP
    TotpVerify,
    /// Update TOTP secret
    TotpUpdate,
    /// Verify Access Code
    RecoveryCodeVerify,
    /// Generate or Regenerate recovery codes
    RecoveryCodesGenerate,
    /// Terminate two factor authentication
    TerminateTwoFactorAuth,
    /// Check 2FA status
    TwoFactorAuthStatus,
    /// Create user authentication method
    CreateUserAuthenticationMethod,
    /// Update user authentication method
    UpdateUserAuthenticationMethod,
    /// List user authentication methods
    ListUserAuthenticationMethods,
    /// Get sso auth url
    GetSsoAuthUrl,
    /// Signin with SSO
    SignInWithSso,
    /// Auth Select
    AuthSelect,
    /// List Orgs for user
    ListOrgForUser,
    /// List Merchants for user in org
    ListMerchantsForUserInOrg,
    /// List Profile for user in org and merchant
    ListProfileForUserInOrgAndMerchant,
    /// List Users in Org
    ListUsersInLineage,
    /// List invitations for user
    ListInvitationsForUser,
    /// Get theme using lineage
    GetThemeUsingLineage,
    /// Get theme using theme id
    GetThemeUsingThemeId,
    /// Upload file to theme storage
    UploadFileToThemeStorage,
    /// Create theme
    CreateTheme,
    /// Update theme
    UpdateTheme,
    /// Delete theme
    DeleteTheme,
    /// Create user theme
    CreateUserTheme,
    /// Update user theme
    UpdateUserTheme,
    /// Delete user theme
    DeleteUserTheme,
    /// Upload file to user theme storage
    UploadFileToUserThemeStorage,
    /// Get user theme using theme id
    GetUserThemeUsingThemeId,
    ///List All Themes In Lineage
    ListAllThemesInLineage,
    /// Get user theme using lineage
    GetUserThemeUsingLineage,
    /// List initial webhook delivery attempts
    WebhookEventInitialDeliveryAttemptList,
    /// List delivery attempts for a webhook event
    WebhookEventDeliveryAttemptList,
    /// Manually retry the delivery for a webhook event
    WebhookEventDeliveryRetry,
    /// Retrieve status of the Poll
    RetrievePollStatus,
    /// Toggles the extended card info feature in profile level
    ToggleExtendedCardInfo,
    /// Toggles the extended card info feature in profile level
    ToggleConnectorAgnosticMit,
    /// Get the extended card info associated to a payment_id
    GetExtendedCardInfo,
    /// Manually update the refund details like status, error code, error message etc.
    RefundsManualUpdate,
    /// Manually update the payment details like status, error code, error message etc.
    PaymentsManualUpdate,
    /// Dynamic Tax Calcultion
    SessionUpdateTaxCalculation,
    ProxyConfirmIntent,
    /// Payments post session tokens flow
    PaymentsPostSessionTokens,
    /// Payments Update Metadata
    PaymentsUpdateMetadata,
    /// Payments start redirection flow
    PaymentStartRedirection,
    /// Volume split on the routing type
    VolumeSplitOnRoutingType,
    /// Routing evaluate rule flow
    RoutingEvaluateRule,
    /// Relay flow
    Relay,
    /// Relay retrieve flow
    RelayRetrieve,
    /// Card tokenization flow
    TokenizeCard,
    /// Card tokenization using payment method flow
    TokenizeCardUsingPaymentMethodId,
    /// Cards batch tokenization flow
    TokenizeCardBatch,
    /// Incoming Relay Webhook Receive
    IncomingRelayWebhookReceive,
    /// Generate Hypersense Token
    HypersenseTokenRequest,
    /// Verify Hypersense Token
    HypersenseVerifyToken,
    /// Signout Hypersense Token
    HypersenseSignoutToken,
    /// Payment Method Session Create
    PaymentMethodSessionCreate,
    /// Payment Method Session Retrieve
    PaymentMethodSessionRetrieve,
    // Payment Method Session Update
    PaymentMethodSessionUpdate,
    /// Update a saved payment method using the payment methods session
    PaymentMethodSessionUpdateSavedPaymentMethod,
    /// Delete a saved payment method using the payment methods session
    PaymentMethodSessionDeleteSavedPaymentMethod,
    /// Confirm a payment method session with payment method data
    PaymentMethodSessionConfirm,
    /// Create Cards Info flow
    CardsInfoCreate,
    /// Update Cards Info flow
    CardsInfoUpdate,
    /// Cards Info migrate flow
    CardsInfoMigrate,
    ///Total payment method count for merchant
    TotalPaymentMethodCount,
    /// Process Tracker Revenue Recovery Workflow Retrieve
    RevenueRecoveryRetrieve,
    /// Process Tracker Revenue Recovery Workflow Resume
    RevenueRecoveryResume,
    /// Tokenization flow
    TokenizationCreate,
    /// Tokenization retrieve flow
    TokenizationRetrieve,
    /// Clone Connector flow
    CloneConnector,
    /// Authentication Create flow
    AuthenticationCreate,
    /// Authentication Eligibility flow
    AuthenticationEligibility,
    /// Authentication Sync flow
    AuthenticationSync,
    /// Authentication Sync Post Update flow
    AuthenticationSyncPostUpdate,
    /// Authentication Authenticate flow
    AuthenticationAuthenticate,
    /// Authentication Session Token flow
    AuthenticationSessionToken,
    /// Authentication Eligibility Check flow
    AuthenticationEligibilityCheck,
    /// Authentication Retrieve Eligibility Check flow
    AuthenticationRetrieveEligibilityCheck,
    ///Proxy Flow
    Proxy,
    /// Profile Acquirer Create flow
    ProfileAcquirerCreate,
    /// Profile Acquirer Update flow
    ProfileAcquirerUpdate,
    /// ThreeDs Decision Rule Execute flow
    ThreeDsDecisionRuleExecute,
    /// Incoming Network Token Webhook Receive
    IncomingNetworkTokenWebhookReceive,
    /// Decision Engine Decide Gateway Call
    DecisionEngineDecideGatewayCall,
    /// Decision Engine Gateway Feedback Call
    DecisionEngineGatewayFeedbackCall,
    /// Recovery payments create flow.
    RecoveryPaymentsCreate,
    /// Tokenization delete flow
    TokenizationDelete,
    /// Payment method data backfill flow
    RecoveryDataBackfill,
    /// Revenue recovery Redis operations flow
    RevenueRecoveryRedis,
    /// Payment Method balance check flow
    PaymentMethodBalanceCheck,
    /// Payments Submit Eligibility flow
    PaymentsSubmitEligibility,
    /// Apply payment method data flow
    ApplyPaymentMethodData,
}

/// Trait for providing generic behaviour to flow metric
pub trait FlowMetric: ToString + std::fmt::Debug + Clone {}
impl FlowMetric for Flow {}

/// Category of log event.
#[derive(Debug)]
pub enum Category {
    /// Redis: general.
    Redis,
    /// API: general.
    Api,
    /// Database: general.
    Store,
    /// Event: general.
    Event,
    /// General: general.
    General,
}


// File: crates/router_env/src/logger/config.rs

//! Logger-specific config.

use std::path::PathBuf;

use serde::Deserialize;

/// Config settings.
#[derive(Debug, Deserialize, Clone)]
pub struct Config {
    /// Logging to a file.
    pub log: Log,
}

/// Log config settings.
#[derive(Debug, Deserialize, Clone, Default)]
#[serde(default)]
pub struct Log {
    /// Logging to a file.
    pub file: LogFile,
    /// Logging to a console.
    pub console: LogConsole,
    /// Telemetry / tracing.
    pub telemetry: LogTelemetry,
}

/// Logging to a file.
#[derive(Debug, Deserialize, Clone)]
#[serde(default)]
pub struct LogFile {
    /// Whether you want to store log in log files.
    pub enabled: bool,
    /// Where to store log files.
    pub path: String,
    /// Name of log file without suffix.
    pub file_name: String,
    /// What gets into log files.
    pub level: Level,
    /// Directive which sets the log level for one or more crates/modules.
    pub filtering_directive: Option<String>,
    // pub do_async: bool, // is not used
    // pub rotation: u16,
}

/// Describes the level of verbosity of a span or event.
#[derive(Debug, Clone, Copy)]
pub struct Level(pub(super) tracing::Level);

impl Level {
    /// Returns the most verbose [`tracing::Level`]
    pub fn into_level(self) -> tracing::Level {
        self.0
    }
}

impl<'de> Deserialize<'de> for Level {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: serde::Deserializer<'de>,
    {
        use std::str::FromStr as _;

        let s = String::deserialize(deserializer)?;
        tracing::Level::from_str(&s)
            .map(Level)
            .map_err(serde::de::Error::custom)
    }
}

/// Logging to a console.
#[derive(Debug, Deserialize, Clone)]
#[serde(default)]
pub struct LogConsole {
    /// Whether you want to see log in your terminal.
    pub enabled: bool,
    /// What you see in your terminal.
    pub level: Level,
    /// Log format
    #[serde(default)]
    pub log_format: LogFormat,
    /// Directive which sets the log level for one or more crates/modules.
    pub filtering_directive: Option<String>,
}

/// Telemetry / tracing.
#[derive(Debug, Deserialize, Clone, Default)]
#[serde(default)]
pub struct LogTelemetry {
    /// Whether the traces pipeline is enabled.
    pub traces_enabled: bool,
    /// Whether the metrics pipeline is enabled.
    pub metrics_enabled: bool,
    /// Whether errors in setting up traces or metrics pipelines must be ignored.
    pub ignore_errors: bool,
    /// Sampling rate for traces
    pub sampling_rate: Option<f64>,
    /// Base endpoint URL to send metrics and traces to. Can optionally include the port number.
    pub otel_exporter_otlp_endpoint: Option<String>,
    /// Timeout (in milliseconds) for sending metrics and traces.
    pub otel_exporter_otlp_timeout: Option<u64>,
    /// Whether to use xray ID generator, (enable this if you plan to use AWS-XRAY)
    pub use_xray_generator: bool,
    /// Route Based Tracing
    pub route_to_trace: Option<Vec<String>>,
    /// Interval for collecting the metrics (such as gauge) in background thread
    pub bg_metrics_collection_interval_in_secs: Option<u16>,
}

/// Telemetry / tracing.
#[derive(Default, Debug, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum LogFormat {
    /// Default pretty log format
    Default,
    /// JSON based structured logging
    #[default]
    Json,
    /// JSON based structured logging with pretty print
    PrettyJson,
}

impl Config {
    /// Default constructor.
    pub fn new() -> Result<Self, config::ConfigError> {
        Self::new_with_config_path(None)
    }

    /// Constructor expecting config path set explicitly.
    pub fn new_with_config_path(
        explicit_config_path: Option<PathBuf>,
    ) -> Result<Self, config::ConfigError> {
        // Configuration values are picked up in the following priority order (1 being least
        // priority):
        // 1. Defaults from the implementation of the `Default` trait.
        // 2. Values from config file. The config file accessed depends on the environment
        //    specified by the `RUN_ENV` environment variable. `RUN_ENV` can be one of
        //    `development`, `sandbox` or `production`. If nothing is specified for `RUN_ENV`,
        //    `/config/development.toml` file is read.
        // 3. Environment variables prefixed with `ROUTER` and each level separated by double
        //    underscores.
        //
        // Values in config file override the defaults in `Default` trait, and the values set using
        // environment variables override both the defaults and the config file values.

        let environment = crate::env::which();
        let config_path = Self::config_path(&environment.to_string(), explicit_config_path);

        let config = Self::builder(&environment.to_string())?
            .add_source(config::File::from(config_path).required(false))
            .add_source(config::Environment::with_prefix("ROUTER").separator("__"))
            .build()?;

        // The logger may not yet be initialized when constructing the application configuration
        #[allow(clippy::print_stderr)]
        serde_path_to_error::deserialize(config).map_err(|error| {
            crate::error!(%error, "Unable to deserialize configuration");
            eprintln!("Unable to deserialize application configuration: {error}");
            error.into_inner()
        })
    }

    /// Construct config builder extending it by fall-back defaults and setting config file to load.
    pub fn builder(
        environment: &str,
    ) -> Result<config::ConfigBuilder<config::builder::DefaultState>, config::ConfigError> {
        config::Config::builder()
            // Here, it should be `set_override()` not `set_default()`.
            // "env" can't be altered by config field.
            // Should be single source of truth.
            .set_override("env", environment)
    }

    /// Config path.
    pub fn config_path(environment: &str, explicit_config_path: Option<PathBuf>) -> PathBuf {
        let mut config_path = PathBuf::new();
        if let Some(explicit_config_path_val) = explicit_config_path {
            config_path.push(explicit_config_path_val);
        } else {
            let config_file_name = match environment {
                "production" => "production.toml",
                "sandbox" => "sandbox.toml",
                _ => "development.toml",
            };

            let config_directory = Self::get_config_directory();
            config_path.push(config_directory);
            config_path.push(config_file_name);
        }
        config_path
    }

    /// Get the Directory for the config file
    /// Read the env variable `CONFIG_DIR` or fallback to `config`
    pub fn get_config_directory() -> PathBuf {
        let mut config_path = PathBuf::new();

        let config_directory =
            std::env::var(crate::env::vars::CONFIG_DIR).unwrap_or_else(|_| "config".into());

        config_path.push(crate::env::workspace_path());
        config_path.push(config_directory);
        config_path
    }
}


// File: crates/router_env/src/logger/formatter.rs

//! Formatting [layer](https://docs.rs/tracing-subscriber/0.3.15/tracing_subscriber/layer/trait.Layer.html) for Router.

use std::{
    collections::{HashMap, HashSet},
    fmt,
    io::Write,
    sync::LazyLock,
};

use config::ConfigError;
use serde::ser::{SerializeMap, Serializer};
use serde_json::{ser::Formatter, Value};
// use time::format_description::well_known::Rfc3339;
use time::format_description::well_known::Iso8601;
use tracing::{Event, Metadata, Subscriber};
use tracing_subscriber::{
    fmt::MakeWriter,
    layer::Context,
    registry::{LookupSpan, SpanRef},
    Layer,
};

use crate::Storage;
// TODO: Documentation coverage for this crate

// Implicit keys

const MESSAGE: &str = "message";
const HOSTNAME: &str = "hostname";
const PID: &str = "pid";
const ENV: &str = "env";
const VERSION: &str = "version";
const BUILD: &str = "build";
const LEVEL: &str = "level";
const TARGET: &str = "target";
const SERVICE: &str = "service";
const LINE: &str = "line";
const FILE: &str = "file";
const FN: &str = "fn";
const FULL_NAME: &str = "full_name";
const TIME: &str = "time";

// Extra implicit keys. Keys that are provided during runtime but should be treated as
// implicit in the logs

const FLOW: &str = "flow";
const MERCHANT_AUTH: &str = "merchant_authentication";
const MERCHANT_ID: &str = "merchant_id";
const REQUEST_METHOD: &str = "request_method";
const REQUEST_URL_PATH: &str = "request_url_path";
const REQUEST_ID: &str = "request_id";
const WORKFLOW_ID: &str = "workflow_id";
const GLOBAL_ID: &str = "global_id";
const SESSION_ID: &str = "session_id";

/// Set of predefined implicit keys.
pub static IMPLICIT_KEYS: LazyLock<rustc_hash::FxHashSet<&str>> = LazyLock::new(|| {
    let mut set = rustc_hash::FxHashSet::default();

    set.insert(HOSTNAME);
    set.insert(PID);
    set.insert(ENV);
    set.insert(VERSION);
    set.insert(BUILD);
    set.insert(LEVEL);
    set.insert(TARGET);
    set.insert(SERVICE);
    set.insert(LINE);
    set.insert(FILE);
    set.insert(FN);
    set.insert(FULL_NAME);
    set.insert(TIME);

    set
});

/// Extra implicit keys. Keys that are not purely implicit but need to be logged alongside
/// other implicit keys in the log json.
pub static EXTRA_IMPLICIT_KEYS: LazyLock<rustc_hash::FxHashSet<&str>> = LazyLock::new(|| {
    let mut set = rustc_hash::FxHashSet::default();

    set.insert(MESSAGE);
    set.insert(FLOW);
    set.insert(MERCHANT_AUTH);
    set.insert(MERCHANT_ID);
    set.insert(REQUEST_METHOD);
    set.insert(REQUEST_URL_PATH);
    set.insert(REQUEST_ID);
    set.insert(GLOBAL_ID);
    set.insert(SESSION_ID);
    set.insert(WORKFLOW_ID);

    set
});

/// Describe type of record: entering a span, exiting a span, an event.
#[derive(Clone, Debug)]
pub enum RecordType {
    /// Entering a span.
    EnterSpan,
    /// Exiting a span.
    ExitSpan,
    /// Event.
    Event,
}

impl fmt::Display for RecordType {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let repr = match self {
            Self::EnterSpan => "START",
            Self::ExitSpan => "END",
            Self::Event => "EVENT",
        };
        write!(f, "{repr}")
    }
}

/// Format log records.
/// `FormattingLayer` relies on the `tracing_bunyan_formatter::JsonStorageLayer` which is storage of entries.
#[derive(Debug)]
pub struct FormattingLayer<W, F>
where
    W: for<'a> MakeWriter<'a> + 'static,
    F: Formatter + Clone,
{
    dst_writer: W,
    pid: u32,
    hostname: String,
    env: String,
    service: String,
    #[cfg(feature = "vergen")]
    version: String,
    #[cfg(feature = "vergen")]
    build: String,
    default_fields: HashMap<String, Value>,
    formatter: F,
}

impl<W, F> FormattingLayer<W, F>
where
    W: for<'a> MakeWriter<'a> + 'static,
    F: Formatter + Clone,
{
    /// Constructor of `FormattingLayer`.
    ///
    /// A `name` will be attached to all records during formatting.
    /// A `dst_writer` to forward all records.
    ///
    /// ## Example
    /// ```rust
    /// let formatting_layer = router_env::FormattingLayer::new("my_service", std::io::stdout, serde_json::ser::CompactFormatter);
    /// ```
    pub fn new(
        service: &str,
        dst_writer: W,
        formatter: F,
    ) -> error_stack::Result<Self, ConfigError> {
        Self::new_with_implicit_entries(service, dst_writer, HashMap::new(), formatter)
    }

    /// Construct of `FormattingLayer with implicit default entries.
    pub fn new_with_implicit_entries(
        service: &str,
        dst_writer: W,
        default_fields: HashMap<String, Value>,
        formatter: F,
    ) -> error_stack::Result<Self, ConfigError> {
        let pid = std::process::id();
        let hostname = gethostname::gethostname().to_string_lossy().into_owned();
        let service = service.to_string();
        #[cfg(feature = "vergen")]
        let version = crate::version!().to_string();
        #[cfg(feature = "vergen")]
        let build = crate::build!().to_string();
        let env = crate::env::which().to_string();
        for key in default_fields.keys() {
            if IMPLICIT_KEYS.contains(key.as_str()) {
                return Err(ConfigError::Message(format!(
                    "A reserved key `{key}` was included in `default_fields` in the log formatting layer"
                ))
                .into());
            }
        }

        Ok(Self {
            dst_writer,
            pid,
            hostname,
            env,
            service,
            #[cfg(feature = "vergen")]
            version,
            #[cfg(feature = "vergen")]
            build,
            default_fields,
            formatter,
        })
    }

    /// Serialize common for both span and event entries.
    fn common_serialize<S>(
        &self,
        map_serializer: &mut impl SerializeMap<Error = serde_json::Error>,
        metadata: &Metadata<'_>,
        span: Option<&SpanRef<'_, S>>,
        storage: &Storage<'_>,
        name: &str,
    ) -> Result<(), std::io::Error>
    where
        S: Subscriber + for<'a> LookupSpan<'a>,
    {
        let is_extra = |s: &str| !IMPLICIT_KEYS.contains(s);
        let is_extra_implicit = |s: &str| is_extra(s) && EXTRA_IMPLICIT_KEYS.contains(s);

        map_serializer.serialize_entry(HOSTNAME, &self.hostname)?;
        map_serializer.serialize_entry(PID, &self.pid)?;
        map_serializer.serialize_entry(ENV, &self.env)?;
        #[cfg(feature = "vergen")]
        map_serializer.serialize_entry(VERSION, &self.version)?;
        #[cfg(feature = "vergen")]
        map_serializer.serialize_entry(BUILD, &self.build)?;
        map_serializer.serialize_entry(LEVEL, &format_args!("{}", metadata.level()))?;
        map_serializer.serialize_entry(TARGET, metadata.target())?;
        map_serializer.serialize_entry(SERVICE, &self.service)?;
        map_serializer.serialize_entry(LINE, &metadata.line())?;
        map_serializer.serialize_entry(FILE, &metadata.file())?;
        map_serializer.serialize_entry(FN, name)?;
        map_serializer
            .serialize_entry(FULL_NAME, &format_args!("{}::{}", metadata.target(), name))?;
        if let Ok(time) = &time::OffsetDateTime::now_utc().format(&Iso8601::DEFAULT) {
            map_serializer.serialize_entry(TIME, time)?;
        }

        // Write down implicit default entries.
        for (key, value) in self.default_fields.iter() {
            map_serializer.serialize_entry(key, value)?;
        }

        #[cfg(feature = "log_custom_entries_to_extra")]
        let mut extra = serde_json::Map::default();
        let mut explicit_entries_set: HashSet<&str> = HashSet::default();
        // Write down explicit event's entries.
        for (key, value) in storage.values.iter() {
            if is_extra_implicit(key) {
                #[cfg(feature = "log_extra_implicit_fields")]
                map_serializer.serialize_entry(key, value)?;
                explicit_entries_set.insert(key);
            } else if is_extra(key) {
                #[cfg(feature = "log_custom_entries_to_extra")]
                extra.insert(key.to_string(), value.clone());
                #[cfg(not(feature = "log_custom_entries_to_extra"))]
                map_serializer.serialize_entry(key, value)?;
                explicit_entries_set.insert(key);
            } else {
                tracing::warn!(
                    ?key,
                    ?value,
                    "Attempting to log a reserved entry. It won't be added to the logs"
                );
            }
        }

        // Write down entries from the span, if it exists.
        if let Some(span) = &span {
            let extensions = span.extensions();
            if let Some(visitor) = extensions.get::<Storage<'_>>() {
                for (key, value) in &visitor.values {
                    if is_extra_implicit(key) && !explicit_entries_set.contains(key) {
                        #[cfg(feature = "log_extra_implicit_fields")]
                        map_serializer.serialize_entry(key, value)?;
                    } else if is_extra(key) && !explicit_entries_set.contains(key) {
                        #[cfg(feature = "log_custom_entries_to_extra")]
                        extra.insert(key.to_string(), value.clone());
                        #[cfg(not(feature = "log_custom_entries_to_extra"))]
                        map_serializer.serialize_entry(key, value)?;
                    } else {
                        tracing::warn!(
                            ?key,
                            ?value,
                            "Attempting to log a reserved entry. It won't be added to the logs"
                        );
                    }
                }
            }
        }

        #[cfg(feature = "log_custom_entries_to_extra")]
        map_serializer.serialize_entry("extra", &extra)?;

        Ok(())
    }

    /// Flush memory buffer into an output stream trailing it with next line.
    ///
    /// Should be done by single `write_all` call to avoid fragmentation of log because of mutlithreading.
    fn flush(&self, mut buffer: Vec<u8>) -> Result<(), std::io::Error> {
        buffer.write_all(b"\n")?;
        self.dst_writer.make_writer().write_all(&buffer)
    }

    /// Serialize entries of span.
    fn span_serialize<S>(
        &self,
        span: &SpanRef<'_, S>,
        ty: RecordType,
    ) -> Result<Vec<u8>, std::io::Error>
    where
        S: Subscriber + for<'a> LookupSpan<'a>,
    {
        let mut buffer = Vec::new();
        let mut serializer =
            serde_json::Serializer::with_formatter(&mut buffer, self.formatter.clone());
        let mut map_serializer = serializer.serialize_map(None)?;
        let message = Self::span_message(span, ty);
        let mut storage = Storage::default();
        storage.record_value("message", message.into());

        self.common_serialize(
            &mut map_serializer,
            span.metadata(),
            Some(span),
            &storage,
            span.name(),
        )?;

        map_serializer.end()?;
        Ok(buffer)
    }

    /// Serialize event into a buffer of bytes using parent span.
    pub fn event_serialize<S>(
        &self,
        span: Option<&SpanRef<'_, S>>,
        event: &Event<'_>,
    ) -> std::io::Result<Vec<u8>>
    where
        S: Subscriber + for<'a> LookupSpan<'a>,
    {
        let mut buffer = Vec::new();
        let mut serializer =
            serde_json::Serializer::with_formatter(&mut buffer, self.formatter.clone());
        let mut map_serializer = serializer.serialize_map(None)?;

        let mut storage = Storage::default();
        event.record(&mut storage);

        let name = span.map_or("?", SpanRef::name);
        Self::event_message(span, event, &mut storage);

        self.common_serialize(&mut map_serializer, event.metadata(), span, &storage, name)?;

        map_serializer.end()?;
        Ok(buffer)
    }

    /// Format message of a span.
    ///
    /// Example: "[FN_WITHOUT_COLON - START]"
    fn span_message<S>(span: &SpanRef<'_, S>, ty: RecordType) -> String
    where
        S: Subscriber + for<'a> LookupSpan<'a>,
    {
        format!("[{} - {}]", span.metadata().name().to_uppercase(), ty)
    }

    /// Format message of an event.
    ///
    /// Examples: "[FN_WITHOUT_COLON - EVENT] Message"
    fn event_message<S>(span: Option<&SpanRef<'_, S>>, event: &Event<'_>, storage: &mut Storage<'_>)
    where
        S: Subscriber + for<'a> LookupSpan<'a>,
    {
        // Get value of kept "message" or "target" if does not exist.
        let message = storage
            .values
            .entry("message")
            .or_insert_with(|| event.metadata().target().into());

        // Prepend the span name to the message if span exists.
        if let (Some(span), Value::String(a)) = (span, message) {
            *a = format!("{} {}", Self::span_message(span, RecordType::Event), a,);
        }
    }
}

#[allow(clippy::expect_used)]
impl<S, W, F> Layer<S> for FormattingLayer<W, F>
where
    S: Subscriber + for<'a> LookupSpan<'a>,
    W: for<'a> MakeWriter<'a> + 'static,
    F: Formatter + Clone + 'static,
{
    fn on_event(&self, event: &Event<'_>, ctx: Context<'_, S>) {
        // Event could have no span.
        let span = ctx.lookup_current();

        let result: std::io::Result<Vec<u8>> = self.event_serialize(span.as_ref(), event);
        if let Ok(formatted) = result {
            let _ = self.flush(formatted);
        }
    }

    #[cfg(feature = "log_active_span_json")]
    fn on_enter(&self, id: &tracing::Id, ctx: Context<'_, S>) {
        let span = ctx.span(id).expect("No span");
        if let Ok(serialized) = self.span_serialize(&span, RecordType::EnterSpan) {
            let _ = self.flush(serialized);
        }
    }

    #[cfg(not(feature = "log_active_span_json"))]
    fn on_close(&self, id: tracing::Id, ctx: Context<'_, S>) {
        let span = ctx.span(&id).expect("No span");
        if span.parent().is_none() {
            if let Ok(serialized) = self.span_serialize(&span, RecordType::ExitSpan) {
                let _ = self.flush(serialized);
            }
        }
    }

    #[cfg(feature = "log_active_span_json")]
    fn on_close(&self, id: tracing::Id, ctx: Context<'_, S>) {
        let span = ctx.span(&id).expect("No span");
        if let Ok(serialized) = self.span_serialize(&span, RecordType::ExitSpan) {
            let _ = self.flush(serialized);
        }
    }
}


// File: crates/router_env/src/logger/storage.rs

//! Storing [layer](https://docs.rs/tracing-subscriber/0.3.15/tracing_subscriber/layer/trait.Layer.html) for Router.

use std::{collections::HashMap, fmt, time::Instant};

use tracing::{
    field::{Field, Visit},
    span::{Attributes, Record},
    Id, Subscriber,
};
use tracing_subscriber::{layer::Context, Layer};

/// Storage to store key value pairs of spans.
#[derive(Clone, Debug)]
pub struct StorageSubscription;

/// Storage to store key value pairs of spans.
/// When new entry is crated it stores it in [HashMap] which is owned by `extensions`.
#[derive(Clone, Debug)]
pub struct Storage<'a> {
    /// Hash map to store values.
    pub values: HashMap<&'a str, serde_json::Value>,
}

impl<'a> Storage<'a> {
    /// Default constructor.
    pub fn new() -> Self {
        Self::default()
    }

    pub fn record_value(&mut self, key: &'a str, value: serde_json::Value) {
        if super::formatter::IMPLICIT_KEYS.contains(key) {
            tracing::warn!(value =? value, "{} is a reserved entry. Skipping it.", key);
        } else {
            self.values.insert(key, value);
        }
    }
}

/// Default constructor.
impl Default for Storage<'_> {
    fn default() -> Self {
        Self {
            values: HashMap::new(),
        }
    }
}

/// Visitor to store entry.
impl Visit for Storage<'_> {
    /// A i64.
    fn record_i64(&mut self, field: &Field, value: i64) {
        self.record_value(field.name(), serde_json::Value::from(value));
    }

    /// A u64.
    fn record_u64(&mut self, field: &Field, value: u64) {
        self.record_value(field.name(), serde_json::Value::from(value));
    }

    /// A 64-bit floating point.
    fn record_f64(&mut self, field: &Field, value: f64) {
        self.record_value(field.name(), serde_json::Value::from(value));
    }

    /// A boolean.
    fn record_bool(&mut self, field: &Field, value: bool) {
        self.record_value(field.name(), serde_json::Value::from(value));
    }

    /// A string.
    fn record_str(&mut self, field: &Field, value: &str) {
        self.record_value(field.name(), serde_json::Value::from(value));
    }

    /// Otherwise.
    fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
        match field.name() {
            // Skip fields which are already handled
            name if name.starts_with("log.") => (),
            name if name.starts_with("r#") => {
                self.record_value(
                    #[allow(clippy::expect_used)]
                    name.get(2..)
                        .expect("field name must have a minimum of two characters"),
                    serde_json::Value::from(format!("{value:?}")),
                );
            }
            name => {
                self.record_value(name, serde_json::Value::from(format!("{value:?}")));
            }
        };
    }
}

const PERSISTENT_KEYS: [&str; 6] = [
    "payment_id",
    "connector_name",
    "merchant_id",
    "flow",
    "payment_method",
    "status_code",
];

impl<S: Subscriber + for<'a> tracing_subscriber::registry::LookupSpan<'a>> Layer<S>
    for StorageSubscription
{
    /// On new span.
    fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) {
        #[allow(clippy::expect_used)]
        let span = ctx.span(id).expect("No span");
        let mut extensions = span.extensions_mut();

        let mut visitor = if let Some(parent_span) = span.parent() {
            let mut extensions = parent_span.extensions_mut();
            extensions
                .get_mut::<Storage<'_>>()
                .map(|v| v.to_owned())
                .unwrap_or_default()
        } else {
            Storage::default()
        };

        attrs.record(&mut visitor);
        extensions.insert(visitor);
    }

    /// On additional key value pairs store it.
    fn on_record(&self, span: &Id, values: &Record<'_>, ctx: Context<'_, S>) {
        #[allow(clippy::expect_used)]
        let span = ctx.span(span).expect("No span");
        let mut extensions = span.extensions_mut();
        #[allow(clippy::expect_used)]
        let visitor = extensions
            .get_mut::<Storage<'_>>()
            .expect("The span does not have storage");
        values.record(visitor);
    }

    /// On enter store time.
    fn on_enter(&self, span: &Id, ctx: Context<'_, S>) {
        #[allow(clippy::expect_used)]
        let span = ctx.span(span).expect("No span");
        let mut extensions = span.extensions_mut();
        if extensions.get_mut::<Instant>().is_none() {
            extensions.insert(Instant::now());
        }
    }

    /// On close create an entry about how long did it take.
    fn on_close(&self, span: Id, ctx: Context<'_, S>) {
        #[allow(clippy::expect_used)]
        let span = ctx.span(&span).expect("No span");

        let elapsed_milliseconds = {
            let extensions = span.extensions();
            extensions
                .get::<Instant>()
                .map(|i| i.elapsed().as_millis())
                .unwrap_or(0)
        };

        if let Some(s) = span.extensions().get::<Storage<'_>>() {
            s.values.iter().for_each(|(k, v)| {
                if PERSISTENT_KEYS.contains(k) {
                    span.parent().and_then(|p| {
                        p.extensions_mut()
                            .get_mut::<Storage<'_>>()
                            .map(|s| s.record_value(k, v.to_owned()))
                    });
                }
            })
        };

        let mut extensions_mut = span.extensions_mut();
        #[allow(clippy::expect_used)]
        let visitor = extensions_mut
            .get_mut::<Storage<'_>>()
            .expect("No visitor in extensions");

        if let Ok(elapsed) = serde_json::to_value(elapsed_milliseconds) {
            visitor.record_value("elapsed_milliseconds", elapsed);
        }
    }
}


// File: crates/router_env/src/logger/defaults.rs

impl Default for super::config::LogFile {
    fn default() -> Self {
        Self {
            enabled: true,
            path: "logs".into(),
            file_name: "debug.log".into(),
            level: super::config::Level(tracing::Level::DEBUG),
            filtering_directive: None,
        }
    }
}

impl Default for super::config::LogConsole {
    fn default() -> Self {
        Self {
            enabled: false,
            level: super::config::Level(tracing::Level::INFO),
            log_format: super::config::LogFormat::Json,
            filtering_directive: None,
        }
    }
}


// File: crates/router_env/src/logger/setup.rs

//! Setup logging subsystem.

use std::time::Duration;

use ::config::ConfigError;
use serde_json::ser::{CompactFormatter, PrettyFormatter};
use tracing_appender::non_blocking::WorkerGuard;
use tracing_subscriber::{fmt, prelude::*, util::SubscriberInitExt, EnvFilter, Layer};

use crate::{config, FormattingLayer, StorageSubscription};

/// Contains guards necessary for logging and metrics collection.
#[derive(Debug)]
pub struct TelemetryGuard {
    _log_guards: Vec<WorkerGuard>,
}

/// Setup logging sub-system specifying the logging configuration, service (binary) name, and a
/// list of external crates for which a more verbose logging must be enabled. All crates within the
/// current cargo workspace are automatically considered for verbose logging.
#[allow(clippy::print_stdout)] // The logger hasn't been initialized yet
pub fn setup(
    config: &config::Log,
    service_name: &str,
    crates_to_filter: impl AsRef<[&'static str]>,
) -> error_stack::Result<TelemetryGuard, ConfigError> {
    let mut guards = Vec::new();

    // Setup OpenTelemetry traces and metrics
    let traces_layer = if config.telemetry.traces_enabled {
        setup_tracing_pipeline(&config.telemetry, service_name)
    } else {
        None
    };

    if config.telemetry.metrics_enabled {
        setup_metrics_pipeline(&config.telemetry)
    };

    // Setup file logging
    let file_writer = if config.file.enabled {
        let mut path = crate::env::workspace_path();
        // Using an absolute path for file log path would replace workspace path with absolute path,
        // which is the intended behavior for us.
        path.push(&config.file.path);

        let file_appender = tracing_appender::rolling::hourly(&path, &config.file.file_name);
        let (file_writer, guard) = tracing_appender::non_blocking(file_appender);
        guards.push(guard);

        let file_filter = get_envfilter(
            config.file.filtering_directive.as_ref(),
            config::Level(tracing::Level::WARN),
            config.file.level,
            &crates_to_filter,
        );
        println!("Using file logging filter: {file_filter}");
        let layer = FormattingLayer::new(service_name, file_writer, CompactFormatter)?
            .with_filter(file_filter);
        Some(layer)
    } else {
        None
    };

    let subscriber = tracing_subscriber::registry()
        .with(traces_layer)
        .with(StorageSubscription)
        .with(file_writer);

    // Setup console logging
    if config.console.enabled {
        let (console_writer, guard) = tracing_appender::non_blocking(std::io::stdout());
        guards.push(guard);

        let console_filter = get_envfilter(
            config.console.filtering_directive.as_ref(),
            config::Level(tracing::Level::WARN),
            config.console.level,
            &crates_to_filter,
        );
        println!("Using console logging filter: {console_filter}");

        match config.console.log_format {
            config::LogFormat::Default => {
                let logging_layer = fmt::layer()
                    .with_timer(fmt::time::time())
                    .pretty()
                    .with_writer(console_writer)
                    .with_filter(console_filter);
                subscriber.with(logging_layer).init();
            }
            config::LogFormat::Json => {
                error_stack::Report::set_color_mode(error_stack::fmt::ColorMode::None);
                subscriber
                    .with(
                        FormattingLayer::new(service_name, console_writer, CompactFormatter)?
                            .with_filter(console_filter),
                    )
                    .init();
            }
            config::LogFormat::PrettyJson => {
                error_stack::Report::set_color_mode(error_stack::fmt::ColorMode::None);
                subscriber
                    .with(
                        FormattingLayer::new(service_name, console_writer, PrettyFormatter::new())?
                            .with_filter(console_filter),
                    )
                    .init();
            }
        }
    } else {
        subscriber.init();
    };

    // Returning the TelemetryGuard for logs to be printed and metrics to be collected until it is
    // dropped
    Ok(TelemetryGuard {
        _log_guards: guards,
    })
}

fn get_opentelemetry_exporter_config(
    config: &config::LogTelemetry,
) -> opentelemetry_otlp::ExportConfig {
    let mut exporter_config = opentelemetry_otlp::ExportConfig {
        protocol: opentelemetry_otlp::Protocol::Grpc,
        endpoint: config.otel_exporter_otlp_endpoint.clone(),
        ..Default::default()
    };

    if let Some(timeout) = config.otel_exporter_otlp_timeout {
        exporter_config.timeout = Duration::from_millis(timeout);
    }

    exporter_config
}

#[derive(Debug, Clone)]
enum TraceUrlAssert {
    Match(String),
    EndsWith(String),
}

impl TraceUrlAssert {
    fn compare_url(&self, url: &str) -> bool {
        match self {
            Self::Match(value) => url == value,
            Self::EndsWith(end) => url.ends_with(end),
        }
    }
}

impl From<String> for TraceUrlAssert {
    fn from(value: String) -> Self {
        match value {
            url if url.starts_with('*') => Self::EndsWith(url.trim_start_matches('*').to_string()),
            url => Self::Match(url),
        }
    }
}

#[derive(Debug, Clone)]
struct TraceAssertion {
    clauses: Option<Vec<TraceUrlAssert>>,
    /// default behaviour for tracing if no condition is provided
    default: bool,
}

impl TraceAssertion {
    /// Should the provided url be traced
    fn should_trace_url(&self, url: &str) -> bool {
        match &self.clauses {
            Some(clauses) => clauses.iter().all(|cur| cur.compare_url(url)),
            None => self.default,
        }
    }
}

/// Conditional Sampler for providing control on url based tracing
#[derive(Clone, Debug)]
struct ConditionalSampler<T: opentelemetry_sdk::trace::ShouldSample + Clone + 'static>(
    TraceAssertion,
    T,
);

impl<T: opentelemetry_sdk::trace::ShouldSample + Clone + 'static>
    opentelemetry_sdk::trace::ShouldSample for ConditionalSampler<T>
{
    fn should_sample(
        &self,
        parent_context: Option<&opentelemetry::Context>,
        trace_id: opentelemetry::trace::TraceId,
        name: &str,
        span_kind: &opentelemetry::trace::SpanKind,
        attributes: &[opentelemetry::KeyValue],
        links: &[opentelemetry::trace::Link],
    ) -> opentelemetry::trace::SamplingResult {
        use opentelemetry::trace::TraceContextExt;

        match attributes
            .iter()
            .find(|&kv| kv.key == opentelemetry::Key::new("http.route"))
            .map_or(self.0.default, |inner| {
                self.0.should_trace_url(&inner.value.as_str())
            }) {
            true => {
                self.1
                    .should_sample(parent_context, trace_id, name, span_kind, attributes, links)
            }
            false => opentelemetry::trace::SamplingResult {
                decision: opentelemetry::trace::SamplingDecision::Drop,
                attributes: Vec::new(),
                trace_state: match parent_context {
                    Some(ctx) => ctx.span().span_context().trace_state().clone(),
                    None => opentelemetry::trace::TraceState::default(),
                },
            },
        }
    }
}

fn setup_tracing_pipeline(
    config: &config::LogTelemetry,
    service_name: &str,
) -> Option<
    tracing_opentelemetry::OpenTelemetryLayer<
        tracing_subscriber::Registry,
        opentelemetry_sdk::trace::Tracer,
    >,
> {
    use opentelemetry::trace::TracerProvider;
    use opentelemetry_otlp::WithExportConfig;
    use opentelemetry_sdk::trace;

    opentelemetry::global::set_text_map_propagator(
        opentelemetry_sdk::propagation::TraceContextPropagator::new(),
    );

    // Set the export interval to 1 second
    let batch_config = trace::BatchConfigBuilder::default()
        .with_scheduled_delay(Duration::from_millis(1000))
        .build();

    let exporter_result = opentelemetry_otlp::SpanExporter::builder()
        .with_tonic()
        .with_export_config(get_opentelemetry_exporter_config(config))
        .build();

    let exporter = if config.ignore_errors {
        #[allow(clippy::print_stderr)] // The logger hasn't been initialized yet
        exporter_result
            .inspect_err(|error| eprintln!("Failed to build traces exporter: {error:?}"))
            .ok()?
    } else {
        // Safety: This is conditional, there is an option to avoid this behavior at runtime.
        #[allow(clippy::expect_used)]
        exporter_result.expect("Failed to build traces exporter")
    };

    let mut provider_builder = trace::TracerProvider::builder()
        .with_span_processor(
            trace::BatchSpanProcessor::builder(
                exporter,
                // The runtime would have to be updated if a different web framework is used
                opentelemetry_sdk::runtime::TokioCurrentThread,
            )
            .with_batch_config(batch_config)
            .build(),
        )
        .with_sampler(trace::Sampler::ParentBased(Box::new(ConditionalSampler(
            TraceAssertion {
                clauses: config
                    .route_to_trace
                    .clone()
                    .map(|inner| inner.into_iter().map(TraceUrlAssert::from).collect()),
                default: false,
            },
            trace::Sampler::TraceIdRatioBased(config.sampling_rate.unwrap_or(1.0)),
        ))))
        .with_resource(opentelemetry_sdk::Resource::new(vec![
            opentelemetry::KeyValue::new("service.name", service_name.to_owned()),
        ]));

    if config.use_xray_generator {
        provider_builder = provider_builder
            .with_id_generator(opentelemetry_aws::trace::XrayIdGenerator::default());
    }

    Some(
        tracing_opentelemetry::layer()
            .with_tracer(provider_builder.build().tracer(service_name.to_owned())),
    )
}

fn setup_metrics_pipeline(config: &config::LogTelemetry) {
    use opentelemetry_otlp::WithExportConfig;

    let exporter_result = opentelemetry_otlp::MetricExporter::builder()
        .with_tonic()
        .with_temporality(opentelemetry_sdk::metrics::Temporality::Cumulative)
        .with_export_config(get_opentelemetry_exporter_config(config))
        .build();

    let exporter = if config.ignore_errors {
        #[allow(clippy::print_stderr)] // The logger hasn't been initialized yet
        exporter_result
            .inspect_err(|error| eprintln!("Failed to build metrics exporter: {error:?}"))
            .ok();
        return;
    } else {
        // Safety: This is conditional, there is an option to avoid this behavior at runtime.
        #[allow(clippy::expect_used)]
        exporter_result.expect("Failed to build metrics exporter")
    };

    let reader = opentelemetry_sdk::metrics::PeriodicReader::builder(
        exporter,
        // The runtime would have to be updated if a different web framework is used
        opentelemetry_sdk::runtime::TokioCurrentThread,
    )
    .with_interval(Duration::from_secs(3))
    .with_timeout(Duration::from_secs(10))
    .build();

    let provider = opentelemetry_sdk::metrics::SdkMeterProvider::builder()
        .with_reader(reader)
        .with_resource(opentelemetry_sdk::Resource::new([
            opentelemetry::KeyValue::new(
                "pod",
                std::env::var("POD_NAME").unwrap_or(String::from("hyperswitch-server-default")),
            ),
        ]))
        .build();

    opentelemetry::global::set_meter_provider(provider);
}

fn get_envfilter(
    filtering_directive: Option<&String>,
    default_log_level: config::Level,
    filter_log_level: config::Level,
    crates_to_filter: impl AsRef<[&'static str]>,
) -> EnvFilter {
    filtering_directive
        .map(|filter| {
            // Try to create target filter from specified filtering directive, if set

            // Safety: If user is overriding the default filtering directive, then we need to panic
            // for invalid directives.
            #[allow(clippy::expect_used)]
            EnvFilter::builder()
                .with_default_directive(default_log_level.into_level().into())
                .parse(filter)
                .expect("Invalid EnvFilter filtering directive")
        })
        .unwrap_or_else(|| {
            // Construct a default target filter otherwise
            let mut workspace_members = crate::cargo_workspace_members!();
            workspace_members.extend(crates_to_filter.as_ref());

            workspace_members
                .drain()
                .zip(std::iter::repeat(filter_log_level.into_level()))
                .fold(
                    EnvFilter::default().add_directive(default_log_level.into_level().into()),
                    |env_filter, (target, level)| {
                        // Safety: This is a hardcoded basic filtering directive. If even the basic
                        // filter is wrong, it's better to panic.
                        #[allow(clippy::expect_used)]
                        env_filter.add_directive(
                            format!("{target}={level}")
                                .parse()
                                .expect("Invalid EnvFilter directive format"),
                        )
                    },
                )
        })
}


// File: crates/payment_link/Cargo.toml

[package]
name = "payment_link"
description = "Payment Link rendering and WASM bindings"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
license.workspace = true

[lib]
crate-type = ["rlib", "cdylib"]

[features]
default = ["v1"]
v1 = ["api_models/v1"]
v2 = ["api_models/v2"]
wasm = ["dep:wasm-bindgen", "getrandom/js"]

[dependencies]
serde = { version = "1.0.219", features = ["derive"] }
serde_json = "1.0.140"
tera = "1.20.0"
url = { version = "2.5.4", features = ["serde"] }
urlencoding = "2.1.3"
error-stack = "0.4.1"
thiserror = "1.0.69"
wasm-bindgen = { version = "0.2", optional = true }

# First party crates
api_models = { path = "../api_models", default-features = false }
common_utils = { path = "../common_utils" }
getrandom = { version = "0.2.16", optional = true }

[build-dependencies]
router_env = { path = "../router_env" }
toml = "0.8"
serde = { version = "1.0.219", features = ["derive"] }

[package.metadata.wasm-pack.profile.release]
wasm-opt = ["--enable-bulk-memory", "--enable-nontrapping-float-to-int"]

[lints]
workspace = true


// File: crates/payment_link/build.rs

use std::{collections::HashMap, fs, str::FromStr};

use router_env::env::Env;
use serde::Deserialize;

fn main() {
    // Manually deserialize ENVIRONMENT variable to Env enum
    let environment = std::env::var("ENVIRONMENT")
        .ok()
        .and_then(|env_str| Env::from_str(&env_str).ok())
        .unwrap_or_else(|| {
            #[cfg(debug_assertions)]
            let default = Env::Development;
            #[cfg(not(debug_assertions))]
            let default = Env::Production;
            default
        });

    // Use the payment_link specific SDK URL configuration
    let sdk_config_path = "config/sdk_urls.toml";

    // Tell cargo to rerun if config file changes
    println!("cargo:rerun-if-changed={}", sdk_config_path);
    println!("cargo:rerun-if-env-changed=ENVIRONMENT");

    // Read and parse SDK URLs TOML file
    #[allow(clippy::panic)]
    let config_content = fs::read_to_string(sdk_config_path).unwrap_or_else(|e| {
        panic!(
            "Failed to read SDK config file '{}': {}",
            sdk_config_path, e
        );
    });

    #[allow(clippy::panic)]
    let config: HashMap<Env, EnvConfig> = toml::from_str(&config_content).unwrap_or_else(|e| {
        panic!("Failed to parse TOML in '{}': {}", sdk_config_path, e);
    });

    // Extract SDK URL for the current environment
    #[allow(clippy::panic)]
    let sdk_url = config
        .get(&environment)
        .map(|c| c.sdk_url.as_str())
        .unwrap_or_else(|| {
            panic!(
                "Missing [{}] section in config file: {}",
                environment, sdk_config_path
            )
        });

    // Set environment variable for compile-time access
    println!("cargo:rustc-env=SDK_URL={}", sdk_url);
}

#[derive(Debug, Deserialize)]
struct EnvConfig {
    sdk_url: String,
}


// File: crates/payment_link/config/sdk_urls.toml

# SDK URL Configuration for Payment Link WASM Builds
#
# Used ONLY for WASM builds - read at compile-time by build.rs and embedded into the WASM binary.
# Router uses different SDK URLs from config/*.toml files (can be overridden by env vars at runtime).
#
# We don't use router's configs because build.rs cannot replicate router's complex runtime
# configuration resolution (TOML + env var overrides). This simplified config only needs
# the ENVIRONMENT env var to select the appropriate SDK URL.
#
# Usage: ENVIRONMENT=integ wasm-pack build --features wasm --target web
# Environments must match router_env::Env enum: development, integ, sandbox, production

[development]
sdk_url = "http://localhost:9050/HyperLoader.js"

[integ]
sdk_url = "https://dev.hyperswitch.io/v0/HyperLoader.js"

[sandbox]
sdk_url = "https://beta.hyperswitch.io/v1/HyperLoader.js"

[production]
sdk_url = "https://checkout.hyperswitch.io/v0/HyperLoader.js"


// File: crates/payment_link/src/types.rs

//! Payment link specific types

use common_utils::{
    events::{ApiEventMetric, ApiEventsType},
    impl_api_event_type,
};

#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
pub struct PaymentLinkFormData {
    pub js_script: String,
    pub css_script: String,
    pub sdk_url: url::Url,
    pub html_meta_tags: String,
}

#[derive(Debug, Eq, PartialEq, Clone, serde::Serialize, serde::Deserialize)]
pub struct PaymentLinkStatusData {
    pub js_script: String,
    pub css_script: String,
}

impl_api_event_type!(Miscellaneous, (PaymentLinkFormData, PaymentLinkStatusData));


// File: crates/payment_link/src/meta_tags.rs

use api_models::payments::PaymentLinkDetails;

pub fn get_meta_tags_html(payment_details: &PaymentLinkDetails) -> String {
    format!(
        r#"<meta property="og:title" content="Payment request from {0}"/>
        <meta property="og:description" content="{1}"/>"#,
        payment_details.merchant_name.clone(),
        payment_details
            .merchant_description
            .clone()
            .unwrap_or_default()
    )
}


// File: crates/payment_link/src/lib.rs

pub mod css_generator;
pub mod js_generator;
pub mod meta_tags;
pub mod template_renderer;
pub mod types;

#[cfg(feature = "wasm")]
pub mod wasm;

pub use css_generator::get_css_script;
pub use js_generator::get_js_script;
pub use meta_tags::get_meta_tags_html;
pub use template_renderer::{
    build_payment_link_html, build_secure_payment_link_html, get_payment_link_status,
};
pub use types::{PaymentLinkFormData, PaymentLinkStatusData};
// WASM bindings - thin wrappers around implementation functions in wasm.rs
#[cfg(feature = "wasm")]
use wasm_bindgen::prelude::*;

/// Generate a payment link HTML preview from configuration JSON
///
/// This function is exported to JavaScript when compiled as WASM.
/// It wraps the implementation function in wasm.rs.
#[cfg(feature = "wasm")]
#[wasm_bindgen]
pub fn generate_payment_link_preview(config_json: &str) -> Result<String, JsValue> {
    wasm::generate_payment_link_preview_impl(config_json).map_err(|e| JsValue::from_str(&e))
}

/// Validate payment link configuration and return validation results as JSON
///
/// This function is exported to JavaScript when compiled as WASM.
/// It wraps the implementation function in wasm.rs.
#[cfg(feature = "wasm")]
#[wasm_bindgen]
pub fn validate_payment_link_config(config_json: &str) -> Result<String, JsValue> {
    wasm::validate_payment_link_config_impl(config_json).map_err(|e| JsValue::from_str(&e))
}


// File: crates/payment_link/src/css_generator.rs

use std::collections::HashMap;

use api_models::admin::PaymentLinkConfig;
use error_stack::{Result, ResultExt};

#[derive(Debug, thiserror::Error)]
pub enum PaymentLinkError {
    #[error("Invalid CSS selector: {0}")]
    InvalidCssSelector(String),
}

pub fn get_css_script(payment_link_config: &PaymentLinkConfig) -> Result<String, PaymentLinkError> {
    let custom_rules_css_option = payment_link_config
        .payment_link_ui_rules
        .as_ref()
        .map(generate_dynamic_css)
        .transpose()?;

    let color_scheme_css = get_color_scheme_css(payment_link_config);

    if let Some(custom_rules_css) = custom_rules_css_option {
        Ok(format!("{color_scheme_css}\n{custom_rules_css}"))
    } else {
        Ok(color_scheme_css)
    }
}

fn get_color_scheme_css(payment_link_config: &PaymentLinkConfig) -> String {
    let background_primary_color = payment_link_config
        .background_colour
        .clone()
        .unwrap_or(payment_link_config.theme.clone());
    format!(
        ":root {{
      --primary-color: {background_primary_color};
    }}"
    )
}

fn generate_dynamic_css(
    rules: &HashMap<String, HashMap<String, String>>,
) -> Result<String, PaymentLinkError> {
    if rules.is_empty() {
        return Ok(String::new());
    }

    let mut css_string = String::new();
    css_string.push_str("/* Dynamically Injected UI Rules */\n");

    for (selector, styles_map) in rules {
        if selector.trim().is_empty() {
            return Err(PaymentLinkError::InvalidCssSelector(
                "CSS selector cannot be empty.".to_string(),
            ))
            .attach_printable("Empty CSS selector found in payment_link_ui_rules")?;
        }

        css_string.push_str(selector);
        css_string.push_str(" {\n");

        for (prop_camel_case, css_value) in styles_map {
            let css_property = camel_to_kebab(prop_camel_case);

            css_string.push_str("  ");
            css_string.push_str(&css_property);
            css_string.push_str(": ");
            css_string.push_str(css_value);
            css_string.push_str(";\n");
        }
        css_string.push_str("}\n");
    }
    Ok(css_string)
}

fn camel_to_kebab(s: &str) -> String {
    let mut result = String::new();
    if s.is_empty() {
        return result;
    }

    let chars: Vec<char> = s.chars().collect();

    for (i, &ch) in chars.iter().enumerate() {
        if ch.is_uppercase() {
            let should_add_dash = i > 0
                && (chars.get(i - 1).map(|c| c.is_lowercase()).unwrap_or(false)
                    || (i + 1 < chars.len()
                        && chars.get(i + 1).map(|c| c.is_lowercase()).unwrap_or(false)
                        && chars.get(i - 1).map(|c| c.is_uppercase()).unwrap_or(false)));

            if should_add_dash {
                result.push('-');
            }
            result.push(ch.to_ascii_lowercase());
        } else {
            result.push(ch);
        }
    }
    result
}


// File: crates/payment_link/src/template_renderer.rs

use error_stack::{Result, ResultExt};
use tera::{Context, Tera};

use crate::types::{PaymentLinkFormData, PaymentLinkStatusData};

#[derive(Debug, thiserror::Error)]
pub enum PaymentLinkError {
    #[error("Failed to render template")]
    TemplateRenderError,
    #[error("Failed to build template")]
    TemplateBuildError,
}

pub fn build_payment_link_html(
    payment_link_data: PaymentLinkFormData,
) -> Result<String, PaymentLinkError> {
    let (tera, mut context) = build_payment_link_template(payment_link_data)
        .change_context(PaymentLinkError::TemplateBuildError)
        .attach_printable("Failed to build payment link's HTML template")?;

    let payment_link_initiator = include_str!(
        "../../router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js"
    )
    .to_string();
    context.insert("payment_link_initiator", &payment_link_initiator);

    tera.render("payment_link", &context)
        .change_context(PaymentLinkError::TemplateRenderError)
        .attach_printable("Error while rendering open payment link's HTML template")
}

pub fn build_secure_payment_link_html(
    payment_link_data: PaymentLinkFormData,
) -> Result<String, PaymentLinkError> {
    let (tera, mut context) = build_payment_link_template(payment_link_data)
        .change_context(PaymentLinkError::TemplateBuildError)
        .attach_printable("Failed to build payment link's HTML template")?;

    let payment_link_initiator = include_str!(
        "../../router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js"
    )
    .to_string();
    context.insert("payment_link_initiator", &payment_link_initiator);

    tera.render("payment_link", &context)
        .change_context(PaymentLinkError::TemplateRenderError)
        .attach_printable("Error while rendering secure payment link's HTML template")
}

fn build_payment_link_template(
    payment_link_data: PaymentLinkFormData,
) -> Result<(Tera, Context), PaymentLinkError> {
    let mut tera = Tera::default();

    let css_template =
        include_str!("../../router/src/core/payment_link/payment_link_initiate/payment_link.css")
            .to_string();
    let _ = tera.add_raw_template("payment_link_css", &css_template);
    let mut context = Context::new();
    context.insert("css_color_scheme", &payment_link_data.css_script);

    let rendered_css = tera
        .render("payment_link_css", &context)
        .change_context(PaymentLinkError::TemplateRenderError)?;

    let js_template =
        include_str!("../../router/src/core/payment_link/payment_link_initiate/payment_link.js")
            .to_string();
    let _ = tera.add_raw_template("payment_link_js", &js_template);

    context.insert("payment_details_js_script", &payment_link_data.js_script);
    let sdk_origin = payment_link_data
        .sdk_url
        .host_str()
        .ok_or(PaymentLinkError::TemplateBuildError)
        .attach_printable("Host missing for payment link SDK URL")
        .and_then(|host| {
            if host == "localhost" {
                let port = payment_link_data
                    .sdk_url
                    .port()
                    .ok_or(PaymentLinkError::TemplateBuildError)
                    .attach_printable("Port missing for localhost in SDK URL")?;
                Ok(format!(
                    "{}://{}:{}",
                    payment_link_data.sdk_url.scheme(),
                    host,
                    port
                ))
            } else {
                Ok(format!("{}://{}", payment_link_data.sdk_url.scheme(), host))
            }
        })?;
    context.insert("sdk_origin", &sdk_origin);

    let rendered_js = tera
        .render("payment_link_js", &context)
        .change_context(PaymentLinkError::TemplateRenderError)?;

    let logging_template =
        include_str!("../../router/src/services/redirection/assets/redirect_error_logs_push.js")
            .to_string();
    let locale_template = include_str!("../../router/src/core/payment_link/locale.js").to_string();

    let html_template =
        include_str!("../../router/src/core/payment_link/payment_link_initiate/payment_link.html")
            .to_string();
    let _ = tera.add_raw_template("payment_link", &html_template);

    context.insert("rendered_meta_tag_html", &payment_link_data.html_meta_tags);
    context.insert(
        "preload_link_tags",
        &get_preload_link_html_template(&payment_link_data.sdk_url),
    );
    context.insert(
        "hyperloader_sdk_link",
        &get_hyper_loader_sdk(&payment_link_data.sdk_url),
    );
    context.insert("locale_template", &locale_template);
    context.insert("rendered_css", &rendered_css);
    context.insert("rendered_js", &rendered_js);
    context.insert("logging_template", &logging_template);

    Ok((tera, context))
}

fn get_hyper_loader_sdk(sdk_url: &url::Url) -> String {
    format!("<script src=\"{sdk_url}\" onload=\"initializeSDK()\"></script>")
}

fn get_preload_link_html_template(sdk_url: &url::Url) -> String {
    format!(
        r#"<link rel="preload" href="https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700;800" as="style">
            <link rel="preload" href="{sdk_url}" as="script">"#,
    )
}

pub fn get_payment_link_status(
    payment_link_data: PaymentLinkStatusData,
) -> Result<String, PaymentLinkError> {
    let mut tera = Tera::default();

    // Add modification to css template with dynamic data
    let css_template =
        include_str!("../../router/src/core/payment_link/payment_link_status/status.css")
            .to_string();
    let _ = tera.add_raw_template("payment_link_css", &css_template);
    let mut context = Context::new();
    context.insert("css_color_scheme", &payment_link_data.css_script);

    let rendered_css = tera
        .render("payment_link_css", &context)
        .change_context(PaymentLinkError::TemplateRenderError)?;

    //Locale template
    let locale_template = include_str!("../../router/src/core/payment_link/locale.js");

    // Logging template
    let logging_template =
        include_str!("../../router/src/services/redirection/assets/redirect_error_logs_push.js")
            .to_string();

    // Add modification to js template with dynamic data
    let js_template =
        include_str!("../../router/src/core/payment_link/payment_link_status/status.js")
            .to_string();
    let _ = tera.add_raw_template("payment_link_js", &js_template);
    context.insert("payment_details_js_script", &payment_link_data.js_script);

    let rendered_js = tera
        .render("payment_link_js", &context)
        .change_context(PaymentLinkError::TemplateRenderError)?;

    // Modify Html template with rendered js and rendered css files
    let html_template =
        include_str!("../../router/src/core/payment_link/payment_link_status/status.html")
            .to_string();
    let _ = tera.add_raw_template("payment_link_status", &html_template);

    context.insert("rendered_css", &rendered_css);
    context.insert("locale_template", &locale_template);

    context.insert("rendered_js", &rendered_js);
    context.insert("logging_template", &logging_template);

    tera.render("payment_link_status", &context)
        .change_context(PaymentLinkError::TemplateRenderError)
        .attach_printable("Error while rendering payment link status page")
}


// File: crates/payment_link/src/js_generator.rs

use api_models::payments::PaymentLinkData;
use error_stack::{Result, ResultExt};

#[derive(Debug, thiserror::Error)]
pub enum PaymentLinkError {
    #[error("Failed to serialize payment link data")]
    SerializationFailed,
}

pub fn get_js_script(payment_details: &PaymentLinkData) -> Result<String, PaymentLinkError> {
    let payment_details_str = serde_json::to_string(payment_details)
        .change_context(PaymentLinkError::SerializationFailed)
        .attach_printable("Failed to serialize PaymentLinkData")?;
    let url_encoded_str = urlencoding::encode(&payment_details_str);
    Ok(format!("window.__PAYMENT_DETAILS = '{url_encoded_str}';"))
}


// File: crates/payment_link/src/wasm.rs

use api_models::{admin::PaymentLinkConfig, payments::PaymentLinkData};

use crate::{
    build_payment_link_html, get_css_script, get_js_script, get_meta_tags_html, PaymentLinkFormData,
};

const SDK_URL: &str = env!("SDK_URL");

/// Implementation function for generating payment link preview
/// Called by the wasm_bindgen wrapper in lib.rs
pub fn generate_payment_link_preview_impl(config_json: &str) -> Result<String, String> {
    let payment_link_details: api_models::payments::PaymentLinkDetails =
        serde_json::from_str(config_json)
            .map_err(|e| format!("Failed to deserialize PaymentLinkDetails: {}", e))?;

    let mut payment_link_config = PaymentLinkConfig {
        theme: payment_link_details.theme.clone(),
        logo: payment_link_details.merchant_logo.clone(),
        seller_name: payment_link_details.merchant_name.clone(),
        sdk_layout: payment_link_details.sdk_layout.clone(),
        display_sdk_only: payment_link_details.display_sdk_only,
        hide_card_nickname_field: payment_link_details.hide_card_nickname_field,
        show_card_form_by_default: payment_link_details.show_card_form_by_default,
        transaction_details: payment_link_details.transaction_details.clone(),
        background_image: payment_link_details.background_image.clone(),
        details_layout: payment_link_details.details_layout,
        branding_visibility: payment_link_details.branding_visibility,
        payment_button_text: payment_link_details.payment_button_text.clone(),
        custom_message_for_card_terms: payment_link_details.custom_message_for_card_terms.clone(),
        payment_button_colour: payment_link_details.payment_button_colour.clone(),
        skip_status_screen: payment_link_details.skip_status_screen,
        background_colour: payment_link_details.background_colour.clone(),
        payment_button_text_colour: payment_link_details.payment_button_text_colour.clone(),
        sdk_ui_rules: payment_link_details.sdk_ui_rules.clone(),
        enable_button_only_on_form_ready: payment_link_details.enable_button_only_on_form_ready,
        payment_form_header_text: payment_link_details.payment_form_header_text.clone(),
        payment_form_label_type: payment_link_details.payment_form_label_type,
        show_card_terms: payment_link_details.show_card_terms,
        is_setup_mandate_flow: payment_link_details.is_setup_mandate_flow,
        color_icon_card_cvc_error: payment_link_details.color_icon_card_cvc_error.clone(),
        enabled_saved_payment_method: false,
        allowed_domains: None,
        payment_link_ui_rules: None,
        custom_message_for_payment_method_types: payment_link_details
            .custom_message_for_payment_method_types
            .clone(),
    };

    if let Ok(config_from_json) = serde_json::from_str::<PaymentLinkConfig>(config_json) {
        payment_link_config.enabled_saved_payment_method =
            config_from_json.enabled_saved_payment_method;
        payment_link_config.allowed_domains = config_from_json.allowed_domains;
        payment_link_config.payment_link_ui_rules = config_from_json.payment_link_ui_rules;
    }

    let sdk_url = url::Url::parse(SDK_URL).map_err(|e| format!("Invalid SDK URL: {}", e))?;

    let js_script = get_js_script(&PaymentLinkData::PaymentLinkDetails(Box::new(
        payment_link_details.clone(),
    )))
    .map_err(|e| format!("Failed to generate JS script: {:?}", e))?;

    let css_script = get_css_script(&payment_link_config)
        .map_err(|e| format!("Failed to generate CSS script: {:?}", e))?;

    let html_meta_tags = get_meta_tags_html(&payment_link_details);

    let payment_link_form_data = PaymentLinkFormData {
        js_script,
        sdk_url,
        css_script,
        html_meta_tags,
    };

    build_payment_link_html(payment_link_form_data)
        .map_err(|e| format!("Failed to build payment link HTML: {:?}", e))
}

/// Implementation function for validating payment link config
/// Called by the wasm_bindgen wrapper in lib.rs
pub fn validate_payment_link_config_impl(config_json: &str) -> Result<String, String> {
    let config: api_models::payments::PaymentLinkDetails =
        serde_json::from_str(config_json).map_err(|e| format!("Failed to parse config: {}", e))?;

    let mut errors = Vec::new();
    let mut warnings = Vec::new();

    if !config.theme.starts_with('#') || config.theme.len() != 7 {
        errors.push("Theme color must be a valid hex color (e.g., #4E6ADD)".to_string());
    }

    if let Some(bg_color) = &config.background_colour {
        if !bg_color.starts_with('#') || bg_color.len() != 7 {
            errors.push("Background color must be a valid hex color (e.g., #FFFFFF)".to_string());
        }
    }

    if !config.merchant_logo.is_empty() && !config.merchant_logo.starts_with("http") {
        warnings.push("Merchant logo should be a valid HTTP/HTTPS URL".to_string());
    }

    let valid_layouts = ["accordion", "tabs", "spaced_accordion"];
    if !valid_layouts.contains(&config.sdk_layout.as_str()) {
        errors.push(format!(
            "SDK layout must be one of: {}",
            valid_layouts.join(", ")
        ));
    }

    if config.merchant_name.is_empty() {
        errors.push("Merchant name is required".to_string());
    }

    if config.client_secret.is_empty() {
        errors.push("Client secret is required".to_string());
    }

    if config.pub_key.is_empty() {
        errors.push("Publishable key is required".to_string());
    }

    let validation_result = serde_json::json!({
        "valid": errors.is_empty(),
        "errors": errors,
        "warnings": warnings
    });

    Ok(validation_result.to_string())
}


// File: crates/redis_interface/Cargo.toml

[package]
name = "redis_interface"
description = "A user-friendly interface to Redis"
version = "0.1.0"
edition.workspace = true
rust-version.workspace = true
readme = "README.md"
license.workspace = true

[features]
multitenancy_fallback = []

[dependencies]
error-stack = "0.4.1"
fred = { version = "7.1.2", features = ["metrics", "partial-tracing", "subscriber-client", "check-unresponsive"] }
futures = "0.3"
serde = { version = "1.0.219", features = ["derive"] }
thiserror = "1.0.69"
tokio = "1.48.0"
tokio-stream = { version = "0.1.17", features = ["sync"] }
tracing = { workspace = true }

# First party crates
common_utils = { version = "0.1.0", path = "../common_utils", features = ["async_ext"] }

[dev-dependencies]
tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread"] }

[lints]
workspace = true


// File: crates/redis_interface/README.md

# Redis Interface

A user-friendly interface to Redis.


// File: crates/redis_interface/src/types.rs

//! Data types and type conversions
//! from `fred`'s internal data-types to custom data-types

use common_utils::errors::CustomResult;
use fred::types::RedisValue as FredRedisValue;

use crate::{errors, RedisConnectionPool};

pub struct RedisValue {
    inner: FredRedisValue,
}

impl std::ops::Deref for RedisValue {
    type Target = FredRedisValue;

    fn deref(&self) -> &Self::Target {
        &self.inner
    }
}

impl RedisValue {
    pub fn new(value: FredRedisValue) -> Self {
        Self { inner: value }
    }
    pub fn into_inner(self) -> FredRedisValue {
        self.inner
    }

    pub fn from_bytes(val: Vec<u8>) -> Self {
        Self {
            inner: FredRedisValue::Bytes(val.into()),
        }
    }
    pub fn from_string(value: String) -> Self {
        Self {
            inner: FredRedisValue::String(value.into()),
        }
    }
}

impl From<RedisValue> for FredRedisValue {
    fn from(v: RedisValue) -> Self {
        v.inner
    }
}

#[derive(Debug, serde::Deserialize, Clone)]
#[serde(default)]
pub struct RedisSettings {
    pub host: String,
    pub port: u16,
    pub cluster_enabled: bool,
    pub cluster_urls: Vec<String>,
    pub use_legacy_version: bool,
    pub pool_size: usize,
    pub reconnect_max_attempts: u32,
    /// Reconnect delay in milliseconds
    pub reconnect_delay: u32,
    /// TTL in seconds
    pub default_ttl: u32,
    /// TTL for hash-tables in seconds
    pub default_hash_ttl: u32,
    pub stream_read_count: u64,
    pub auto_pipeline: bool,
    pub disable_auto_backpressure: bool,
    pub max_in_flight_commands: u64,
    pub default_command_timeout: u64,
    pub max_feed_count: u64,
    pub unresponsive_timeout: u64,
}

impl RedisSettings {
    /// Validates the Redis configuration provided.
    pub fn validate(&self) -> CustomResult<(), errors::RedisError> {
        use common_utils::{ext_traits::ConfigExt, fp_utils::when};

        when(self.host.is_default_or_empty(), || {
            Err(errors::RedisError::InvalidConfiguration(
                "Redis `host` must be specified".into(),
            ))
        })?;

        when(self.cluster_enabled && self.cluster_urls.is_empty(), || {
            Err(errors::RedisError::InvalidConfiguration(
                "Redis `cluster_urls` must be specified if `cluster_enabled` is `true`".into(),
            ))
        })?;

        when(
            self.default_command_timeout < self.unresponsive_timeout,
            || {
                Err(errors::RedisError::InvalidConfiguration(
                    "Unresponsive timeout cannot be greater than the command timeout".into(),
                )
                .into())
            },
        )
    }
}

impl Default for RedisSettings {
    fn default() -> Self {
        Self {
            host: "127.0.0.1".to_string(),
            port: 6379,
            cluster_enabled: false,
            cluster_urls: vec![],
            use_legacy_version: false,
            pool_size: 5,
            reconnect_max_attempts: 5,
            reconnect_delay: 5,
            default_ttl: 300,
            stream_read_count: 1,
            default_hash_ttl: 900,
            auto_pipeline: true,
            disable_auto_backpressure: false,
            max_in_flight_commands: 5000,
            default_command_timeout: 30,
            max_feed_count: 200,
            unresponsive_timeout: 10,
        }
    }
}

#[derive(Debug)]
pub enum RedisEntryId {
    UserSpecifiedID {
        milliseconds: String,
        sequence_number: String,
    },
    AutoGeneratedID,
    AfterLastID,
    /// Applicable only with consumer groups
    UndeliveredEntryID,
}

impl From<RedisEntryId> for fred::types::XID {
    fn from(id: RedisEntryId) -> Self {
        match id {
            RedisEntryId::UserSpecifiedID {
                milliseconds,
                sequence_number,
            } => Self::Manual(fred::bytes_utils::format_bytes!(
                "{milliseconds}-{sequence_number}"
            )),
            RedisEntryId::AutoGeneratedID => Self::Auto,
            RedisEntryId::AfterLastID => Self::Max,
            RedisEntryId::UndeliveredEntryID => Self::NewInGroup,
        }
    }
}

impl From<&RedisEntryId> for fred::types::XID {
    fn from(id: &RedisEntryId) -> Self {
        match id {
            RedisEntryId::UserSpecifiedID {
                milliseconds,
                sequence_number,
            } => Self::Manual(fred::bytes_utils::format_bytes!(
                "{milliseconds}-{sequence_number}"
            )),
            RedisEntryId::AutoGeneratedID => Self::Auto,
            RedisEntryId::AfterLastID => Self::Max,
            RedisEntryId::UndeliveredEntryID => Self::NewInGroup,
        }
    }
}

#[derive(Eq, PartialEq)]
pub enum SetnxReply {
    KeySet,
    KeyNotSet, // Existing key
}

impl fred::types::FromRedis for SetnxReply {
    fn from_value(value: fred::types::RedisValue) -> Result<Self, fred::error::RedisError> {
        match value {
            // Returns String ( "OK" ) in case of success
            fred::types::RedisValue::String(_) => Ok(Self::KeySet),
            // Return Null in case of failure
            fred::types::RedisValue::Null => Ok(Self::KeyNotSet),
            // Unexpected behaviour
            _ => Err(fred::error::RedisError::new(
                fred::error::RedisErrorKind::Unknown,
                "Unexpected SETNX command reply",
            )),
        }
    }
}

#[derive(Eq, PartialEq)]
pub enum HsetnxReply {
    KeySet,
    KeyNotSet, // Existing key
}

impl fred::types::FromRedis for HsetnxReply {
    fn from_value(value: fred::types::RedisValue) -> Result<Self, fred::error::RedisError> {
        match value {
            fred::types::RedisValue::Integer(1) => Ok(Self::KeySet),
            fred::types::RedisValue::Integer(0) => Ok(Self::KeyNotSet),
            _ => Err(fred::error::RedisError::new(
                fred::error::RedisErrorKind::Unknown,
                "Unexpected HSETNX command reply",
            )),
        }
    }
}

#[derive(Eq, PartialEq)]
pub enum MsetnxReply {
    KeysSet,
    KeysNotSet, // At least one existing key
}

impl fred::types::FromRedis for MsetnxReply {
    fn from_value(value: fred::types::RedisValue) -> Result<Self, fred::error::RedisError> {
        match value {
            fred::types::RedisValue::Integer(1) => Ok(Self::KeysSet),
            fred::types::RedisValue::Integer(0) => Ok(Self::KeysNotSet),
            _ => Err(fred::error::RedisError::new(
                fred::error::RedisErrorKind::Unknown,
                "Unexpected MSETNX command reply",
            )),
        }
    }
}

#[derive(Debug)]
pub enum StreamCapKind {
    MinID,
    MaxLen,
}

impl From<StreamCapKind> for fred::types::XCapKind {
    fn from(item: StreamCapKind) -> Self {
        match item {
            StreamCapKind::MaxLen => Self::MaxLen,
            StreamCapKind::MinID => Self::MinID,
        }
    }
}

#[derive(Debug)]
pub enum StreamCapTrim {
    Exact,
    AlmostExact,
}

impl From<StreamCapTrim> for fred::types::XCapTrim {
    fn from(item: StreamCapTrim) -> Self {
        match item {
            StreamCapTrim::Exact => Self::Exact,
            StreamCapTrim::AlmostExact => Self::AlmostExact,
        }
    }
}

#[derive(Debug)]
pub enum DelReply {
    KeyDeleted,
    KeyNotDeleted, // Key not found
}

impl DelReply {
    pub fn is_key_deleted(&self) -> bool {
        matches!(self, Self::KeyDeleted)
    }

    pub fn is_key_not_deleted(&self) -> bool {
        matches!(self, Self::KeyNotDeleted)
    }
}

impl fred::types::FromRedis for DelReply {
    fn from_value(value: fred::types::RedisValue) -> Result<Self, fred::error::RedisError> {
        match value {
            fred::types::RedisValue::Integer(1) => Ok(Self::KeyDeleted),
            fred::types::RedisValue::Integer(0) => Ok(Self::KeyNotDeleted),
            _ => Err(fred::error::RedisError::new(
                fred::error::RedisErrorKind::Unknown,
                "Unexpected del command reply",
            )),
        }
    }
}

#[derive(Debug)]
pub enum SaddReply {
    KeySet,
    KeyNotSet,
}

impl fred::types::FromRedis for SaddReply {
    fn from_value(value: fred::types::RedisValue) -> Result<Self, fred::error::RedisError> {
        match value {
            fred::types::RedisValue::Integer(1) => Ok(Self::KeySet),
            fred::types::RedisValue::Integer(0) => Ok(Self::KeyNotSet),
            _ => Err(fred::error::RedisError::new(
                fred::error::RedisErrorKind::Unknown,
                "Unexpected sadd command reply",
            )),
        }
    }
}

#[derive(Debug)]
pub enum SetGetReply<T> {
    ValueSet(T),    // Value was set and this is the value that was set
    ValueExists(T), // Value already existed and this is the existing value
}

impl<T> SetGetReply<T> {
    pub fn get_value(&self) -> &T {
        match self {
            Self::ValueSet(value) => value,
            Self::ValueExists(value) => value,
        }
    }
}

#[derive(Debug)]
pub struct RedisKey(String);

impl RedisKey {
    pub fn tenant_aware_key(&self, pool: &RedisConnectionPool) -> String {
        pool.add_prefix(&self.0)
    }

    pub fn tenant_unaware_key(&self, _pool: &RedisConnectionPool) -> String {
        self.0.clone()
    }
}

impl<T: AsRef<str>> From<T> for RedisKey {
    fn from(value: T) -> Self {
        let value = value.as_ref();

        Self(value.to_string())
    }
}