Skip to content

Generated server code contains trailing whitespace, causing cargo fmt to fail with error[internal]: left behind trailing whitespace #4634

@PeterUlb

Description

@PeterUlb

Summary

smithy-rs server codegen emits Rust source files that contain lines with only indentation (whitespace-only lines). When running cargo fmt against the generated workspace, rustfmt aborts with error[internal]: left behind trailing whitespace and refuses to format the file. The same condition also surfaces as a [WARNING] Failed to run cargo fmt: ... during the Gradle build, because the codegen pipeline invokes cargo fmt on its output.

This reproduces on the stock smithy-rust-quickstart template with no modifications.

Versions

  • Template: smithy init -t smithy-rust-quickstart
  • smithyRsVersion: 0.1.3 (from the template's gradle.properties) (updating to 0.1.14 and has the same issue too)
  • smithyVersion: 1.69.0
  • smithyGradleVersion: 1.4.0
  • Smithy CLI: 1.69.0
  • Rust toolchain verified: stable 1.95.0 (rustfmt 1.9.0) and 1.85.0 (rustfmt 1.8.0). Both fail with the same internal error.
  • Platform: macOS aarch64

Reproducer 1 (stock template, no modifications)

smithy init -t smithy-rust-quickstart
cd smithy-rust-quickstart
./gradlew build
cargo fmt

The Gradle build already logs:

[WARNING] [rust-server-codegen] Generating Rust server for service ...
Failed to run cargo fmt: [com.example#CoffeeShop]
Unexpected exception thrown when executing subprocess:
...

And cargo fmt fails:

error[internal]: left behind trailing whitespace
  --> server/build/smithyprojections/server/source/rust-server-codegen/src/model.rs:411:1
    |
411 |     
    | ^^^^
    |

warning: rustfmt has failed to format. See previous 1 errors.

Line 411 of the generated model.rs is 4 spaces followed by a newline. It sits inside ConstraintViolation::as_validation_exception_field for the Uuid shape (which has @length + @pattern). The surrounding block also has inconsistent indentation:

impl ConstraintViolation {
    pub(crate) fn as_validation_exception_field(
        self,
        path: ::std::string::String,
    ) -> crate::model::ValidationExceptionField {
        match self {
                        Self::Length(length) => crate::model::ValidationExceptionField {
                        message: format!("Value with length {} at '{}' failed to satisfy constraint: Member must have length between 1 and 128, inclusive", length, &path),
                        path,
                    },
    ⎵⎵⎵⎵                                     ← line 411: whitespace-only, 4 spaces
    #[allow(unused_variables)]
    Self::Pattern(_) => crate::model::ValidationExceptionField {
                        message: format!("Value at '{}' failed to satisfy constraint: Member must satisfy regular expression pattern: {}", &path, r#"..."#),
                        path
                    },
                    }
    }
}

Reproducer 2 (union with constrained structure variants, inside operation input)

Add the following shapes to the template and wire the union into the CreateOrder input. The shape names are long enough that the generated variant expression breaks across multiple lines — with shorter names rustfmt collapses the expression onto one line, which hides the bug (see note below).

// common.smithy
/// Describes how a customer subscription is renewed
union CustomerSubscriptionRenewalPolicy {
    scheduledRenewal: ScheduledRenewalWindow
    autoRenewal: AutoRenewalConfiguration
    cancelled: smithy.api#Unit
}

structure ScheduledRenewalWindow {
    @required
    scheduledAt: Timestamp
}

structure AutoRenewalConfiguration {
    @required
    intervalDays: Long
}
// order.smithy — inside CreateOrder.input
input := for Order {
    @required
    $coffeeType

    @notProperty
    renewalPolicy: CustomerSubscriptionRenewalPolicy
}

cargo fmt now reports the model.rs error from Reproducer 1 plus four additional errors in unconstrained.rs (two per constrained-structure variant):

error[internal]: left behind trailing whitespace
  --> server/build/smithyprojections/server/source/rust-server-codegen/src/model.rs:532:1
error[internal]: left behind trailing whitespace
  --> server/build/smithyprojections/server/source/rust-server-codegen/src/unconstrained.rs:34:1
error[internal]: left behind trailing whitespace
  --> server/build/smithyprojections/server/source/rust-server-codegen/src/unconstrained.rs:35:1
error[internal]: left behind trailing whitespace
  --> server/build/smithyprojections/server/source/rust-server-codegen/src/unconstrained.rs:42:1
error[internal]: left behind trailing whitespace
  --> server/build/smithyprojections/server/source/rust-server-codegen/src/unconstrained.rs:43:1
warning: rustfmt has failed to format. See previous 5 errors.

The offending region in the generated unconstrained.rs (whitespace made visible with , one 40-space indent per ⎵⎵⎵… group shown):

match value {
    crate::unconstrained::customer_subscription_renewal_policy_unconstrained::CustomerSubscriptionRenewalPolicyUnconstrained::AutoRenewal(unconstrained) => Self::AutoRenewal(
        unconstrained
                                        .try_into()
                                        ⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵   ← 40-space whitespace-only line
                                        ⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵   ← 40-space whitespace-only line
                                        .map_err(Self::Error::AutoRenewal)?
    ),
    crate::unconstrained::customer_subscription_renewal_policy_unconstrained::CustomerSubscriptionRenewalPolicyUnconstrained::Cancelled => Self::Cancelled,
    crate::unconstrained::customer_subscription_renewal_policy_unconstrained::CustomerSubscriptionRenewalPolicyUnconstrained::ScheduledRenewal(unconstrained) => Self::ScheduledRenewal(
        unconstrained
                                        .try_into()
                                        ⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵   ← 40-space whitespace-only line
                                        ⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵⎵   ← 40-space whitespace-only line
                                        .map_err(Self::Error::ScheduledRenewal)?
    ),
}

Note on name length: with a short union (e.g. union U { a: A, b: smithy.api#Unit } and structure A { @required field: Long }), the codegen emits the same template, but each variant's expression is short enough that rustfmt collapses it onto one line (Self::A(unconstrained.try_into().map_err(Self::Error::A)?)), absorbing the whitespace-only lines. The unconstrained.rs error only surfaces once a variant's expression exceeds rustfmt's line-break threshold.

Expected behavior

Generated .rs files should not contain whitespace-only lines (or any trailing whitespace). cargo fmt should succeed on a freshly generated workspace, and the codegen's internal cargo fmt invocation should not fail.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions