Skip to content
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

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

Closed
5 of 6 tasks
felixauringer opened this issue Apr 28, 2024 · 2 comments · Fixed by #20631
Closed
5 of 6 tasks

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

felixauringer opened this issue Apr 28, 2024 · 2 comments · Fixed by #20631

Comments

@felixauringer
Copy link

felixauringer commented Apr 28, 2024

Bug Report Checklist

  • Have you provided a full/minimal spec to reproduce the issue?
  • Have you validated the input using an OpenAPI validator (example)?
  • Have you tested with the latest master to confirm the issue still exists?
  • Have you searched for related issues/PRs?
  • What's the actual output vs expected output?
  • [Optional] Sponsorship to speed up the bug fix or feature request (example)
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 06ed7c8 (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.

  • Add the snippet above to the test resources (e.g. modules/openapi-generator/src/test/resources/3_0/rust/empty-schema.yaml).

  • Create a config for the test (e.g. bin/configs/rust-reqwest-empty-schema.yaml):

    generatorName: rust
    outputDir: samples/client/others/rust/reqwest/empty-schema
    library: reqwest
    inputSpec: modules/openapi-generator/src/test/resources/3_0/rust/empty-schema.yaml
    templateDir: modules/openapi-generator/src/main/resources/rust
    additionalProperties:
      supportAsync: false
      packageName: empty-schema-reqwest
  • Generate the rust samples with ./bin/generate-samples.sh ./bin/configs/rust-*.

  • Execute the tests with mvn integration-test -f samples/client/others/rust/pom.xml.

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
Copy link

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
/*
 * Twilio - Api
 *
 * This is the public Twilio REST API.
 *
 * The version of the OpenAPI document: 1.55.5
 * Contact: [email protected]
 * 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<String>,
    /// 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<f64>,
    /// 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<String>,
    /// 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<String>,
    /// 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<String>,
    /// 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<i32>,
    /// 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 <Pay> Connector. [Read more](https://www.twilio.com/console/voice/pay-connectors).
    pub parameter: Option<models::serde_json::Value>,
    /// This is the unique name corresponding to the Pay Connector installed in the Twilio Add-ons. Learn more about [<Pay> Connectors](https://www.twilio.com/console/voice/pay-connectors). The default value is `Default`.
    pub payment_connector: Option<String>,
    pub payment_method: Option<String>,
    /// 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<bool>,
    /// 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<bool>,
    /// The number of seconds that <Pay> 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<i32>,
    pub token_type: Option<String>,
    /// Credit card types separated by space that Pay should accept. The default value is `visa mastercard amex`
    pub valid_card_types: Option<String>
}

/// 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<String>,
    pub status: Option<String>
}


/// 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<models::ApiPeriodV2010PeriodAccountPeriodCallPeriodPayments, Error<CreatePaymentsError>> {
    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<CreatePaymentsError> = 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<models::ApiPeriodV2010PeriodAccountPeriodCallPeriodPayments, Error<UpdatePaymentsError>> {
    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<UpdatePaymentsError> = 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
Copy link

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants