From d336956a39227d2e99f72210f26e0fc0199734f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matou=C5=A1=20Dzivjak?= Date: Mon, 15 Dec 2025 15:13:21 +0100 Subject: [PATCH] feat(sdk): treat all enums as open enums by default --- codegen/src/schema.rs | 98 ++++++++++++--- sdk/src/resources/common.rs | 217 ++++++++++++++++++++++++++++++--- sdk/src/resources/merchants.rs | 152 +++++++++++++++++++++-- sdk/src/resources/readers.rs | 49 +++++++- 4 files changed, 462 insertions(+), 54 deletions(-) diff --git a/codegen/src/schema.rs b/codegen/src/schema.rs index dad998e..ed4eb99 100644 --- a/codegen/src/schema.rs +++ b/codegen/src/schema.rs @@ -171,25 +171,23 @@ pub fn generate_structs_for_schemas( } openapiv3::SchemaKind::Type(openapiv3::Type::String(s)) => { if !s.enumeration.is_empty() { - let variants_tokens: Vec = s - .enumeration - .iter() - .filter_map(|v| v.as_ref()) - .map(|variant| { - let variant_name = sanitize_enum_variant(variant); - let variant_ident = Ident::new(&variant_name, Span::call_site()); - if variant != &variant_name { - quote! { - #[serde(rename = #variant)] - #variant_ident - } - } else { - quote! { #variant_ident } - } - }) - .collect(); + let mut used_variant_names = HashSet::new(); + let mut variant_entries: Vec<(String, Ident)> = Vec::new(); + + for value in s.enumeration.iter().flatten() { + let mut variant_name = sanitize_enum_variant(value); + if variant_name == "Unknown" { + variant_name.push_str("Value"); + } + while used_variant_names.contains(&variant_name) { + variant_name.push('_'); + } + used_variant_names.insert(variant_name.clone()); + let variant_ident = Ident::new(&variant_name, Span::call_site()); + variant_entries.push((value.clone(), variant_ident)); + } - if !variants_tokens.is_empty() { + if !variant_entries.is_empty() { let description = schema .schema_data .description @@ -198,12 +196,72 @@ pub fn generate_structs_for_schemas( let deprecation = generate_deprecation_attribute(&schema.schema_data); + let variant_defs: Vec<_> = variant_entries + .iter() + .map(|(_, ident)| quote! { #ident }) + .collect(); + + let as_str_matchers: Vec<_> = variant_entries + .iter() + .map(|(value, ident)| { + let value = value.as_str(); + quote! { Self::#ident => #value } + }) + .collect(); + + let deserialize_matchers: Vec<_> = variant_entries + .iter() + .map(|(value, ident)| { + let value = value.as_str(); + quote! { #value => Some(Self::#ident) } + }) + .collect(); + items.push(quote! { #description #deprecation - #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] + #[derive(Debug, Clone, PartialEq, Eq)] pub enum #struct_name { - #(#variants_tokens,)* + #(#variant_defs,)* + #[doc = "Fallback variant for values unknown to this SDK."] + Unknown(String), + } + + impl #struct_name { + pub fn as_str(&self) -> &str { + match self { + #(#as_str_matchers,)* + Self::Unknown(value) => value.as_str(), + } + } + } + + impl serde::Serialize for #struct_name { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } + } + + impl<'de> serde::Deserialize<'de> for #struct_name { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = ::deserialize(deserializer)?; + let known = match value.as_str() { + #(#deserialize_matchers,)* + _ => None, + }; + + if let Some(variant) = known { + Ok(variant) + } else { + Ok(Self::Unknown(value)) + } + } } }); } else { diff --git a/sdk/src/resources/common.rs b/sdk/src/resources/common.rs index 800c02b..88916af 100644 --- a/sdk/src/resources/common.rs +++ b/sdk/src/resources/common.rs @@ -27,7 +27,7 @@ pub type AmountEvent = f32; #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] pub struct Attributes {} /// Three-letter [ISO4217](https://en.wikipedia.org/wiki/ISO_4217) code of the currency for the amount. Currently supported currency values are enumerated above. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Currency { BGN, BRL, @@ -44,6 +44,69 @@ pub enum Currency { RON, SEK, USD, + ///Fallback variant for values unknown to this SDK. + Unknown(String), +} +impl Currency { + pub fn as_str(&self) -> &str { + match self { + Self::BGN => "BGN", + Self::BRL => "BRL", + Self::CHF => "CHF", + Self::CLP => "CLP", + Self::CZK => "CZK", + Self::DKK => "DKK", + Self::EUR => "EUR", + Self::GBP => "GBP", + Self::HRK => "HRK", + Self::HUF => "HUF", + Self::NOK => "NOK", + Self::PLN => "PLN", + Self::RON => "RON", + Self::SEK => "SEK", + Self::USD => "USD", + Self::Unknown(value) => value.as_str(), + } + } +} +impl serde::Serialize for Currency { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} +impl<'de> serde::Deserialize<'de> for Currency { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = ::deserialize(deserializer)?; + let known = match value.as_str() { + "BGN" => Some(Self::BGN), + "BRL" => Some(Self::BRL), + "CHF" => Some(Self::CHF), + "CLP" => Some(Self::CLP), + "CZK" => Some(Self::CZK), + "DKK" => Some(Self::DKK), + "EUR" => Some(Self::EUR), + "GBP" => Some(Self::GBP), + "HRK" => Some(Self::HRK), + "HUF" => Some(Self::HUF), + "NOK" => Some(Self::NOK), + "PLN" => Some(Self::PLN), + "RON" => Some(Self::RON), + "SEK" => Some(Self::SEK), + "USD" => Some(Self::USD), + _ => None, + }; + if let Some(variant) = known { + Ok(variant) + } else { + Ok(Self::Unknown(value)) + } + } } /// Error message structure. #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] @@ -90,32 +153,108 @@ impl std::fmt::Display for ErrorForbidden { impl std::error::Error for ErrorForbidden {} pub type EventId = i64; /// Status of the transaction event. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum EventStatus { - #[serde(rename = "PENDING")] Pending, - #[serde(rename = "SCHEDULED")] Scheduled, - #[serde(rename = "FAILED")] Failed, - #[serde(rename = "REFUNDED")] Refunded, - #[serde(rename = "SUCCESSFUL")] Successful, - #[serde(rename = "PAID_OUT")] PaidOut, + ///Fallback variant for values unknown to this SDK. + Unknown(String), +} +impl EventStatus { + pub fn as_str(&self) -> &str { + match self { + Self::Pending => "PENDING", + Self::Scheduled => "SCHEDULED", + Self::Failed => "FAILED", + Self::Refunded => "REFUNDED", + Self::Successful => "SUCCESSFUL", + Self::PaidOut => "PAID_OUT", + Self::Unknown(value) => value.as_str(), + } + } +} +impl serde::Serialize for EventStatus { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} +impl<'de> serde::Deserialize<'de> for EventStatus { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = ::deserialize(deserializer)?; + let known = match value.as_str() { + "PENDING" => Some(Self::Pending), + "SCHEDULED" => Some(Self::Scheduled), + "FAILED" => Some(Self::Failed), + "REFUNDED" => Some(Self::Refunded), + "SUCCESSFUL" => Some(Self::Successful), + "PAID_OUT" => Some(Self::PaidOut), + _ => None, + }; + if let Some(variant) = known { + Ok(variant) + } else { + Ok(Self::Unknown(value)) + } + } } /// Type of the transaction event. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum EventType { - #[serde(rename = "PAYOUT")] Payout, - #[serde(rename = "CHARGE_BACK")] ChargeBack, - #[serde(rename = "REFUND")] Refund, - #[serde(rename = "PAYOUT_DEDUCTION")] PayoutDeduction, + ///Fallback variant for values unknown to this SDK. + Unknown(String), +} +impl EventType { + pub fn as_str(&self) -> &str { + match self { + Self::Payout => "PAYOUT", + Self::ChargeBack => "CHARGE_BACK", + Self::Refund => "REFUND", + Self::PayoutDeduction => "PAYOUT_DEDUCTION", + Self::Unknown(value) => value.as_str(), + } + } +} +impl serde::Serialize for EventType { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} +impl<'de> serde::Deserialize<'de> for EventType { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = ::deserialize(deserializer)?; + let known = match value.as_str() { + "PAYOUT" => Some(Self::Payout), + "CHARGE_BACK" => Some(Self::ChargeBack), + "REFUND" => Some(Self::Refund), + "PAYOUT_DEDUCTION" => Some(Self::PayoutDeduction), + _ => None, + }; + if let Some(variant) = known { + Ok(variant) + } else { + Ok(Self::Unknown(value)) + } + } } /// Pending invitation for membership. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -139,18 +278,56 @@ pub struct MandateResponse { pub merchant_code: Option, } /// The status of the membership. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum MembershipStatus { - #[serde(rename = "accepted")] Accepted, - #[serde(rename = "pending")] Pending, - #[serde(rename = "expired")] Expired, - #[serde(rename = "disabled")] Disabled, - #[serde(rename = "unknown")] - Unknown, + UnknownValue, + ///Fallback variant for values unknown to this SDK. + Unknown(String), +} +impl MembershipStatus { + pub fn as_str(&self) -> &str { + match self { + Self::Accepted => "accepted", + Self::Pending => "pending", + Self::Expired => "expired", + Self::Disabled => "disabled", + Self::UnknownValue => "unknown", + Self::Unknown(value) => value.as_str(), + } + } +} +impl serde::Serialize for MembershipStatus { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} +impl<'de> serde::Deserialize<'de> for MembershipStatus { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = ::deserialize(deserializer)?; + let known = match value.as_str() { + "accepted" => Some(Self::Accepted), + "pending" => Some(Self::Pending), + "expired" => Some(Self::Expired), + "disabled" => Some(Self::Disabled), + "unknown" => Some(Self::UnknownValue), + _ => None, + }; + if let Some(variant) = known { + Ok(variant) + } else { + Ok(Self::Unknown(value)) + } + } } /// Set of user-defined key-value pairs attached to the object. Partial updates are not supported. When updating, always submit whole metadata. Maximum of 64 parameters are allowed in the object. #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] diff --git a/sdk/src/resources/merchants.rs b/sdk/src/resources/merchants.rs index 8afbf93..6cefc9d 100644 --- a/sdk/src/resources/merchants.rs +++ b/sdk/src/resources/merchants.rs @@ -202,28 +202,164 @@ pub struct CompanyIdentifier { pub type CompanyIdentifiers = Vec; pub type CountryCode = String; /// The category of the error. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum ErrorCategoryClient { - #[serde(rename = "client_error")] ClientError, + ///Fallback variant for values unknown to this SDK. + Unknown(String), +} +impl ErrorCategoryClient { + pub fn as_str(&self) -> &str { + match self { + Self::ClientError => "client_error", + Self::Unknown(value) => value.as_str(), + } + } +} +impl serde::Serialize for ErrorCategoryClient { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} +impl<'de> serde::Deserialize<'de> for ErrorCategoryClient { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = ::deserialize(deserializer)?; + let known = match value.as_str() { + "client_error" => Some(Self::ClientError), + _ => None, + }; + if let Some(variant) = known { + Ok(variant) + } else { + Ok(Self::Unknown(value)) + } + } } /// The category of the error. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum ErrorCategoryServer { - #[serde(rename = "server_error")] ServerError, + ///Fallback variant for values unknown to this SDK. + Unknown(String), +} +impl ErrorCategoryServer { + pub fn as_str(&self) -> &str { + match self { + Self::ServerError => "server_error", + Self::Unknown(value) => value.as_str(), + } + } +} +impl serde::Serialize for ErrorCategoryServer { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} +impl<'de> serde::Deserialize<'de> for ErrorCategoryServer { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = ::deserialize(deserializer)?; + let known = match value.as_str() { + "server_error" => Some(Self::ServerError), + _ => None, + }; + if let Some(variant) = known { + Ok(variant) + } else { + Ok(Self::Unknown(value)) + } + } } /// An error code specifying the exact error that occurred. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum ErrorCodeInternalServerError { - #[serde(rename = "internal_error")] InternalError, + ///Fallback variant for values unknown to this SDK. + Unknown(String), +} +impl ErrorCodeInternalServerError { + pub fn as_str(&self) -> &str { + match self { + Self::InternalError => "internal_error", + Self::Unknown(value) => value.as_str(), + } + } +} +impl serde::Serialize for ErrorCodeInternalServerError { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} +impl<'de> serde::Deserialize<'de> for ErrorCodeInternalServerError { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = ::deserialize(deserializer)?; + let known = match value.as_str() { + "internal_error" => Some(Self::InternalError), + _ => None, + }; + if let Some(variant) = known { + Ok(variant) + } else { + Ok(Self::Unknown(value)) + } + } } /// An error code specifying the exact error that occurred. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum ErrorCodeNotFound { - #[serde(rename = "not_found")] NotFound, + ///Fallback variant for values unknown to this SDK. + Unknown(String), +} +impl ErrorCodeNotFound { + pub fn as_str(&self) -> &str { + match self { + Self::NotFound => "not_found", + Self::Unknown(value) => value.as_str(), + } + } +} +impl serde::Serialize for ErrorCodeNotFound { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} +impl<'de> serde::Deserialize<'de> for ErrorCodeNotFound { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = ::deserialize(deserializer)?; + let known = match value.as_str() { + "not_found" => Some(Self::NotFound), + _ => None, + }; + if let Some(variant) = known { + Ok(variant) + } else { + Ok(Self::Unknown(value)) + } + } } pub type LegalType = String; #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] diff --git a/sdk/src/resources/readers.rs b/sdk/src/resources/readers.rs index 40effdf..45f9d84 100644 --- a/sdk/src/resources/readers.rs +++ b/sdk/src/resources/readers.rs @@ -128,16 +128,53 @@ pub type ReaderPairingCode = String; /// - `processing` - The reader is created and waits for the physical device to confirm the pairing. /// - `paired` - The reader is paired with a merchant account and can be used with SumUp APIs. /// - `expired` - The pairing is expired and no longer usable with the account. The resource needs to get recreated. -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum ReaderStatus { - #[serde(rename = "unknown")] - Unknown, - #[serde(rename = "processing")] + UnknownValue, Processing, - #[serde(rename = "paired")] Paired, - #[serde(rename = "expired")] Expired, + ///Fallback variant for values unknown to this SDK. + Unknown(String), +} +impl ReaderStatus { + pub fn as_str(&self) -> &str { + match self { + Self::UnknownValue => "unknown", + Self::Processing => "processing", + Self::Paired => "paired", + Self::Expired => "expired", + Self::Unknown(value) => value.as_str(), + } + } +} +impl serde::Serialize for ReaderStatus { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} +impl<'de> serde::Deserialize<'de> for ReaderStatus { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = ::deserialize(deserializer)?; + let known = match value.as_str() { + "unknown" => Some(Self::UnknownValue), + "processing" => Some(Self::Processing), + "paired" => Some(Self::Paired), + "expired" => Some(Self::Expired), + _ => None, + }; + if let Some(variant) = known { + Ok(variant) + } else { + Ok(Self::Unknown(value)) + } + } } #[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)] pub struct CreateReaderCheckoutErrorErrors {