Skip to content

feat(gRPC): build gRPC client interface to initiate communication with recovery-decider service #8178

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 150 commits into from
Aug 6, 2025
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
150 commits
Select commit Hold shift + click to select a range
809da65
add address field and attempt_count in stripebilling webhook
Apr 2, 2025
8f160b0
Merge branch 'main' into stripebilling_transaction_monitoring_feilds
Apr 2, 2025
bd411f5
add endtime and start time of invoice in webhook
Apr 3, 2025
2e02b84
ran formatter
Apr 4, 2025
fd47398
add full address with encryption
Apr 4, 2025
4a82642
consumes required fields to support transaction monitoring[chargebee]
Apr 9, 2025
dd531f9
formats the code
Apr 9, 2025
01d98a0
Merge branch 'main' into support_for_storing_address
Apr 9, 2025
653cd5d
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Apr 9, 2025
6cae8e1
addresses clippy v2 failure
Apr 9, 2025
787912a
chore: run formatter
hyperswitch-bot[bot] Apr 9, 2025
a5c5d9a
makes billing address optional
Apr 10, 2025
da18869
Merge branch 'main' into stripebilling_transaction_monitoring_feilds
Apr 10, 2025
f5d5294
Merge branch 'support_for_storing_address' into stripebilling_transac…
Apr 10, 2025
826202a
adds get_billing_address_for_invoice function
Apr 10, 2025
d03c564
invoice from webhook for stripebilling
Apr 11, 2025
24dac12
add bool to get invoice from webhook or additional call
Apr 11, 2025
a4262c7
adds billing connector invoice sync flow
Apr 11, 2025
71ec2bc
typo fix
Apr 11, 2025
3b04911
add invoice sync flow to revenue recovery
Apr 11, 2025
44e5a18
chore: run formatter
hyperswitch-bot[bot] Apr 11, 2025
75ef438
adds config to fetch details
Apr 15, 2025
c53b310
recurly invoice sync connector inetgration support
Apr 17, 2025
0479693
add invoice sync call support in recovery incoming file
Apr 17, 2025
4227dfb
Merge branch 'main' into recurly_invoice_sync_support_with_transactio…
Apr 17, 2025
69d5d24
recoveryincoming changes
Apr 17, 2025
6764550
define ve type and change connector integration v1 to v2
Apr 21, 2025
2fe86f9
resolve clippy fails
Apr 21, 2025
3142502
add support for transaction monitoring for recurly
Apr 22, 2025
49f7a22
Merge branch 'main' into billing_connector_invoice_psync_call
Apr 22, 2025
b4c7e71
Merge branch 'billing_connector_invoice_psync_call' into recurly_invo…
Apr 22, 2025
a9899b5
resolve spell checks
Apr 22, 2025
e93d5a7
resolve spell checks
Apr 22, 2025
5aaaea6
Merge branch 'recurly_invoice_sync_support_with_transaction_monitorin…
Apr 23, 2025
da5a505
chore: run formatter
hyperswitch-bot[bot] Apr 23, 2025
d75b207
format the code and remove print statements
Apr 23, 2025
c9f7aa6
cleaning up recurly code
Apr 24, 2025
9bebc57
fix clippy_errors
Apr 24, 2025
86c9715
removed recurly tests since we dont have any payment related thngs to…
Apr 27, 2025
272c416
resolve clippy error
Apr 28, 2025
7378787
Merge branch 'main' into billing_connector_invoice_psync_call
NISHANTH1221 Apr 28, 2025
23ed82c
resolve clippy errors
Apr 28, 2025
23e13fa
resolve merge conflicts
Apr 28, 2025
9e30a2a
Merge branch 'main' into recurly_invoice_sync_support_with_transactio…
Apr 29, 2025
eaf1e40
resolve clippy errors
Apr 29, 2025
b154311
Merge branch 'recurly_invoice_sync_support_with_transaction_monitorin…
NISHANTH1221 Apr 29, 2025
d2e3caa
remove string based matching
Apr 29, 2025
1110062
chore: run formatter
hyperswitch-bot[bot] Apr 29, 2025
7170f7a
Merge branch 'main' into recurly_invoice_sync_support_with_transactio…
Apr 29, 2025
79d8e64
resolve error in clippy-v2
Apr 29, 2025
964fd78
removes unncessary billing address function in Incoming Webhook Trait
Apr 29, 2025
fda03ea
recurly add comment on why we dont need amount convertor
Apr 29, 2025
3e619b7
format the code
Apr 29, 2025
054bf7f
Merge branch 'recurly_invoice_sync_support_with_transaction_monitorin…
Apr 29, 2025
f916cb5
add white space
Apr 29, 2025
f0fe9d5
Merge branch 'main' into stripebilling_transaction_monitoring_feilds
May 2, 2025
1fdab57
consume card details from billing connectors
May 21, 2025
e1483a3
Merge branch 'main' into add_card_information_for_billing_connectors
May 28, 2025
264766f
small bug fix for stripebilling external payments sync flow
May 28, 2025
738037d
build gRPC Client Interface to initiate communication with recovery-t…
AdityaKumaar21 May 29, 2025
167dccd
chore: run formatter
hyperswitch-bot[bot] May 29, 2025
720b422
clippy fix
AdityaKumaar21 May 29, 2025
86a63b6
chore: run formatter
hyperswitch-bot[bot] May 29, 2025
0a516f6
Merge branch 'main' into build-grpc-client-recovery
AdityaKumaar21 May 29, 2025
4c0503d
add connector level support to consume card network and card bin
Jun 2, 2025
48214ee
introducing apis for trainer and decider
AdityaKumaar21 Jun 3, 2025
1b619b3
Delete crates/external_services/src/grpc_client/recovery_trainer_clie…
AdityaKumaar21 Jun 3, 2025
ae153fb
Merge branch 'main' into build-grpc-client-recovery
AdityaKumaar21 Jun 4, 2025
8d78311
Delete proto/recovery_trainer.proto
AdityaKumaar21 Jun 4, 2025
0177ba4
chore: update Cargo.lock
hyperswitch-bot[bot] Jun 4, 2025
fe2af2e
refactor recovery decider client and update proto definitions
AdityaKumaar21 Jun 4, 2025
77b750d
fix clippy
AdityaKumaar21 Jun 4, 2025
32a74f9
chore: run formatter
hyperswitch-bot[bot] Jun 4, 2025
4386261
Final Adjustments
Jun 4, 2025
a4aac2a
chore: run formatter
hyperswitch-bot[bot] Jun 4, 2025
1bdedf7
add card network in stripe billing
Jun 5, 2025
98aeceb
add open api changes
Jun 5, 2025
f478efb
Merge branch 'main' into add_card_information_for_billing_connectors
NISHANTH1221 Jun 5, 2025
38378f9
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Jun 5, 2025
d4b6025
add error codes
Jun 6, 2025
b7fc94e
bring back v1 from v2
Jun 8, 2025
ad3beaa
chore: run formatter
hyperswitch-bot[bot] Jun 8, 2025
c6ed016
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Jun 8, 2025
49e68b5
write correct example for card issuer
Jun 8, 2025
35eae6a
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Jun 8, 2025
9770ff6
resolves comments
Jun 10, 2025
60d745a
decider refactor
AdityaKumaar21 Jun 10, 2025
bd1913e
Rename comments
NISHANTH1221 Jun 11, 2025
f89bf01
chore: run formatter
hyperswitch-bot[bot] Jun 11, 2025
88671ca
Merge branch 'main' into add_card_information_for_billing_connectors
NISHANTH1221 Jun 11, 2025
576b4ab
removed defaults
AdityaKumaar21 Jun 11, 2025
5e7297d
Merge branch 'main' into build-grpc-client-recovery
AdityaKumaar21 Jun 11, 2025
4c50e55
chore: run formatter
hyperswitch-bot[bot] Jun 11, 2025
71e0b4e
refactor: remove TrainerClient and related gRPC functionality
AdityaKumaar21 Jun 11, 2025
3d21400
chore: run formatter
hyperswitch-bot[bot] Jun 11, 2025
ffd75fe
add new BillingConnectorPaymentMethodDetails enum
Jun 11, 2025
2b1d402
chore: run formatter
hyperswitch-bot[bot] Jun 11, 2025
401c7b5
Merge branch 'main' into add_card_information_for_billing_connectors
Jun 12, 2025
aa325bd
make the enum optional
Jun 12, 2025
feb3f88
chore: run formatter
hyperswitch-bot[bot] Jun 12, 2025
89405fb
revert default version and add box pin
Jun 12, 2025
5cead6b
add v2 feeature flag on conversions
Jun 12, 2025
d62ba13
fix proxy
AdityaKumaar21 Jun 12, 2025
5de0d97
add type serde tag and write an time stamp desrializing tag on primit…
Jun 12, 2025
1b768bf
Merge branch 'main' into add_card_information_for_billing_connectors
NISHANTH1221 Jun 12, 2025
0c01259
Merge branch 'add_card_information_for_billing_connectors' into build…
AdityaKumaar21 Jun 13, 2025
4edb072
chore: run formatter
hyperswitch-bot[bot] Jun 13, 2025
f66a288
addressing comments
AdityaKumaar21 Jun 16, 2025
dfdb3e3
Merge remote-tracking branch 'origin' into build-grpc-client-recovery
AdityaKumaar21 Jun 16, 2025
51deffb
fix clippy
AdityaKumaar21 Jun 16, 2025
c26bded
addressing comments
AdityaKumaar21 Jun 17, 2025
77bcbd5
docs(openapi): re-generate OpenAPI specification
hyperswitch-bot[bot] Jun 17, 2025
3aed65b
fix clippy
AdityaKumaar21 Jun 17, 2025
dea1639
chore: run formatter
hyperswitch-bot[bot] Jun 17, 2025
fa5f1ce
add grpc-recovery-header
AdityaKumaar21 Jun 17, 2025
9ecd485
fix clippy
AdityaKumaar21 Jun 17, 2025
3eae00b
addressing comment
AdityaKumaar21 Jun 18, 2025
a9d90a1
refactoring
AdityaKumaar21 Jun 18, 2025
060a33a
Merge branch 'main' into build-grpc-client-recovery
AdityaKumaar21 Jun 19, 2025
273acd1
fix feature flags
AdityaKumaar21 Jun 19, 2025
0536d0e
Merge branch 'main' into build-grpc-client-recovery
AdityaKumaar21 Jun 20, 2025
abf8d50
Merge branch 'main' into build-grpc-client-recovery
AdityaKumaar21 Jun 22, 2025
8dd5919
minor changes
AdityaKumaar21 Jun 24, 2025
baeb245
minor fix
AdityaKumaar21 Jun 24, 2025
7e81472
chore: run formatter
hyperswitch-bot[bot] Jun 24, 2025
920f211
fix clippy
AdityaKumaar21 Jun 24, 2025
20a864f
chore: bump tonic version from 0.12 to 0.13
Aprabhat19 Jun 25, 2025
2bb5234
Merge remote-tracking branch 'origin/bump_tonic' into build-grpc-clie…
AdityaKumaar21 Jun 25, 2025
4b269f1
Merge branch 'main' into build-grpc-client-recovery
AdityaKumaar21 Jun 25, 2025
26fee44
Merge remote-tracking branch 'origin' into build-grpc-client-recovery
AdityaKumaar21 Jun 26, 2025
ff53383
Merge remote-tracking branch 'origin' into build-grpc-client-recovery
AdityaKumaar21 Jul 30, 2025
f791524
feat(revenue_recovery): Introduce recovery timestamp configuration an…
AdityaKumaar21 Jul 30, 2025
3818225
Merge branch 'main' into build-grpc-client-recovery
AdityaKumaar21 Aug 1, 2025
5da1854
fix clippy
AdityaKumaar21 Aug 1, 2025
01aaa59
chore: run formatter
hyperswitch-bot[bot] Aug 1, 2025
553f094
errors fixed
AdityaKumaar21 Aug 4, 2025
da3304a
chore: run formatter
hyperswitch-bot[bot] Aug 4, 2025
e810940
Update build.rs
AdityaKumaar21 Aug 4, 2025
baee0b3
Update Cargo.toml
AdityaKumaar21 Aug 4, 2025
99daa29
resolving comments
AdityaKumaar21 Aug 4, 2025
560a72b
addressing comments
AdityaKumaar21 Aug 5, 2025
7bd8144
fix clippy
AdityaKumaar21 Aug 5, 2025
45435cc
chore: run formatter
hyperswitch-bot[bot] Aug 5, 2025
59f5a77
fix clippy
AdityaKumaar21 Aug 5, 2025
bdddba9
Merge branch 'main' into build-grpc-client-recovery
AdityaKumaar21 Aug 5, 2025
4e19f8f
adding more field in decider request and update the monitoring thresh…
AdityaKumaar21 Aug 6, 2025
d973789
formatting
AdityaKumaar21 Aug 6, 2025
bbdc2a6
adding more fields in decider request
AdityaKumaar21 Aug 6, 2025
c9031ce
Merge branch 'main' into build-grpc-client-recovery
AdityaKumaar21 Aug 6, 2025
ee1735d
chore: run formatter
hyperswitch-bot[bot] Aug 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions config/development.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1145,6 +1145,14 @@ host = "localhost"
port = 8000
service = "dynamo"

