OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
20.62k stars 6.29k forks source link

[BUG][RUST] Parameter with empty schema produces invalid rust code #18526

Open felixauringer opened 2 months ago

felixauringer commented 2 months ago

Bug Report Checklist

Description

If a request parameter has an empty schema (schema: {}), the produces rust code is invalid (does not compile). According to the OpenAPI validator linked above and the official specification, this is a valid spec.

The OpenAPI spec below produces a function with the following signature:

pub fn get_test(configuration: &configuration::Configuration, param: models::serde_json::Value) -> Result<(), Error<GetTestError>>

This cannot be compiled, the error message is could not find 'serde_json' in 'models'. I would expect that all code generated from a valid OpenAPI spec generates compilable rust code.

openapi-generator version

I tested with version 06ed7c82056aace4651d65ec56b68e4520739b53 (the current main).

OpenAPI declaration file content or url
openapi: 3.0.0
info:
  title: empty-schema
  description: Ensure that empty schema produces valid rust code.
  version: 1.0.0
paths:
  /v1/test/{param}:
    get:
      operationId: getTest
      parameters:
        - name: param
          in: path
          required: true
          schema: {}
      responses:
        '200':
          description: The request was successful.
Steps to reproduce

I'll provide the steps to create a failing test using the valid OpenAPI spec above.

Related issues/PRs

I did not find any issues regarding empty schemas and rust.

Suggest a fix

I'm new to rust and to openapi-generator, so I am no help here, sorry.

tgrushka commented 2 months ago

Yes, I confirm this bug with generated code for Twilio API:

openapi-generator generate -g rust \
  -i https://raw.githubusercontent.com/twilio/twilio-oai/main/spec/json/twilio_api_v2010.json \
  -o twilio_rust \
  --additional-properties=useSingleRequestParameter=true

Error:

error[E0433]: failed to resolve: could not find `serde_json` in `models`
  --> /Users/tom/twilio_test/twilio_rust/src/apis/api20100401_payment_api.rs:40:35
   |
40 |     pub parameter: Option<models::serde_json::Value>,
   |                                   ^^^^^^^^^^ could not find `serde_json` in `models`
   |
help: consider importing this crate
   |
12 + use crate::serde_json;
   |
help: if you import `serde_json`, refer to it directly
   |
40 -     pub parameter: Option<models::serde_json::Value>,
40 +     pub parameter: Option<serde_json::Value>,
   |

For more information about this error, try `rustc --explain E0433`.
error: could not compile `openapi` (lib) due to 1 previous error

Source JSON link:

https://github.com/twilio/twilio-oai/blob/753ee12a9d26702029abbe3e7c0b14a8dd63e3db/spec/json/twilio_api_v2010.json#L23045

Generated code:

