Repository: juspay/hyperswitch
File: crates/api_models/src/revenue_recovery_data_backfill.rs (Rust)

Code before:
use std::{collections::HashMap, fs::File, io::BufReader};

use actix_multipart::form::{tempfile::TempFile, MultipartForm};
use actix_web::{HttpResponse, ResponseError};
use common_enums::{CardNetwork, PaymentMethodType};
use common_utils::{events::ApiEventMetric, pii::PhoneNumberStrategy};
use csv::Reader;
use masking::Secret;
use serde::{Deserialize, Serialize};
use time::{Date, PrimitiveDateTime};

#[derive(Debug, Deserialize, Serialize)]
#[serde(untagged)]
pub enum ScheduledAtUpdate {
    SetToDateTime(#[serde(deserialize_with = "deserialize_primitive_datetime")] PrimitiveDateTime),
    SetToNull(String), // matches "null" string
}

fn deserialize_primitive_datetime<'de, D>(deserializer: D) -> Result<PrimitiveDateTime, D::Error>
where
    D: serde::Deserializer<'de>,
{
    let s = String::deserialize(deserializer)?;

    // Try multiple datetime formats
    PrimitiveDateTime::parse(&s, &time::format_description::well_known::Iso8601::DEFAULT)
        .or_else(|_| {
            let format = time::macros::format_description!(
                "[year]-[month]-[day]T[hour]:[minute]:[second].[subsecond]"
            );
            PrimitiveDateTime::parse(&s, format)
        })
        .or_else(|_| {
            let format =
                time::macros::format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]");
            PrimitiveDateTime::parse(&s, format)
        })
        .map_err(|e| serde::de::Error::custom(format!("Invalid datetime format: {}", e)))
}

impl ScheduledAtUpdate {
    pub fn to_option(self) -> Option<PrimitiveDateTime> {

Review comment (suggestion):
With this you won't be needing the custom deserializer

```suggestion
pub enum ScheduledAtUpdate {
    SetToDateTime(#[serde(with = "common_utils::custom_serde::iso8601")] PrimitiveDateTime),
    #[serde(rename = "null")]
    SetToNull, // matches "null" string
}
```

Code after:
use std::{collections::HashMap, fs::File, io::BufReader};

use actix_multipart::form::{tempfile::TempFile, MultipartForm};
use actix_web::{HttpResponse, ResponseError};
use common_enums::{CardNetwork, PaymentMethodType};
use common_utils::{events::ApiEventMetric, pii::PhoneNumberStrategy};
use csv::Reader;
use masking::Secret;
use serde::{Deserialize, Serialize};
use time::{Date, PrimitiveDateTime};

#[derive(Debug, Deserialize, Serialize)]
pub struct RevenueRecoveryBackfillRequest {
    pub bin_number: Option<Secret<String>>,
    pub customer_id_resp: String,
    pub connector_payment_id: Option<String>,
    pub token: Option<Secret<String>>,
    pub exp_date: Option<Secret<String>>,
    pub card_network: Option<CardNetwork>,
    pub payment_method_sub_type: Option<PaymentMethodType>,
    pub clean_bank_name: Option<String>,
    pub country_name: Option<String>,
    pub daily_retry_history: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct UnlockStatusResponse {
    pub unlocked: bool,
}

#[derive(Debug, Serialize)]
pub struct RevenueRecoveryDataBackfillResponse {
    pub processed_records: usize,
    pub failed_records: usize,
}

#[derive(Debug, Serialize)]
pub struct CsvParsingResult {
    pub records: Vec<RevenueRecoveryBackfillRequest>,
    pub failed_records: Vec<CsvParsingError>,
}