[grpc_client.recovery_decider_client]
host = "127.0.0.1"
port = 8000

[grpc_client.trainer_client]
host = "0.0.0.0"
port = 50051

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Invalid host for trainer client
Using 0.0.0.0 as a remote host is not valid for client connections. Replace with a resolvable address (e.g., 127.0.0.1 or localhost).

Apply this diff:

-[grpc_client.trainer_client]
-host = "0.0.0.0"
+[grpc_client.trainer_client]
+host = "127.0.0.1"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[grpc_client.trainer_client]
host = "0.0.0.0"
port = 50051
[grpc_client.trainer_client]
host = "127.0.0.1"
port = 50051
🤖 Prompt for AI Agents
In config/development.toml around lines 1140 to 1143, the
grpc_client.trainer_client host is set to "0.0.0.0", which is invalid for client
connections. Change the host value to a resolvable address such as "127.0.0.1"
or "localhost" to ensure proper connectivity.

[theme.storage]
file_storage_backend = "file_system" # Theme storage backend to be used

Expand Down
1 change: 1 addition & 0 deletions crates/common_utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ nutype = { version = "0.4.3", features = ["serde"] }
once_cell = "1.21.3"
openssl = {version = "0.10.72", optional = true}
phonenumber = "0.3.7"
prost-types = { version = "0.13" }
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