src/apis/api20100401_payment_api.rs ```rs /* * Twilio - Api * * This is the public Twilio REST API. * * The version of the OpenAPI document: 1.55.5 * Contact: support@twilio.com * Generated by: https://openapi-generator.tech */ use reqwest; use crate::{apis::ResponseContent, models}; use super::{Error, configuration}; /// struct for passing parameters to the method [`create_payments`] #[derive(Clone, Debug)] pub struct CreatePaymentsParams { /// The SID of the [Account](https://www.twilio.com/docs/iam/api/account) that will create the resource. pub account_sid: String, /// The SID of the call that will create the resource. Call leg associated with this sid is expected to provide payment information thru DTMF. pub call_sid: String, /// A unique token that will be used to ensure that multiple API calls with the same information do not result in multiple transactions. This should be a unique string value per API call and can be a randomly generated. pub idempotency_key: String, /// Provide an absolute or relative URL to receive status updates regarding your Pay session. Read more about the [expected StatusCallback values](https://www.twilio.com/docs/voice/api/payment-resource#statuscallback) pub status_callback: String, pub bank_account_type: Option, /// A positive decimal value less than 1,000,000 to charge against the credit card or bank account. Default currency can be overwritten with `currency` field. Leave blank or set to 0 to tokenize. pub charge_amount: Option, /// The currency of the `charge_amount`, formatted as [ISO 4127](http://www.iso.org/iso/home/standards/currency_codes.htm) format. The default value is `USD` and all values allowed from the Pay Connector are accepted. pub currency: Option, /// The description can be used to provide more details regarding the transaction. This information is submitted along with the payment details to the Payment Connector which are then posted on the transactions. pub description: Option, /// A list of inputs that should be accepted. Currently only `dtmf` is supported. All digits captured during a pay session are redacted from the logs. pub input: Option, /// A positive integer that is used to validate the length of the `PostalCode` inputted by the user. User must enter this many digits. pub min_postal_code_length: Option, /// A single-level JSON object used to pass custom parameters to payment processors. (Required for ACH payments). The information that has to be included here depends on the Connector. [Read more](https://www.twilio.com/console/voice/pay-connectors). pub parameter: Option, /// This is the unique name corresponding to the Pay Connector installed in the Twilio Add-ons. Learn more about [ Connectors](https://www.twilio.com/console/voice/pay-connectors). The default value is `Default`. pub payment_connector: Option, pub payment_method: Option, /// Indicates whether the credit card postal code (zip code) is a required piece of payment information that must be provided by the caller. The default is `true`. pub postal_code: Option, /// Indicates whether the credit card security code is a required piece of payment information that must be provided by the caller. The default is `true`. pub security_code: Option, /// The number of seconds that should wait for the caller to press a digit between each subsequent digit, after the first one, before moving on to validate the digits captured. The default is `5`, maximum is `600`. pub timeout: Option, pub token_type: Option, /// Credit card types separated by space that Pay should accept. The default value is `visa mastercard amex` pub valid_card_types: Option } /// struct for passing parameters to the method [`update_payments`] #[derive(Clone, Debug)] pub struct UpdatePaymentsParams { /// The SID of the [Account](https://www.twilio.com/docs/iam/api/account) that will update the resource. pub account_sid: String, /// The SID of the call that will update the resource. This should be the same call sid that was used to create payments resource. pub call_sid: String, /// The SID of Payments session that needs to be updated. pub sid: String, /// A unique token that will be used to ensure that multiple API calls with the same information do not result in multiple transactions. This should be a unique string value per API call and can be a randomly generated. pub idempotency_key: String, /// Provide an absolute or relative URL to receive status updates regarding your Pay session. Read more about the [Update](https://www.twilio.com/docs/voice/api/payment-resource#statuscallback-update) and [Complete/Cancel](https://www.twilio.com/docs/voice/api/payment-resource#statuscallback-cancelcomplete) POST requests. pub status_callback: String, pub capture: Option, pub status: Option } /// struct for typed errors of method [`create_payments`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum CreatePaymentsError { UnknownValue(serde_json::Value), } /// struct for typed errors of method [`update_payments`] #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(untagged)] pub enum UpdatePaymentsError { UnknownValue(serde_json::Value), } /// create an instance of payments. This will start a new payments session pub async fn create_payments(configuration: &configuration::Configuration, params: CreatePaymentsParams) -> Result> { let local_var_configuration = configuration; // unbox the parameters let account_sid = params.account_sid; let call_sid = params.call_sid; let idempotency_key = params.idempotency_key; let status_callback = params.status_callback; let bank_account_type = params.bank_account_type; let charge_amount = params.charge_amount; let currency = params.currency; let description = params.description; let input = params.input; let min_postal_code_length = params.min_postal_code_length; let parameter = params.parameter; let payment_connector = params.payment_connector; let payment_method = params.payment_method; let postal_code = params.postal_code; let security_code = params.security_code; let timeout = params.timeout; let token_type = params.token_type; let valid_card_types = params.valid_card_types; let local_var_client = &local_var_configuration.client; let local_var_uri_str = format!("{}/2010-04-01/Accounts/{AccountSid}/Calls/{CallSid}/Payments.json", local_var_configuration.base_path, AccountSid=crate::apis::urlencode(account_sid), CallSid=crate::apis::urlencode(call_sid)); let mut local_var_req_builder = local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); } if let Some(ref local_var_auth_conf) = local_var_configuration.basic_auth { local_var_req_builder = local_var_req_builder.basic_auth(local_var_auth_conf.0.to_owned(), local_var_auth_conf.1.to_owned()); }; let mut local_var_form_params = std::collections::HashMap::new(); local_var_form_params.insert("IdempotencyKey", idempotency_key.to_string()); local_var_form_params.insert("StatusCallback", status_callback.to_string()); if let Some(local_var_param_value) = bank_account_type { local_var_form_params.insert("BankAccountType", local_var_param_value.to_string()); } if let Some(local_var_param_value) = charge_amount { local_var_form_params.insert("ChargeAmount", local_var_param_value.to_string()); } if let Some(local_var_param_value) = currency { local_var_form_params.insert("Currency", local_var_param_value.to_string()); } if let Some(local_var_param_value) = description { local_var_form_params.insert("Description", local_var_param_value.to_string()); } if let Some(local_var_param_value) = input { local_var_form_params.insert("Input", local_var_param_value.to_string()); } if let Some(local_var_param_value) = min_postal_code_length { local_var_form_params.insert("MinPostalCodeLength", local_var_param_value.to_string()); } if let Some(local_var_param_value) = parameter { local_var_form_params.insert("Parameter", local_var_param_value.to_string()); } if let Some(local_var_param_value) = payment_connector { local_var_form_params.insert("PaymentConnector", local_var_param_value.to_string()); } if let Some(local_var_param_value) = payment_method { local_var_form_params.insert("PaymentMethod", local_var_param_value.to_string()); } if let Some(local_var_param_value) = postal_code { local_var_form_params.insert("PostalCode", local_var_param_value.to_string()); } if let Some(local_var_param_value) = security_code { local_var_form_params.insert("SecurityCode", local_var_param_value.to_string()); } if let Some(local_var_param_value) = timeout { local_var_form_params.insert("Timeout", local_var_param_value.to_string()); } if let Some(local_var_param_value) = token_type { local_var_form_params.insert("TokenType", local_var_param_value.to_string()); } if let Some(local_var_param_value) = valid_card_types { local_var_form_params.insert("ValidCardTypes", local_var_param_value.to_string()); } local_var_req_builder = local_var_req_builder.form(&local_var_form_params); let local_var_req = local_var_req_builder.build()?; let local_var_resp = local_var_client.execute(local_var_req).await?; let local_var_status = local_var_resp.status(); let local_var_content = local_var_resp.text().await?; if !local_var_status.is_client_error() && !local_var_status.is_server_error() { serde_json::from_str(&local_var_content).map_err(Error::from) } else { let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; Err(Error::ResponseError(local_var_error)) } } /// update an instance of payments with different phases of payment flows. pub async fn update_payments(configuration: &configuration::Configuration, params: UpdatePaymentsParams) -> Result> { let local_var_configuration = configuration; // unbox the parameters let account_sid = params.account_sid; let call_sid = params.call_sid; let sid = params.sid; let idempotency_key = params.idempotency_key; let status_callback = params.status_callback; let capture = params.capture; let status = params.status; let local_var_client = &local_var_configuration.client; let local_var_uri_str = format!("{}/2010-04-01/Accounts/{AccountSid}/Calls/{CallSid}/Payments/{Sid}.json", local_var_configuration.base_path, AccountSid=crate::apis::urlencode(account_sid), CallSid=crate::apis::urlencode(call_sid), Sid=crate::apis::urlencode(sid)); let mut local_var_req_builder = local_var_client.request(reqwest::Method::POST, local_var_uri_str.as_str()); if let Some(ref local_var_user_agent) = local_var_configuration.user_agent { local_var_req_builder = local_var_req_builder.header(reqwest::header::USER_AGENT, local_var_user_agent.clone()); } if let Some(ref local_var_auth_conf) = local_var_configuration.basic_auth { local_var_req_builder = local_var_req_builder.basic_auth(local_var_auth_conf.0.to_owned(), local_var_auth_conf.1.to_owned()); }; let mut local_var_form_params = std::collections::HashMap::new(); local_var_form_params.insert("IdempotencyKey", idempotency_key.to_string()); local_var_form_params.insert("StatusCallback", status_callback.to_string()); if let Some(local_var_param_value) = capture { local_var_form_params.insert("Capture", local_var_param_value.to_string()); } if let Some(local_var_param_value) = status { local_var_form_params.insert("Status", local_var_param_value.to_string()); } local_var_req_builder = local_var_req_builder.form(&local_var_form_params); let local_var_req = local_var_req_builder.build()?; let local_var_resp = local_var_client.execute(local_var_req).await?; let local_var_status = local_var_resp.status(); let local_var_content = local_var_resp.text().await?; if !local_var_status.is_client_error() && !local_var_status.is_server_error() { serde_json::from_str(&local_var_content).map_err(Error::from) } else { let local_var_entity: Option = serde_json::from_str(&local_var_content).ok(); let local_var_error = ResponseContent { status: local_var_status, content: local_var_content, entity: local_var_entity }; Err(Error::ResponseError(local_var_error)) } } ```
efitzpatrick commented 1 week ago

I can also confirm this is a problem. Any idea on whether this is a twilio problem or an openapi-generator problem?