Bug: Eliminate Lack of Diagnostics Information because of macro panicking



Proc macro should not panic.  When proc macro panics, it will be the only error message returned by the rust compiler. Instead, in case of errors, proc macro should emit compile_error!() to communicate error to the user. This will allow other errors to be displayed.

https://github.com/juspay/orca/blob/01cafe753bc4ea0cf33ec604dcbe0017abfebcad/crates/router_derive/src/macros/operation.rs#L92
diff --git a/crates/router_derive/src/lib.rs b/crates/router_derive/src/lib.rs
index 8f9015d484c..e1e04d0b818 100644
--- a/crates/router_derive/src/lib.rs
+++ b/crates/router_derive/src/lib.rs
@@ -427,5 +427,6 @@ pub fn api_error_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStre
 /// used by `diesel`.
 #[proc_macro_derive(PaymentOperation, attributes(operation))]
 pub fn operation_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
-    macros::operation_derive_inner(input)
+    let input = syn::parse_macro_input!(input as syn::DeriveInput);
+    macros::operation_derive_inner(input).unwrap_or_else(|err| err.to_compile_error().into())
 }
diff --git a/crates/router_derive/src/macros/helpers.rs b/crates/router_derive/src/macros/helpers.rs
index 132dd528a44..ee1aae15846 100644
--- a/crates/router_derive/src/macros/helpers.rs
+++ b/crates/router_derive/src/macros/helpers.rs
@@ -19,6 +19,10 @@ pub(super) fn occurrence_error<T: ToTokens>(
     error
 }
 
+pub(super) fn syn_error(span: Span, message: &str) -> syn::Error {
+    syn::Error::new(span, message)
+}
+
 pub(super) fn get_metadata_inner<'a, T: Parse + Spanned>(
     ident: &str,
     attrs: impl IntoIterator<Item = &'a Attribute>,
diff --git a/crates/router_derive/src/macros/operation.rs b/crates/router_derive/src/macros/operation.rs
index bee3185b444..19797ae8c2b 100644
--- a/crates/router_derive/src/macros/operation.rs
+++ b/crates/router_derive/src/macros/operation.rs
@@ -2,7 +2,9 @@ use std::collections::HashMap;
 
 use proc_macro2::{Span, TokenStream};
 use quote::quote;
-use syn::{self, parse_macro_input, DeriveInput, Lit, Meta, MetaNameValue, NestedMeta};
+use syn::{self, spanned::Spanned, DeriveInput, Lit, Meta, MetaNameValue, NestedMeta};
+
+use crate::macros::helpers;
 
 #[derive(Debug, Clone, Copy)]
 enum Derives {
@@ -80,6 +82,7 @@ enum Conversion {
     UpdateTracker,
     PostUpdateTracker,
     All,
+    Invalid(String),
 }
 
 impl From<String> for Conversion {
@@ -91,8 +94,7 @@ impl From<String> for Conversion {
             "update_tracker" => Self::UpdateTracker,
             "post_tracker" => Self::PostUpdateTracker,
             "all" => Self::All,
-            #[allow(clippy::panic)] // FIXME: Use `compile_error!()` instead
-            _ => panic!("Invalid conversion identifier {}", s),
+            s => Self::Invalid(s.to_string()),
         }
     }
 }
@@ -144,6 +146,10 @@ impl Conversion {
                     Ok(self)
                 }
             },
+            Self::Invalid(s) => {
+                helpers::syn_error(Span::call_site(), &format!("Invalid identifier {s}"))
+                    .to_compile_error()
+            }
             Self::All => {
                 let validate_request = Self::ValidateRequest.to_function(ident);
                 let get_tracker = Self::GetTracker.to_function(ident);
@@ -188,6 +194,10 @@ impl Conversion {
                     Ok(*self)
                 }
             },
+            Self::Invalid(s) => {
+                helpers::syn_error(Span::call_site(), &format!("Invalid identifier {s}"))
+                    .to_compile_error()
+            }
             Self::All => {
                 let validate_request = Self::ValidateRequest.to_ref_function(ident);
                 let get_tracker = Self::GetTracker.to_ref_function(ident);
@@ -205,8 +215,7 @@ impl Conversion {
     }
 }
 