prost-types should be an optional, feature-gated dependency

This crate is pulled in by almost every binary in the workspace. Unconditionally compiling prost-types = 0.13 forces the full protobuf stack (and its transitive deps) on all build profiles – even when the consumer is not enabling any gRPC-related features.

Consider:

-prost-types = { version = "0.13" }
+prost-types = { version = "0.13", optional = true }

and then exposing it under an explicit feature (e.g. grpc or prost) inside [features].
This keeps compile times down for non-gRPC builds and avoids multiple prost-* versions sneaking into the dependency graph.

Also double-check that every crate depending on prost/prost-types sticks to the same minor version (0.13.x) to prevent duplicate builds.


🏁 Script executed:

#!/usr/bin/env bash
# 1. Check if common_utils has any features defined around prost-types
echo "=== Features in crates/common_utils/Cargo.toml ==="
rg -n '^\[features\]' crates/common_utils/Cargo.toml -A 20

# 2. List all workspace crates that depend on common_utils
echo -e "\n=== Crates depending on common_utils ==="
rg -l 'common_utils *= ' -t toml

# 3. Find all instances of prost-types in Cargo.toml files
echo -e "\n=== prost-types usages ==="
rg -n 'prost-types' -t toml

# 4. Find all instances of prost in Cargo.toml files
echo -e "\n=== prost = usages ==="
rg -n 'prost *= ' -t toml

Length of output: 2455


prost-types should be feature-gated in common_utils

To avoid dragging in the full Protobuf stack for every build, mark prost-types as optional in crates/common_utils and expose it under an explicit feature (e.g. grpc). Then have only the gRPC-using crates opt in.

• crates/common_utils/Cargo.toml

 [features]
   default = []
   keymanager = ["dep:router_env"]
   …  
   tokenization_v2 = []
+  grpc = ["dep:prost-types"]

 [dependencies]
-prost-types = { version = "0.13" }
+prost-types = { version = "0.13", optional = true }

• crates/router/Cargo.toml
– Remove or mark prost-types as optional.
– Enable the new feature on common_utils instead of pulling in prost-types directly:

common_utils = { path = "../common_utils", features = ["grpc"] }

• crates/external_services/Cargo.toml
– Prefer using common_utils[grpc] over a standalone prost-types dependency, or if you keep your own, ensure it remains optional = true and you activate it via a feature.

Finally, confirm every prost/prost-types entry across the workspace stays on 0.13.x to prevent version duplication.

🤖 Prompt for AI Agents
In crates/common_utils/Cargo.toml at line 46, the prost-types dependency is
currently unconditional, which causes the full protobuf stack to be included in
all builds. Modify the prost-types dependency to be optional by adding `optional
= true` and then define a new feature (e.g., "grpc") in the [features] section
that includes prost-types. Update crates/router/Cargo.toml and
crates/external_services/Cargo.toml to remove direct prost-types dependencies or
mark them optional, and instead enable the new "grpc" feature on common_utils to
pull in prost-types transitively. Finally, verify that all prost and prost-types
dependencies across the workspace use the same minor version 0.13.x to avoid
duplicate builds.