-fn find_operation_attr(a: &[syn::Attribute]) -> syn::Attribute {
-    #[allow(clippy::expect_used)] // FIXME: Use `compile_error!()` instead
+fn find_operation_attr(a: &[syn::Attribute]) -> syn::Result<syn::Attribute> {
     a.iter()
         .find(|a| {
             a.path
@@ -214,11 +223,16 @@ fn find_operation_attr(a: &[syn::Attribute]) -> syn::Attribute {
                 .map(|ident| *ident == "operation")
                 .unwrap_or(false)
         })
-        .expect("Missing 'operation' attribute with conversions")
-        .clone()
+        .cloned()
+        .ok_or_else(|| {
+            helpers::syn_error(
+                Span::call_site(),
+                "Cannot find attribute 'operation' in the macro",
+            )
+        })
 }
 
-fn find_value(v: NestedMeta) -> Option<(String, Vec<String>)> {
+fn find_value(v: &NestedMeta) -> Option<(String, Vec<String>)> {
     match v {
         NestedMeta::Meta(Meta::NameValue(MetaNameValue {
             ref path,
@@ -235,9 +249,7 @@ fn find_value(v: NestedMeta) -> Option<(String, Vec<String>)> {
     }
 }
 
-// FIXME: Propagate errors in a better manner instead of `expect()`, maybe use `compile_error!()`
-#[allow(clippy::unwrap_used, clippy::expect_used)]
-fn find_properties(attr: &syn::Attribute) -> Option<HashMap<String, Vec<String>>> {
+fn find_properties(attr: &syn::Attribute) -> syn::Result<HashMap<String, Vec<String>>> {
     let meta = attr.parse_meta();
     match meta {
         Ok(syn::Meta::List(syn::MetaList {
@@ -245,26 +257,64 @@ fn find_properties(attr: &syn::Attribute) -> Option<HashMap<String, Vec<String>>
             paren_token: _,
             mut nested,
         })) => {
-            path.get_ident().map(|i| i == "operation")?;
-            let tracker = nested.pop().unwrap().into_value();
-            let operation = nested.pop().unwrap().into_value();
-            let o = find_value(tracker).expect("Invalid format of attributes");
-            let t = find_value(operation).expect("Invalid format of attributes");
-            Some(HashMap::from_iter([t, o]))
+            path.get_ident().map(|i| i == "operation").ok_or_else(|| {
+                helpers::syn_error(path.span(), "Attribute 'operation' was not found")
+            })?;
+            let tracker = nested
+                .pop()
+                .ok_or_else(|| {
+                    helpers::syn_error(
+                        nested.span(),
+                        "Invalid format of attributes. Expected format is ops=...,flow=...",
+                    )
+                })?
+                .into_value();
+            let operation = nested
+                .pop()
+                .ok_or_else(|| {
+                    helpers::syn_error(
+                        nested.span(),
+                        "Invalid format of attributes. Expected format is ops=...,flow=...",
+                    )
+                })?
+                .into_value();
+            let o = find_value(&tracker).ok_or_else(|| {
+                helpers::syn_error(
+                    tracker.span(),
+                    "Invalid format of attributes. Expected format is ops=...,flow=...",
+                )
+            })?;
+            let t = find_value(&operation).ok_or_else(|| {
+                helpers::syn_error(
+                    operation.span(),
+                    "Invalid format of attributes. Expected format is ops=...,flow=...",
+                )
+            })?;
+            Ok(HashMap::from_iter([t, o]))
         }
-        _ => None,
+        _ => Err(helpers::syn_error(
+            attr.span(),
+            "No attributes were found. Expected format is ops=..,flow=..",
+        )),
     }
 }
 
-// FIXME: Propagate errors in a better manner instead of `expect()`, maybe use `compile_error!()`
-#[allow(clippy::expect_used)]
-pub fn operation_derive_inner(token: proc_macro::TokenStream) -> proc_macro::TokenStream {
-    let input = parse_macro_input!(token as DeriveInput);
+pub fn operation_derive_inner(input: DeriveInput) -> syn::Result<proc_macro::TokenStream> {
     let struct_name = &input.ident;
-    let op = find_operation_attr(&input.attrs);
-    let prop = find_properties(&op);
-    let ops = prop.as_ref().expect("Invalid Properties")["ops"].clone();
-    let flow = prop.as_ref().expect("Invalid Properties")["flow"].clone();
+    let op = find_operation_attr(&input.attrs)?;
+    let prop = find_properties(&op)?;
+    let ops = prop.get("ops").ok_or_else(|| {
+        helpers::syn_error(
+            op.span(),
+            "Invalid properties. Property 'ops' was not found",
+        )
+    })?;
+    let flow = prop.get("flow").ok_or_else(|| {
+        helpers::syn_error(
+            op.span(),
+            "Invalid properties. Property 'flow' was not found",
+        )
+    })?;
     let trait_derive = flow.iter().map(|derive| {
         let derive: Derives = derive.to_owned().into();
         let fns = ops.iter().map(|t| {
@@ -315,5 +365,5 @@ pub fn operation_derive_inner(token: proc_macro::TokenStream) -> proc_macro::Tok
                 #trait_derive
             };
     };
-    proc_macro::TokenStream::from(output)
+    Ok(proc_macro::TokenStream::from(output))
 }

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [x] Enhancement
- [ ] Refactoring
- [ ] Dependency updates

## Description
<!-- Describe your changes in detail -->
Use compile_error in proc macros instead of panic.

### Additional Changes

- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!-- 
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless its an obvious bug or documentation fix
that will have little conversation).
-->
This closes #115 

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
manual.

## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed submitted code
- [ ] I added unit tests for my changes where possible
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