quick-xml = { version = "0.31.0", features = ["serialize"] }
rand = "0.8.5"
regex = "1.11.1"
Expand Down
2 changes: 2 additions & 0 deletions crates/common_utils/src/custom_serde.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
//! Custom serialization/deserialization implementations.
/// Serde helpers for `prost_types::Timestamp` and `Option<prost_types::Timestamp>`.
pub mod prost_timestamp;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Missing cfg-gate mirrors unconditional dep

The new prost_timestamp helper is great, but because the module is compiled unconditionally it implicitly requires the prost-types crate for every build of common_utils (see previous comment).
If you make prost-types optional, protect this module too:

-/// Serde helpers for `prost_types::Timestamp` and `Option<prost_types::Timestamp>`.
-pub mod prost_timestamp;
+/// Serde helpers for `prost_types::Timestamp` and `Option<prost_types::Timestamp>`.
+#[cfg(feature = "prost")]
+pub mod prost_timestamp;

(Or re-use whatever feature name you decide on.)
Without the guard the crate will not compile when that feature is disabled.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Serde helpers for `prost_types::Timestamp` and `Option<prost_types::Timestamp>`.
pub mod prost_timestamp;
/// Serde helpers for `prost_types::Timestamp` and `Option<prost_types::Timestamp>`.
#[cfg(feature = "prost")]
pub mod prost_timestamp;
🤖 Prompt for AI Agents
In crates/common_utils/src/custom_serde.rs at lines 2 to 3, the prost_timestamp
module is included unconditionally, which requires the prost-types crate for
every build. To fix this, add a cfg attribute to conditionally compile the
prost_timestamp module only when the corresponding feature (e.g., "prost-types")
is enabled. This ensures the crate compiles successfully when the feature is
disabled and aligns with the optional dependency setup.


/// Use the well-known ISO 8601 format when serializing and deserializing an
/// [`PrimitiveDateTime`][PrimitiveDateTime].
Expand Down
80 changes: 80 additions & 0 deletions crates/common_utils/src/custom_serde/prost_timestamp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use prost_types::Timestamp;
use serde::{Deserialize, Deserializer, Serialize, Serializer};

/// A wrapper around `prost_types::Timestamp` to enable custom Serde implementations.
#[derive(Debug, Clone, PartialEq)]
pub struct SerializableTimestamp(pub Timestamp);

impl From<Timestamp> for SerializableTimestamp {
fn from(ts: Timestamp) -> Self {
Self(ts)
}
}

impl From<SerializableTimestamp> for Timestamp {
fn from(sts: SerializableTimestamp) -> Self {
sts.0
}
}

// Helper struct for serializing/deserializing the fields of Timestamp
#[derive(Serialize, Deserialize)]
struct TimestampFields {
seconds: i64,
nanos: i32,
}

impl Serialize for SerializableTimestamp {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let fields = TimestampFields {
seconds: self.0.seconds,
nanos: self.0.nanos,
};
fields.serialize(serializer)
}
}

impl<'de> Deserialize<'de> for SerializableTimestamp {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let fields = TimestampFields::deserialize(deserializer)?;
Ok(Self(Timestamp {
seconds: fields.seconds,
nanos: fields.nanos,
}))
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add validation for the nanos field during deserialization.

The nanos field in protobuf timestamps must be between 0 and 999,999,999. Invalid values could cause issues when converting to other time representations.

Apply this diff to add validation:

 impl<'de> Deserialize<'de> for SerializableTimestamp {
     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
     where
         D: Deserializer<'de>,
     {
         let fields = TimestampFields::deserialize(deserializer)?;
+        if fields.nanos < 0 || fields.nanos > 999_999_999 {
+            return Err(serde::de::Error::custom(format!(
+                "nanos field must be between 0 and 999,999,999, got {}",
+                fields.nanos
+            )));
+        }
         Ok(Self(Timestamp {
             seconds: fields.seconds,
             nanos: fields.nanos,
         }))
     }
 }
🤖 Prompt for AI Agents
In crates/common_utils/src/custom_serde/prost_timestamp.rs around lines 40 to
51, the deserialization implementation for SerializableTimestamp lacks
validation for the nanos field. To fix this, add a check after deserializing
TimestampFields to ensure that the nanos value is between 0 and 999,999,999
inclusive. If the nanos value is out of this range, return a deserialization
error using D::Error::custom with an appropriate error message. This validation
prevents invalid protobuf timestamp values from causing issues downstream.


/// Serde module for `Option<SerializableTimestamp>`.
pub mod optional_prost_timestamp {
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use super::SerializableTimestamp;

/// Serializes `Option<SerializableTimestamp>`.
pub fn serialize<S>(
option_timestamp: &Option<SerializableTimestamp>,
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match option_timestamp {
Some(timestamp) => timestamp.serialize(serializer), // Directly use SerializableTimestamp's Serialize impl
None => serializer.serialize_none(),
}
}

/// Deserializes `Option<SerializableTimestamp>`.
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<SerializableTimestamp>, D::Error>
where
D: Deserializer<'de>,
{
Option::<SerializableTimestamp>::deserialize(deserializer) // Directly use SerializableTimestamp's Deserialize impl
}
}
23 changes: 23 additions & 0 deletions crates/common_utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ pub mod date_time {
use std::{marker::PhantomData, num::NonZeroU8};

use masking::{Deserialize, Serialize};
use prost_types::Timestamp;
use time::{
format_description::{
well_known::iso8601::{Config, EncodedConfig, Iso8601, TimePrecision},
Expand Down Expand Up @@ -203,6 +204,28 @@ pub mod date_time {
f.write_str(&output)
}
}

/// Converts a `time::PrimitiveDateTime` to a `prost_types::Timestamp`.
/// Assumes UTC for the conversion.
pub fn convert_to_prost_timestamp(dt: PrimitiveDateTime) -> Timestamp {
let odt = dt.assume_utc();
Timestamp {
seconds: odt.unix_timestamp(),
// This conversion is safe as nanoseconds (0..999_999_999) always fit within an i32.
#[allow(clippy::as_conversions)]
nanos: odt.nanosecond() as i32,
}
}

/// Converts a `prost_types::Timestamp` to an `Option<time::PrimitiveDateTime>`.
/// Returns `None` if the timestamp is out of range for `OffsetDateTime`.
pub fn convert_from_prost_timestamp(ts: &Timestamp) -> Option<PrimitiveDateTime> {
OffsetDateTime::from_unix_timestamp_nanos(
i128::from(ts.seconds) * 1_000_000_000 + i128::from(ts.nanos),
)
.ok()
.map(|odt| PrimitiveDateTime::new(odt.date(), odt.time()))
}
}

/// Generate a nanoid with the given prefix and length
Expand Down
12 changes: 12 additions & 0 deletions crates/external_services/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ email = ["dep:aws-config"]
aws_s3 = ["dep:aws-config", "dep:aws-sdk-s3"]
hashicorp-vault = ["dep:vaultrs"]
v1 = ["hyperswitch_interfaces/v1", "common_utils/v1"]
v2 = [
"dep:prost",
"dep:prost-types",
"dep:tonic",
"dep:tonic-build",
"dep:router_env",
"tokio/macros",
"tokio/rt-multi-thread",
"dep:hyper-util",
"dep:http-body-util"
]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

v2 feature leaks build-time deps into runtime set

tonic-build is a compile-time code-generator; bundling it in a runtime feature will pull it into the final binary and the dependency graph unnecessarily.

Consider moving only tonic-build under [build-dependencies] (it is already there) and remove it from the v2 feature list.

-    "dep:tonic-build",

Same applies to any other build-only crates you might add later.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
v2 = [
"dep:prost",
"dep:prost-types",
"dep:tonic",
"dep:tonic-build",
"dep:router_env",
"tokio/macros",
"tokio/rt-multi-thread",
"dep:hyper-util",
"dep:http-body-util"
]
v2 = [
"dep:prost",
"dep:prost-types",
"dep:tonic",
"dep:router_env",
"tokio/macros",
"tokio/rt-multi-thread",
"dep:hyper-util",
"dep:http-body-util"
]
🤖 Prompt for AI Agents
In crates/external_services/Cargo.toml between lines 16 and 26, the v2 feature
list incorrectly includes tonic-build, which is a build-time only dependency.
Remove tonic-build from the v2 feature array to prevent it from being included
in the runtime dependencies and final binary. Ensure tonic-build remains only
under [build-dependencies] as it is already declared there. Review other
build-only crates to keep them out of runtime features similarly.

dynamic_routing = [
"dep:prost",
"dep:tonic",
Expand Down Expand Up @@ -47,6 +58,7 @@ serde = { version = "1.0.219", features = ["derive"] }
thiserror = "1.0.69"
vaultrs = { version = "0.7.4", optional = true }
prost = { version = "0.13", optional = true }
prost-types = { version = "0.13" , optional = true}
tokio = "1.45.1"
tonic = { version = "0.12.3", optional = true }
tonic-reflection = { version = "0.12.3", optional = true }
Expand Down
59 changes: 45 additions & 14 deletions crates/external_services/build.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,60 @@
#[allow(clippy::expect_used)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(any(feature = "dynamic_routing", feature = "v2"))]
compile_protos()?;

Ok(())
}

#[cfg(any(feature = "dynamic_routing", feature = "v2"))]
fn compile_protos() -> Result<(), Box<dyn std::error::Error>> {
let mut proto_files_to_compile = Vec::new();
let proto_base_path = router_env::workspace_path().join("proto");

#[cfg(feature = "dynamic_routing")]
{
// Get the directory of the current crate
proto_files_to_compile.push(proto_base_path.join("success_rate.proto"));
proto_files_to_compile.push(proto_base_path.join("contract_routing.proto"));
proto_files_to_compile.push(proto_base_path.join("elimination_rate.proto"));
proto_files_to_compile.push(proto_base_path.join("health_check.proto"));
}

#[cfg(feature = "v2")]
{
proto_files_to_compile.push(proto_base_path.join("recovery_decider.proto"));
proto_files_to_compile.push(proto_base_path.join("trainer_client.proto"));
}

if !proto_files_to_compile.is_empty() {
// Ensure proto files are unique in case a file is needed by multiple features
proto_files_to_compile.sort();
proto_files_to_compile.dedup();

let proto_path = router_env::workspace_path().join("proto");
let success_rate_proto_file = proto_path.join("success_rate.proto");
let contract_routing_proto_file = proto_path.join("contract_routing.proto");
let elimination_proto_file = proto_path.join("elimination_rate.proto");
let health_check_proto_file = proto_path.join("health_check.proto");
let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR")?);

// Compile the .proto file
tonic_build::configure()
.out_dir(out_dir)
.compile(
&[
success_rate_proto_file,
health_check_proto_file,
elimination_proto_file,
contract_routing_proto_file,
],
&[proto_path],
.compile_well_known_types(true)
.extern_path(".google.protobuf.Timestamp", "::prost_types::Timestamp")
.type_attribute(
"trainer.TriggerTrainingRequest",
"#[derive(masking::Deserialize, masking::Serialize)]",
)
.type_attribute(
"trainer.TriggerTrainingResponse",
"#[derive(serde::Serialize)]",
)
.type_attribute(
"trainer.GetTrainingJobStatusResponse",
"#[derive(serde::Serialize)]",
)
.type_attribute(
"google.protobuf.Timestamp",
"#[derive(serde::Serialize, serde::Deserialize)]",
)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

type_attribute on google.protobuf.Timestamp is ineffective and breaks with extern_path

Because compile_well_known_types(true) + extern_path(".google.protobuf.Timestamp", "::prost_types::Timestamp")
re-uses the prost_types::Timestamp definition, the build script does not generate a new Rust struct for the proto Timestamp.
Applying a type_attribute to a type that is not generated is ignored by tonic_build (at best) or fails with
no messages matched” (with newer tonic).

Instead, enable the serde feature on the prost-types crate in Cargo.toml – the upstream type already derives the required traits:

prost-types = { version = "0.12", features = ["serde"] }

and delete the redundant attribute:

-            .type_attribute(
-                "google.protobuf.Timestamp",
-                "#[derive(serde::Serialize, serde::Deserialize)]",
-            )

This keeps the build script leaner and future-proof.

🤖 Prompt for AI Agents
In crates/external_services/build.rs between lines 37 and 56, the type_attribute
applied to "google.protobuf.Timestamp" is ineffective and causes errors because
the type is reused from prost_types via extern_path and not generated by
tonic_build. To fix this, remove the type_attribute line for
"google.protobuf.Timestamp" entirely and instead enable the "serde" feature on
the prost-types crate in Cargo.toml by adding features = ["serde"] to its
dependency declaration. This avoids redundant attributes and ensures the
Timestamp type derives the necessary traits.

.expect("Failed to compile proto files");
.compile(&proto_files_to_compile, &[proto_base_path])?;
}
Ok(())
}
Loading
Loading