diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsGenerator.kt
index c1fc9c8528761a26d9d1deed8867d0f52a49fb72..1e5059830d11e49ee30215b44282840cc66b50d4 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointParamsGenerator.kt
@@ -12,6 +12,7 @@ import software.amazon.smithy.rust.codegen.client.smithy.endpoint.memberName
 import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rustName
 import software.amazon.smithy.rust.codegen.client.smithy.endpoint.symbol
 import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive
 import software.amazon.smithy.rust.codegen.core.rustlang.RustMetadata
 import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
 import software.amazon.smithy.rust.codegen.core.rustlang.RustType
@@ -27,10 +28,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
 import software.amazon.smithy.rust.codegen.core.rustlang.stripOuter
 import software.amazon.smithy.rust.codegen.core.rustlang.writable
 import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
-import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.Clone
-import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.Debug
-import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.Default
-import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType.Companion.PartialEq
 import software.amazon.smithy.rust.codegen.core.smithy.isOptional
 import software.amazon.smithy.rust.codegen.core.smithy.makeOptional
 import software.amazon.smithy.rust.codegen.core.smithy.mapRustType
@@ -175,7 +172,7 @@ internal class EndpointParamsGenerator(private val parameters: Parameters) {
         // Ensure that fields can be added in the future
         Attribute.NonExhaustive.render(writer)
         // Automatically implement standard Rust functionality
-        Attribute.Derives(setOf(Debug, PartialEq, Clone)).render(writer)
+        Attribute(derive(RuntimeType.Debug, RuntimeType.PartialEq, RuntimeType.Clone)).render(writer)
         // Generate the struct block:
         /*
             pub struct Params {
@@ -238,7 +235,7 @@ internal class EndpointParamsGenerator(private val parameters: Parameters) {
 
     private fun generateEndpointParamsBuilder(rustWriter: RustWriter) {
         rustWriter.docs("Builder for [`Params`]")
-        Attribute.Derives(setOf(Debug, Default, PartialEq, Clone)).render(rustWriter)
+        Attribute(derive(RuntimeType.Debug, RuntimeType.Default, RuntimeType.PartialEq, RuntimeType.Clone)).render(rustWriter)
         rustWriter.rustBlock("pub struct ParamsBuilder") {
             parameters.toList().forEach { parameter ->
                 val name = parameter.memberName()
@@ -255,7 +252,7 @@ internal class EndpointParamsGenerator(private val parameters: Parameters) {
                 "ParamsError" to paramsError(),
             ) {
                 val params = writable {
-                    Attribute.Custom("allow(clippy::unnecessary_lazy_evaluations)").render(this)
+                    Attribute.AllowClippyUnnecessaryLazyEvaluations.render(this)
                     rustBlockTemplate("#{Params}", "Params" to paramsStruct()) {
                         parameters.toList().forEach { parameter ->
                             rust("${parameter.memberName()}: self.${parameter.memberName()}")
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt
index aa9eebca7b24bc41f8a1fcce943f28032a57cbbd..385ab13240c99d7ab7eb7a04f708ec089ba44fa5 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/generators/EndpointResolverGenerator.kt
@@ -23,6 +23,7 @@ import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rulesgen.Expre
 import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rulesgen.Ownership
 import software.amazon.smithy.rust.codegen.client.smithy.endpoint.rustName
 import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.allow
 import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
 import software.amazon.smithy.rust.codegen.core.rustlang.Writable
 import software.amazon.smithy.rust.codegen.core.rustlang.comment
@@ -202,7 +203,7 @@ internal class EndpointResolverGenerator(stdlib: List<CustomRuntimeFunction>, ru
         fnsUsed: List<CustomRuntimeFunction>,
     ): RuntimeType {
         return RuntimeType.forInlineFun("resolve_endpoint", EndpointsImpl) {
-            allowLintsForResolver.map { Attribute.Custom("allow($it)") }.map { it.render(this) }
+            Attribute(allow(allowLintsForResolver)).render(this)
             rustTemplate(
                 """
                 pub(super) fn resolve_endpoint($ParamsName: &#{Params}, $DiagnosticCollector: &mut #{DiagnosticCollector}, #{additional_args}) -> #{endpoint}::Result {
@@ -233,7 +234,7 @@ internal class EndpointResolverGenerator(stdlib: List<CustomRuntimeFunction>, ru
         }
         if (!isExhaustive(rules.last())) {
             // it's hard to figure out if these are always needed or not
-            Attribute.Custom("allow(unreachable_code)").render(this)
+            Attribute.AllowUnreachableCode.render(this)
             rustTemplate(
                 """return Err(#{EndpointError}::message(format!("No rules matched these parameters. This is a bug. {:?}", $ParamsName)));""",
                 *codegenScope,
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/TemplateGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/TemplateGenerator.kt
index 4d4bae43a1421934740181d1fa65d168fcf138be..933813139268375eab2101031c4e87061f36999b 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/TemplateGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/endpoint/rulesgen/TemplateGenerator.kt
@@ -56,7 +56,7 @@ class TemplateGenerator(
 
     override fun visitDynamicElement(expr: Expression) = writable {
         // we don't need to own the argument to push_str
-        Attribute.Custom("allow(clippy::needless_borrow)").render(this)
+        Attribute.AllowClippyNeedlessBorrow.render(this)
         rust("out.push_str(&#W);", exprGenerator(expr, Ownership.Borrowed))
     }
 
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt
index 08f77fe6b6e1ebc00281d303d8d362345f14910f..a1e1c73957cdd42b71ef8a7450204f89929d5b94 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/client/FluentClientGenerator.kt
@@ -15,6 +15,8 @@ import software.amazon.smithy.model.traits.DocumentationTrait
 import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
 import software.amazon.smithy.rust.codegen.client.smithy.generators.PaginatorGenerator
 import software.amazon.smithy.rust.codegen.client.smithy.generators.isPaginated
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive
 import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
 import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords
 import software.amazon.smithy.rust.codegen.core.rustlang.RustType
@@ -230,7 +232,8 @@ class FluentClientGenerator(
                 val operationSymbol = symbolProvider.toSymbol(operation)
                 val input = operation.inputShape(model)
                 val baseDerives = symbolProvider.toSymbol(input).expectRustMetadata().derives
-                val derives = baseDerives.derives.intersect(setOf(RuntimeType.Clone)) + RuntimeType.Debug
+                // Filter out any derive that isn't Clone. Then add a Debug derive
+                val derives = baseDerives.filter { it == RuntimeType.Clone } + RuntimeType.Debug
                 rust(
                     """
                     /// Fluent builder constructing a request to `${operationSymbol.name}`.
@@ -240,7 +243,7 @@ class FluentClientGenerator(
 
                 documentShape(operation, model, autoSuppressMissingDocs = false)
                 deprecatedShape(operation)
-                baseDerives.copy(derives = derives).render(this)
+                Attribute(derive(derives.toSet())).render(this)
                 rustTemplate(
                     """
                     pub struct ${operationSymbol.name}#{generics:W} {
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/RequestBindingGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/RequestBindingGenerator.kt
index 9d625c51aedf2472856692b2805ba5ffc2a0a45e..9ec7173d03dd51af03f8f8ec5a6ff6bb462e69b4 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/RequestBindingGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/http/RequestBindingGenerator.kt
@@ -88,7 +88,7 @@ class RequestBindingGenerator(
         uriBase(implBlockWriter)
         val addHeadersFn = httpBindingGenerator.generateAddHeadersFn(operationShape)
         val hasQuery = uriQuery(implBlockWriter)
-        Attribute.Custom("allow(clippy::unnecessary_wraps)").render(implBlockWriter)
+        Attribute.AllowClippyUnnecessaryWraps.render(implBlockWriter)
         implBlockWriter.rustBlockTemplate(
             """
             fn update_http_builder(
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt
index a2530e758737e0750c034e6904949798acf04b31..b8d6a652e80e0d84b6367c317313119da1bcc79b 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ClientProtocolGenerator.kt
@@ -8,6 +8,7 @@ package software.amazon.smithy.rust.codegen.client.smithy.generators.protocol
 import software.amazon.smithy.model.shapes.OperationShape
 import software.amazon.smithy.rust.codegen.client.smithy.generators.client.FluentClientGenerator
 import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive
 import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
 import software.amazon.smithy.rust.codegen.core.rustlang.docLink
 import software.amazon.smithy.rust.codegen.core.rustlang.rust
@@ -77,7 +78,7 @@ open class ClientProtocolGenerator(
             /// See [`crate::client::fluent_builders::$operationName`] for more details about the operation.
             """,
         )
-        Attribute.Derives(setOf(RuntimeType.Clone, RuntimeType.Default, RuntimeType.Debug)).render(operationWriter)
+        Attribute(derive(RuntimeType.Clone, RuntimeType.Default, RuntimeType.Debug)).render(operationWriter)
         operationWriter.rustBlock("pub struct $operationName") {
             write("_private: ()")
         }
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/MakeOperationGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/MakeOperationGenerator.kt
index 4d4fc4e5f4391029d35b042790aa13ea55fb072a..541af1ceb563b0b0aa430725c8ef02afd002cbc7 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/MakeOperationGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/MakeOperationGenerator.kt
@@ -81,11 +81,12 @@ open class MakeOperationGenerator(
         val fnType = if (public) "pub async fn" else "async fn"
 
         implBlockWriter.docs("Consumes the builder and constructs an Operation<#D>", outputSymbol)
-        Attribute.AllowUnusedMut.render(implBlockWriter) // For codegen simplicity
-        Attribute.Custom("allow(clippy::let_and_return)")
-            .render(implBlockWriter) // For codegen simplicity, allow `let x = ...; x`
-        Attribute.Custom("allow(clippy::needless_borrow)")
-            .render(implBlockWriter) // Allows builders that don’t consume the input borrow
+        // For codegen simplicity
+        Attribute.AllowUnusedMut.render(implBlockWriter)
+        // For codegen simplicity, allow `let x = ...; x`
+        Attribute.AllowClippyLetAndReturn.render(implBlockWriter)
+        // Allows builders that don’t consume the input borrow
+        Attribute.AllowClippyNeedlessBorrow.render(implBlockWriter)
         implBlockWriter.rustBlockTemplate(
             "$fnType $functionName($self, _config: &#{config}::Config) -> $returnType",
             *codegenScope,
@@ -99,7 +100,7 @@ open class MakeOperationGenerator(
 
             // When the payload is a `ByteStream`, `into_inner()` already returns an `SdkBody`, so we mute this
             // Clippy warning to make the codegen a little simpler in that case.
-            Attribute.Custom("allow(clippy::useless_conversion)").render(this)
+            Attribute.AllowClippyUselessConversion.render(this)
             withBlockTemplate("let body = #{SdkBody}::from(", ");", *codegenScope) {
                 bodyGenerator.generatePayload(this, "self", shape)
                 val streamingMember = shape.inputShape(model).findStreamingMember(model)
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt
index def2229565f9c5803a395570bc29d7cb6cc95bcf..98d229558e5153735bff09c3470245e363d8a8a3 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/protocol/ProtocolTestGenerator.kt
@@ -22,6 +22,7 @@ import software.amazon.smithy.protocoltests.traits.HttpResponseTestsTrait
 import software.amazon.smithy.rust.codegen.client.smithy.ClientCodegenContext
 import software.amazon.smithy.rust.codegen.client.smithy.generators.clientInstantiator
 import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.allow
 import software.amazon.smithy.rust.codegen.core.rustlang.RustMetadata
 import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
 import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
@@ -36,7 +37,6 @@ import software.amazon.smithy.rust.codegen.core.rustlang.writable
 import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
 import software.amazon.smithy.rust.codegen.core.smithy.generators.error.errorSymbol
 import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolSupport
-import software.amazon.smithy.rust.codegen.core.testutil.TokioTest
 import software.amazon.smithy.rust.codegen.core.util.dq
 import software.amazon.smithy.rust.codegen.core.util.findMemberWithTrait
 import software.amazon.smithy.rust.codegen.core.util.getTrait
@@ -96,8 +96,8 @@ class ProtocolTestGenerator(
             val moduleMeta = RustMetadata(
                 visibility = Visibility.PRIVATE,
                 additionalAttributes = listOf(
-                    Attribute.Cfg("test"),
-                    Attribute.Custom("allow(unreachable_code, unused_variables)"),
+                    Attribute.CfgTest,
+                    Attribute(allow("unreachable_code", "unused_variables")),
                 ),
             )
             writer.withInlineModule(RustModule.LeafModule(testModuleName, moduleMeta, inline = true)) {
@@ -142,7 +142,7 @@ class ProtocolTestGenerator(
         }
         testModuleWriter.write("Test ID: ${testCase.id}")
         testModuleWriter.newlinePrefix = ""
-        TokioTest.render(testModuleWriter)
+        Attribute.TokioTest.render(testModuleWriter)
         val action = when (testCase) {
             is HttpResponseTestCase -> Action.Response
             is HttpRequestTestCase -> Action.Request
diff --git a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/HttpBoundProtocolGenerator.kt b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/HttpBoundProtocolGenerator.kt
index 9a9884f86eb318c492cd032eb798d29eca9ed3ba..0592d91cd9a90dacd0c7fe8b6780bbf299692ee8 100644
--- a/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/HttpBoundProtocolGenerator.kt
+++ b/codegen-client/src/main/kotlin/software/amazon/smithy/rust/codegen/client/smithy/protocols/HttpBoundProtocolGenerator.kt
@@ -169,7 +169,7 @@ class HttpBoundProtocolTraitImplGenerator(
         val outputSymbol = symbolProvider.toSymbol(outputShape)
         val errorSymbol = operationShape.errorSymbol(symbolProvider)
         return RuntimeType.forInlineFun(fnName, operationDeserModule) {
-            Attribute.Custom("allow(clippy::unnecessary_wraps)").render(this)
+            Attribute.AllowClippyUnnecessaryWraps.render(this)
             rustBlockTemplate(
                 "pub fn $fnName(response: &#{http}::Response<#{Bytes}>) -> std::result::Result<#{O}, #{E}>",
                 *codegenScope,
@@ -243,7 +243,7 @@ class HttpBoundProtocolTraitImplGenerator(
         val outputSymbol = symbolProvider.toSymbol(outputShape)
         val errorSymbol = operationShape.errorSymbol(symbolProvider)
         return RuntimeType.forInlineFun(fnName, operationDeserModule) {
-            Attribute.Custom("allow(clippy::unnecessary_wraps)").render(this)
+            Attribute.AllowClippyUnnecessaryWraps.render(this)
             rustBlockTemplate(
                 "pub fn $fnName(op_response: &mut #{operation}::Response) -> std::result::Result<#{O}, #{E}>",
                 *codegenScope,
@@ -251,7 +251,7 @@ class HttpBoundProtocolTraitImplGenerator(
                 "E" to errorSymbol,
             ) {
                 // Not all implementations will use the property bag, but some will
-                Attribute.Custom("allow(unused_variables)").render(this)
+                Attribute.AllowUnusedVariables.render(this)
                 rust("let (response, properties) = op_response.parts_mut();")
                 withBlock("Ok({", "})") {
                     renderShapeParser(
@@ -272,7 +272,7 @@ class HttpBoundProtocolTraitImplGenerator(
         val outputSymbol = symbolProvider.toSymbol(outputShape)
         val errorSymbol = operationShape.errorSymbol(symbolProvider)
         return RuntimeType.forInlineFun(fnName, operationDeserModule) {
-            Attribute.Custom("allow(clippy::unnecessary_wraps)").render(this)
+            Attribute.AllowClippyUnnecessaryWraps.render(this)
             rustBlockTemplate(
                 "pub fn $fnName(response: &#{http}::Response<#{Bytes}>) -> std::result::Result<#{O}, #{E}>",
                 *codegenScope,
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/HttpVersionListGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/HttpVersionListGeneratorTest.kt
index 495e348706854c4764a88736499342a798102e78..0c57ba622a70c4bab25a3df8c4cb243d2e09ae22 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/HttpVersionListGeneratorTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/customizations/HttpVersionListGeneratorTest.kt
@@ -12,13 +12,13 @@ import software.amazon.smithy.rust.codegen.client.smithy.generators.config.Confi
 import software.amazon.smithy.rust.codegen.client.smithy.generators.config.EventStreamSigningConfig
 import software.amazon.smithy.rust.codegen.client.smithy.generators.config.ServiceConfig
 import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
 import software.amazon.smithy.rust.codegen.core.rustlang.Writable
 import software.amazon.smithy.rust.codegen.core.rustlang.rust
 import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
 import software.amazon.smithy.rust.codegen.core.rustlang.writable
 import software.amazon.smithy.rust.codegen.core.smithy.RuntimeConfig
 import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
-import software.amazon.smithy.rust.codegen.core.testutil.TokioTest
 import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
 import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
 
@@ -56,7 +56,7 @@ internal class HttpVersionListGeneratorTest {
         clientIntegrationTest(model) { clientCodegenContext, rustCrate ->
             val moduleName = clientCodegenContext.moduleUseName()
             rustCrate.integrationTest("http_version_list") {
-                TokioTest.render(this)
+                Attribute.TokioTest.render(this)
                 rust(
                     """
                     async fn test_http_version_list_defaults() {
@@ -107,7 +107,7 @@ internal class HttpVersionListGeneratorTest {
         clientIntegrationTest(model) { clientCodegenContext, rustCrate ->
             val moduleName = clientCodegenContext.moduleUseName()
             rustCrate.integrationTest("validate_http") {
-                TokioTest.render(this)
+                Attribute.TokioTest.render(this)
                 rust(
                     """
                     async fn test_http_version_list_defaults() {
@@ -175,7 +175,7 @@ internal class HttpVersionListGeneratorTest {
         ) { clientCodegenContext, rustCrate ->
             val moduleName = clientCodegenContext.moduleUseName()
             rustCrate.integrationTest("validate_eventstream_http") {
-                TokioTest.render(this)
+                Attribute.TokioTest.render(this)
                 rust(
                     """
                     async fn test_http_version_list_defaults() {
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt
index f7e5e414e440dd9c5c4df16713b6b2b0890efcf8..9e47cf6618478e2024ebd6d5bfdc77e6e283fc52 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/endpoint/EndpointsDecoratorTest.kt
@@ -9,8 +9,8 @@ import io.kotest.assertions.throwables.shouldThrow
 import io.kotest.matchers.string.shouldContain
 import org.junit.jupiter.api.Test
 import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
 import software.amazon.smithy.rust.codegen.core.rustlang.rust
-import software.amazon.smithy.rust.codegen.core.testutil.TokioTest
 import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
 import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
 import software.amazon.smithy.rust.codegen.core.testutil.runWithWarnings
@@ -120,7 +120,7 @@ class EndpointsDecoratorTest {
         ) { clientCodegenContext, rustCrate ->
             rustCrate.integrationTest("endpoint_params_test") {
                 val moduleName = clientCodegenContext.moduleUseName()
-                TokioTest.render(this)
+                Attribute.TokioTest.render(this)
                 rust(
                     """
                     async fn endpoint_params_are_set() {
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt
index f682446d0507397683b286defe012472a2830261..2a9787f69d4ed9a0ad996aa716b46b0bfc174687 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/EndpointTraitBindingsTest.kt
@@ -11,6 +11,7 @@ import software.amazon.smithy.model.shapes.OperationShape
 import software.amazon.smithy.model.traits.EndpointTrait
 import software.amazon.smithy.rust.codegen.client.testutil.clientIntegrationTest
 import software.amazon.smithy.rust.codegen.client.testutil.testSymbolProvider
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
 import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
 import software.amazon.smithy.rust.codegen.core.rustlang.rust
 import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
@@ -19,7 +20,6 @@ import software.amazon.smithy.rust.codegen.core.smithy.generators.implBlock
 import software.amazon.smithy.rust.codegen.core.smithy.generators.operationBuildError
 import software.amazon.smithy.rust.codegen.core.testutil.TestRuntimeConfig
 import software.amazon.smithy.rust.codegen.core.testutil.TestWorkspace
-import software.amazon.smithy.rust.codegen.core.testutil.TokioTest
 import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
 import software.amazon.smithy.rust.codegen.core.testutil.compileAndTest
 import software.amazon.smithy.rust.codegen.core.testutil.integrationTest
@@ -140,7 +140,7 @@ internal class EndpointTraitBindingsTest {
         clientIntegrationTest(model) { clientCodegenContext, rustCrate ->
             val moduleName = clientCodegenContext.moduleUseName()
             rustCrate.integrationTest("test_endpoint_prefix") {
-                TokioTest.render(this)
+                Attribute.TokioTest.render(this)
                 rust(
                     """
                     async fn test_endpoint_prefix() {
diff --git a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/PaginatorGeneratorTest.kt b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/PaginatorGeneratorTest.kt
index 9108415f02a238cf348c9f4d006a5290b83d87e2..f45f56a1caa1324306295a2cb361b1e1c0aa082d 100644
--- a/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/PaginatorGeneratorTest.kt
+++ b/codegen-client/src/test/kotlin/software/amazon/smithy/rust/codegen/client/smithy/generators/PaginatorGeneratorTest.kt
@@ -71,7 +71,7 @@ internal class PaginatorGeneratorTest {
     fun `generate paginators that compile`() {
         clientIntegrationTest(model) { clientCodegenContext, rustCrate ->
             rustCrate.integrationTest("paginators_generated") {
-                Attribute.Custom("allow(unused_imports)").render(this)
+                Attribute.AllowUnusedImports.render(this)
                 rust("use ${clientCodegenContext.moduleUseName()}::paginator::PaginatedListPaginator;")
             }
         }
diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustType.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustType.kt
index 489b3f08c074decea4cb7f1ad89910b9938b337c..c39af9da0c53bb8a65e594eff0d2d063fba700f3 100644
--- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustType.kt
+++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustType.kt
@@ -5,8 +5,8 @@
 
 package software.amazon.smithy.rust.codegen.core.rustlang
 
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive
 import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
-import software.amazon.smithy.rust.codegen.core.util.PANIC
 import software.amazon.smithy.rust.codegen.core.util.dq
 
 /**
@@ -199,11 +199,10 @@ fun RustType.asArgumentType(fullyQualified: Boolean = true): String {
 }
 
 /** Format this Rust type so that it may be used as an argument type in a function definition */
-fun RustType.asArgumentValue(name: String) =
-    when (this) {
-        is RustType.String, is RustType.Box -> "$name.into()"
-        else -> name
-    }
+fun RustType.asArgumentValue(name: String) = when (this) {
+    is RustType.String, is RustType.Box -> "$name.into()"
+    else -> name
+}
 
 /**
  * For a given name, generate an `Argument` data class containing pre-formatted strings for using this type when
@@ -241,6 +240,7 @@ fun RustType.render(fullyQualified: Boolean = true): String {
                 "&${this.lifetime?.let { "'$it" } ?: ""} ${this.member.render(fullyQualified)}"
             }
         }
+
         is RustType.Option -> "${this.name}<${this.member.render(fullyQualified)}>"
         is RustType.Box -> "${this.name}<${this.member.render(fullyQualified)}>"
         is RustType.Dyn -> "${this.name} ${this.member.render(fullyQualified)}"
@@ -299,6 +299,7 @@ fun RustType.asDeref(): RustType = when (this) {
     } else {
         this
     }
+
     is RustType.Box -> RustType.Reference(null, member)
     is RustType.String -> RustType.Opaque("str")
     is RustType.Vec -> RustType.Slice(member)
@@ -325,47 +326,43 @@ fun RustType.isCopy(): Boolean = when (this) {
 }
 
 enum class Visibility {
-    PRIVATE,
-    PUBCRATE,
-    PUBLIC;
+    PRIVATE, PUBCRATE, PUBLIC;
 
     companion object {
-        fun publicIf(condition: Boolean, ifNot: Visibility): Visibility =
-            if (condition) {
-                PUBLIC
-            } else {
-                ifNot
-            }
+        fun publicIf(condition: Boolean, ifNot: Visibility): Visibility = if (condition) {
+            PUBLIC
+        } else {
+            ifNot
+        }
     }
 
-    fun toRustQualifier(): String =
-        when (this) {
-            PRIVATE -> ""
-            PUBCRATE -> "pub(crate)"
-            PUBLIC -> "pub"
-        }
+    fun toRustQualifier(): String = when (this) {
+        PRIVATE -> ""
+        PUBCRATE -> "pub(crate)"
+        PUBLIC -> "pub"
+    }
 }
 
 /**
  * Meta information about a Rust construction (field, struct, or enum).
  */
 data class RustMetadata(
-    val derives: Attribute.Derives = Attribute.Derives.Empty,
+    val derives: Set<RuntimeType> = setOf(),
     val additionalAttributes: List<Attribute> = listOf(),
     val visibility: Visibility = Visibility.PRIVATE,
 ) {
-    fun withDerives(vararg newDerive: RuntimeType): RustMetadata =
-        this.copy(derives = derives.copy(derives = derives.derives + newDerive))
+    fun withDerives(vararg newDerives: RuntimeType): RustMetadata =
+        this.copy(derives = derives + newDerives)
 
     fun withoutDerives(vararg withoutDerives: RuntimeType) =
-        this.copy(derives = derives.copy(derives = derives.derives - withoutDerives.toSet()))
-
-    private fun attributes(): List<Attribute> = additionalAttributes + derives
+        this.copy(derives = derives - withoutDerives.toSet())
 
     fun renderAttributes(writer: RustWriter): RustMetadata {
-        attributes().forEach {
+        additionalAttributes.forEach {
             it.render(writer)
         }
+        Attribute(derive(derives)).render(writer)
+
         return this
     }
 
@@ -385,145 +382,163 @@ data class RustMetadata(
         renderVisibility(writer)
     }
 
+    /**
+     * If `true`, the Rust symbol that this metadata references derives a `Debug` implementation.
+     * If `false`, then it doesn't.
+     */
+    fun hasDebugDerive(): Boolean {
+        return derives.contains(RuntimeType.Debug)
+    }
+
     companion object {
         val TestModule = RustMetadata(
             visibility = Visibility.PRIVATE,
             additionalAttributes = listOf(
-                Attribute.Cfg("test"),
+                Attribute.CfgTest,
             ),
         )
     }
 }
 
+data class Argument(val argument: String, val value: String, val type: String)
+
+/**
+ * AttributeKind differentiates between the two kinds of attribute macros: inner and outer.
+ * See the variant docs for more info, and the official Rust [Attribute Macro](https://doc.rust-lang.org/reference/attributes.html)
+ * for even MORE info.
+ */
+enum class AttributeKind {
+    /**
+     * Inner attributes, written with a bang (!) after the hash (#), apply to the item that the attribute is declared within.
+     */
+    Inner,
+
+    /**
+     * Outer attributes, written without the bang after the hash, apply to the thing that follows the attribute.
+     */
+    Outer
+}
+
 /**
  * [Attributes](https://doc.rust-lang.org/reference/attributes.html) are general free form metadata
  * that are interpreted by the compiler.
  *
  * For example:
  * ```rust
- *
  * #[derive(Clone, PartialEq, Serialize)] // <-- this is an attribute
  * #[serde(serialize_with = "abc")] // <-- this is an attribute
  * struct Abc {
  *   a: i64
  * }
+ * ```
  */
-sealed class Attribute {
-    abstract fun render(writer: RustWriter)
+class Attribute(val inner: Writable) {
+    constructor(str: String) : this(writable(str))
+    constructor(runtimeType: RuntimeType) : this(runtimeType.writable)
+
+    fun render(writer: RustWriter, attributeKind: AttributeKind = AttributeKind.Outer) {
+        // Writing "#[]" with nothing inside it is meaningless
+        if (inner.isNotEmpty()) {
+            when (attributeKind) {
+                AttributeKind.Inner -> writer.rust("##![#W]", inner)
+                AttributeKind.Outer -> writer.rust("##[#W]", inner)
+            }
+        }
+    }
 
     companion object {
-        val AllowDeadCode = Custom("allow(dead_code)")
-        val AllowDeprecated = Custom("allow(deprecated)")
-        val AllowUnused = Custom("allow(unused)")
-        val AllowUnusedMut = Custom("allow(unused_mut)")
-        val DocHidden = Custom("doc(hidden)")
-        val DocInline = Custom("doc(inline)")
+        val AllowClippyBoxedLocal = Attribute(allow("clippy::boxed_local"))
+        val AllowClippyLetAndReturn = Attribute(allow("clippy::let_and_return"))
+        val AllowClippyNeedlessBorrow = Attribute(allow("clippy::needless_borrow"))
+        val AllowClippyNewWithoutDefault = Attribute(allow("clippy::new_without_default"))
+        val AllowClippyUnnecessaryWraps = Attribute(allow("clippy::unnecessary_wraps"))
+        val AllowClippyUselessConversion = Attribute(allow("clippy::useless_conversion"))
+        val AllowClippyUnnecessaryLazyEvaluations = Attribute(allow("clippy::unnecessary_lazy_evaluations"))
+        val AllowDeadCode = Attribute(allow("dead_code"))
+        val AllowDeprecated = Attribute(allow("deprecated"))
+        val AllowIrrefutableLetPatterns = Attribute(allow("irrefutable_let_patterns"))
+        val AllowUnreachableCode = Attribute(allow("unreachable_code"))
+        val AllowUnused = Attribute(allow("unused"))
+        val AllowUnusedImports = Attribute(allow("unused_imports"))
+        val AllowUnusedMut = Attribute(allow("unused_mut"))
+        val AllowUnusedVariables = Attribute(allow("unused_variables"))
+        val CfgTest = Attribute(cfg("test"))
+        val DenyMissingDocs = Attribute(deny("missing_docs"))
+        val DocHidden = Attribute(doc("hidden"))
+        val DocInline = Attribute(doc("inline"))
+        val Test = Attribute("test")
+        val TokioTest = Attribute(RuntimeType.Tokio.resolve("test").writable)
 
         /**
          * [non_exhaustive](https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute)
          * indicates that more fields may be added in the future
          */
-        val NonExhaustive = Custom("non_exhaustive")
-    }
+        val NonExhaustive = Attribute("non_exhaustive")
 
-    data class Deprecated(val since: String?, val note: String?) : Attribute() {
-        override fun render(writer: RustWriter) {
-            writer.raw("#[deprecated")
-            if (since != null || note != null) {
-                writer.raw("(")
-                if (since != null) {
-                    writer.raw("""since = "$since"""")
-
-                    if (note != null) {
-                        writer.raw(", ")
-                    }
-                }
+        /**
+         * Mark the following type as deprecated. If you know why and in what version something was deprecated, then
+         * using [deprecated] is preferred.
+         */
+        val Deprecated = Attribute("deprecated")
 
-                if (note != null) {
-                    writer.raw("""note = "$note"""")
-                }
-                writer.raw(")")
+        private fun macroWithArgs(name: String, vararg args: RustWriter.() -> Unit): Writable = {
+            // Macros that require args can't be empty
+            if (args.isNotEmpty()) {
+                rustInline("$name(#W)", args.toList().join(", "))
             }
-            writer.raw("]")
         }
-    }
 
-    data class Derives(val derives: Set<RuntimeType>) : Attribute() {
-        override fun render(writer: RustWriter) {
-            if (derives.isEmpty()) {
-                return
+        private fun macroWithArgs(name: String, vararg args: String): Writable = {
+            // Macros that require args can't be empty
+            if (args.isNotEmpty()) {
+                rustInline("$name(${args.joinToString(", ")})")
             }
-            writer.raw("#[derive(")
-            derives.sortedBy { it.path }.forEach { derive ->
-                writer.writeInline("#T, ", derive)
-            }
-            writer.write(")]")
         }
 
-        companion object {
-            val Empty = Derives(setOf())
-        }
-    }
+        fun all(vararg attrMacros: Writable): Writable = macroWithArgs("all", *attrMacros)
+
+        fun allow(lints: Collection<String>): Writable = macroWithArgs("allow", *lints.toTypedArray())
+        fun allow(vararg lints: String): Writable = macroWithArgs("allow", *lints)
+        fun deny(vararg lints: String): Writable = macroWithArgs("deny", *lints)
+        fun any(vararg attrMacros: Writable): Writable = macroWithArgs("any", *attrMacros)
+        fun cfg(vararg attrMacros: Writable): Writable = macroWithArgs("cfg", *attrMacros)
+        fun cfg(vararg attrMacros: String): Writable = macroWithArgs("cfg", *attrMacros)
+        fun doc(vararg attrMacros: Writable): Writable = macroWithArgs("doc", *attrMacros)
+        fun doc(str: String): Writable = macroWithArgs("doc", writable(str))
+        fun not(vararg attrMacros: Writable): Writable = macroWithArgs("not", *attrMacros)
+
+        fun deprecated(since: String? = null, note: String? = null): Writable {
+            val optionalFields = mutableListOf<Writable>()
+            if (!note.isNullOrEmpty()) {
+                optionalFields.add(pair("note" to note.dq()))
+            }
 
-    /**
-     * A custom Attribute
-     *
-     * [annotation] represents the body of the attribute, e.g. `cfg(foo)` in `#[cfg(foo)]`
-     * If [container] is set, this attribute refers to its container rather than its successor. This generates `#![cfg(foo)]`
-     *
-     * Finally, any symbols listed will be imported when this attribute is rendered. This enables using attributes like
-     * `#[serde(Serialize)]` where `Serialize` is actually a symbol that must be imported.
-     */
-    data class Custom(
-        val annotation: String,
-        val symbols: List<RuntimeType> = listOf(),
-        val container: Boolean = false,
-    ) : Attribute() {
-        override fun render(writer: RustWriter) {
-            val bang = if (container) "!" else ""
-            writer.raw("#$bang[$annotation]")
-            symbols.forEach {
-                try {
-                    writer.addDependency(it.dependency)
-                } catch (ex: Exception) {
-                    PANIC("failed to add dependency for RuntimeType $it")
-                }
+            if (!since.isNullOrEmpty()) {
+                optionalFields.add(pair("since" to since.dq()))
             }
-        }
 
-        companion object {
-            /**
-             * Renders a
-             * [`#[deprecated]`](https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-deprecated-attribute)
-             * attribute.
-             */
-            fun deprecated(note: String? = null, since: String? = null): Custom {
-                val builder = StringBuilder()
-                builder.append("deprecated")
-
-                if (note != null && since != null) {
-                    builder.append("(note = ${note.dq()}, since = ${since.dq()})")
-                } else if (note != null) {
-                    builder.append("(note = ${note.dq()})")
-                } else if (since != null) {
-                    builder.append("(since = ${since.dq()})")
-                } else {
-                    // No-op. Rustc would emit a default message.
+            return {
+                rustInline("deprecated")
+                if (optionalFields.isNotEmpty()) {
+                    rustInline("(#W)", optionalFields.join(", "))
                 }
-                return Custom(builder.toString())
             }
         }
-    }
 
-    data class Cfg(val cond: String) : Attribute() {
-        override fun render(writer: RustWriter) {
-            writer.raw("#[cfg($cond)]")
+        fun derive(vararg runtimeTypes: RuntimeType): Writable = {
+            // Empty derives are meaningless
+            if (runtimeTypes.isNotEmpty()) {
+                // Sorted derives look nicer than unsorted, and it makes test output easier to predict
+                val writables = runtimeTypes.sortedBy { it.path }.map { it.writable }.join(", ")
+                rustInline("derive(#W)", writables)
+            }
         }
 
-        companion object {
-            fun feature(feature: String) = Cfg("feature = ${feature.dq()}")
+        fun derive(runtimeTypes: Collection<RuntimeType>): Writable = derive(*runtimeTypes.toTypedArray())
+
+        fun pair(pair: Pair<String, String>): Writable = {
+            val (key, value) = pair
+            rustInline("$key = $value")
         }
     }
 }
-
-data class Argument(val argument: String, val value: String, val type: String)
diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt
index 9f3ed975e579ae5d0532f41e8b076d36731fab39..59f9c5c40d0e6228218207d50139a93ecf56de79 100644
--- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt
+++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriter.kt
@@ -22,6 +22,7 @@ import software.amazon.smithy.model.shapes.Shape
 import software.amazon.smithy.model.shapes.ShapeId
 import software.amazon.smithy.model.traits.DeprecatedTrait
 import software.amazon.smithy.model.traits.DocumentationTrait
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.deprecated
 import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
 import software.amazon.smithy.rust.codegen.core.smithy.isOptional
 import software.amazon.smithy.rust.codegen.core.smithy.protocols.serialize.ValueExpression
@@ -147,6 +148,16 @@ fun <T : AbstractCodeWriter<T>> T.rust(
     this.write(contents.trim(), *args)
 }
 
+/**
+ * Convenience wrapper that tells Intellij that the contents of this block are Rust
+ */
+fun RustWriter.rustInline(
+    @Language("Rust", prefix = "macro_rules! foo { () =>  {{ ", suffix = "}}}") contents: String,
+    vararg args: Any,
+) {
+    this.writeInline(contents, *args)
+}
+
 /* rewrite #{foo} to #{foo:T} (the smithy template format) */
 private fun transformTemplate(template: String, scope: Array<out Pair<String, Any>>, trim: Boolean = true): String {
     check(scope.distinctBy { it.first.lowercase() }.size == scope.size) { "Duplicate cased keys not supported" }
@@ -324,7 +335,7 @@ fun RustWriter.deprecatedShape(shape: Shape): RustWriter {
     val note = deprecatedTrait.message.orNull()
     val since = deprecatedTrait.since.orNull()
 
-    Attribute.Custom.deprecated(note, since).render(this)
+    Attribute(deprecated(since, note)).render(this)
 
     return this
 }
diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/Writable.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/Writable.kt
index df3b66e6baf278ac0a94e10d00347aa80c12f82d..b17eb3b030a027c8307ba36c0720a1e9364f416c 100644
--- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/Writable.kt
+++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/Writable.kt
@@ -25,6 +25,8 @@ fun Writable.isEmpty(): Boolean {
     return writer.toString() == RustWriter.root().toString()
 }
 
+fun Writable.isNotEmpty(): Boolean = !this.isEmpty()
+
 operator fun Writable.plus(other: Writable): Writable {
     val first = this
     return writable {
diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt
index 7d29b3c0673d6de9f29ffc3d7e19274b31eb4691..1ad33d9e952e733e1f1402fca0be5122194c1475 100644
--- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt
+++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/RuntimeType.kt
@@ -229,6 +229,7 @@ data class RuntimeType(val path: String, val dependency: RustDependency? = null)
         val PercentEncoding = CargoDependency.PercentEncoding.toType()
         val PrettyAssertions = CargoDependency.PrettyAssertions.toType()
         val Regex = CargoDependency.Regex.toType()
+        val Tokio = CargoDependency.Tokio.toType()
         val TokioStream = CargoDependency.TokioStream.toType()
         val Tower = CargoDependency.Tower.toType()
         val Tracing = CargoDependency.Tracing.toType()
diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/SymbolMetadataProvider.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/SymbolMetadataProvider.kt
index ac2bd6398bf4a9a538f1ecd6e9b04f89aa42920c..4f6f095e94019fe71068075d8b1bbc60a86279d0 100644
--- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/SymbolMetadataProvider.kt
+++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/SymbolMetadataProvider.kt
@@ -72,7 +72,7 @@ abstract class SymbolMetadataProvider(private val base: RustSymbolProvider) : Wr
 
 /**
  * The base metadata supports a list of attributes that are used by generators to decorate code.
- * By default we apply #[non_exhaustive] only to client structures since model changes should
+ * By default, we apply ```#[non_exhaustive]``` only to client structures since model changes should
  * be considered as breaking only when generating server code.
  */
 class BaseSymbolMetadataProvider(
@@ -88,15 +88,15 @@ class BaseSymbolMetadataProvider(
             // shape; any sensitive descendant should still be printed as redacted.
             shape.members().any { it.getMemberTrait(model, SensitiveTrait::class.java).isPresent }
 
-        val setOfDerives = if (isSensitive) {
-            defaultDerives.toSet() - RuntimeType.Debug
+        val derives = if (isSensitive) {
+            defaultDerives - RuntimeType.Debug
         } else {
-            defaultDerives.toSet()
+            defaultDerives
         }
         return RustMetadata(
-            Attribute.Derives(setOfDerives),
-            additionalAttributes = additionalAttributes,
-            visibility = Visibility.PUBLIC,
+            derives,
+            additionalAttributes,
+            Visibility.PUBLIC,
         )
     }
 
@@ -137,31 +137,28 @@ class BaseSymbolMetadataProvider(
 
     override fun enumMeta(stringShape: StringShape): RustMetadata {
         return containerDefault(stringShape).withDerives(
-            RuntimeType.std.resolve("hash::Hash"),
-        ).withDerives(
-            // enums can be eq because they can only contain ints and strings
-            RuntimeType.std.resolve("cmp::Eq"),
-            // enums can be Ord because they can only contain ints and strings
-            RuntimeType.std.resolve("cmp::PartialOrd"),
-            RuntimeType.std.resolve("cmp::Ord"),
+            RuntimeType.Hash,
+            // enums can be Eq because they can only contain ints and strings
+            RuntimeType.Eq,
+            // enums can be PartialOrd/Ord because they can only contain ints and strings
+            RuntimeType.PartialOrd,
+            RuntimeType.Ord,
         )
     }
 
     companion object {
         private val defaultDerives by lazy {
-            with(RuntimeType) {
-                listOf(Debug, PartialEq, Clone)
-            }
+            setOf(RuntimeType.Debug, RuntimeType.PartialEq, RuntimeType.Clone)
         }
     }
 }
 
-private const val MetaKey = "meta"
+private const val META_KEY = "meta"
 fun Symbol.Builder.meta(rustMetadata: RustMetadata?): Symbol.Builder {
-    return this.putProperty(MetaKey, rustMetadata)
+    return this.putProperty(META_KEY, rustMetadata)
 }
 
-fun Symbol.expectRustMetadata(): RustMetadata = this.getProperty(MetaKey, RustMetadata::class.java).orElseThrow {
+fun Symbol.expectRustMetadata(): RustMetadata = this.getProperty(META_KEY, RustMetadata::class.java).orElseThrow {
     CodegenException(
         "Expected $this to have metadata attached but it did not. ",
     )
diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/AllowLintsCustomization.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/AllowLintsCustomization.kt
index 0fc88f7a1c3e503a93414c7314c1696b5af0091c..a06ff50a281921d7578d3203517d45269e90eff6 100644
--- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/AllowLintsCustomization.kt
+++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/customizations/AllowLintsCustomization.kt
@@ -6,6 +6,8 @@
 package software.amazon.smithy.rust.codegen.core.smithy.customizations
 
 import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.allow
+import software.amazon.smithy.rust.codegen.core.rustlang.AttributeKind
 import software.amazon.smithy.rust.codegen.core.rustlang.writable
 import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsCustomization
 import software.amazon.smithy.rust.codegen.core.smithy.generators.LibRsSection
@@ -68,13 +70,13 @@ class AllowLintsCustomization(
     override fun section(section: LibRsSection) = when (section) {
         is LibRsSection.Attributes -> writable {
             rustcLints.forEach {
-                Attribute.Custom("allow($it)", container = true).render(this)
+                Attribute(allow(it)).render(this, AttributeKind.Inner)
             }
             clippyLints.forEach {
-                Attribute.Custom("allow(clippy::$it)", container = true).render(this)
+                Attribute(allow("clippy::$it")).render(this, AttributeKind.Inner)
             }
             rustdocLints.forEach {
-                Attribute.Custom("allow(rustdoc::$it)", container = true).render(this)
+                Attribute(allow("rustdoc::$it")).render(this, AttributeKind.Inner)
             }
             // add a newline at the end
             this.write("")
diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt
index b612ca19fcb329798e0ca442c1af5ce39bbc550b..69aca4563081103997b26ad8fd6779db411fb779 100644
--- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt
+++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/BuilderGenerator.kt
@@ -10,6 +10,8 @@ import software.amazon.smithy.codegen.core.SymbolProvider
 import software.amazon.smithy.model.Model
 import software.amazon.smithy.model.shapes.MemberShape
 import software.amazon.smithy.model.shapes.StructureShape
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive
 import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
 import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords
 import software.amazon.smithy.rust.codegen.core.rustlang.RustType
@@ -113,7 +115,9 @@ class BuilderGenerator(
     private val structureSymbol = symbolProvider.toSymbol(shape)
     private val builderSymbol = shape.builderSymbol(symbolProvider)
     private val baseDerives = structureSymbol.expectRustMetadata().derives
-    private val builderDerives = baseDerives.derives.intersect(setOf(RuntimeType.Debug, RuntimeType.PartialEq, RuntimeType.Clone)) + RuntimeType.Default
+
+    // Filter out any derive that isn't Debug, PartialEq, or Clone. Then add a Default derive
+    private val builderDerives = baseDerives.filter { it == RuntimeType.Debug || it == RuntimeType.PartialEq || it == RuntimeType.Clone } + RuntimeType.Default
     private val builderName = "Builder"
 
     fun render(writer: RustWriter) {
@@ -122,7 +126,7 @@ class BuilderGenerator(
         writer.withInlineModule(shape.builderSymbol(symbolProvider).module()) {
             // Matching derives to the main structure + `Default` since we are a builder and everything is optional.
             renderBuilder(this)
-            if (!builderDerives.contains(RuntimeType.Debug)) {
+            if (!structureSymbol.expectRustMetadata().hasDebugDerive()) {
                 renderDebugImpl(this)
             }
         }
@@ -203,7 +207,7 @@ class BuilderGenerator(
 
     private fun renderBuilder(writer: RustWriter) {
         writer.docs("A builder for #D.", structureSymbol)
-        baseDerives.copy(derives = builderDerives).render(writer)
+        Attribute(derive(builderDerives)).render(writer)
         writer.rustBlock("pub struct $builderName") {
             for (member in members) {
                 val memberName = symbolProvider.toMemberName(member)
diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt
index b5c4d1ace0b79ba2a8b0eea093f7040b0a55e09c..ec8f3505e97fdbbd78df90fe80d0265e437a8b0e 100644
--- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt
+++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/EnumGenerator.kt
@@ -57,7 +57,7 @@ class EnumMemberModel(private val definition: EnumDefinition, private val symbol
 
     private fun renderDeprecated(writer: RustWriter) {
         if (definition.isDeprecated) {
-            Attribute.Custom.deprecated().render(writer)
+            Attribute.Deprecated.render(writer)
         }
     }
 
diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt
index 5f4e9ffb91036b420a826cc68f8e10c663bf2605..7e88d3d1fade8524249da95d6249d2438cd05177 100644
--- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt
+++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGenerator.kt
@@ -153,7 +153,7 @@ open class StructureGenerator(
         }
 
         renderStructureImpl()
-        if (!containerMeta.derives.derives.contains(RuntimeType.Debug)) {
+        if (!containerMeta.hasDebugDerive()) {
             renderDebugImpl()
         }
     }
diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/UnionGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/UnionGenerator.kt
index 15615f6069b015648648085824c3736055a57fd8..69cd25f2ea695ccdc2b8385c0a91738daf21492b 100644
--- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/UnionGenerator.kt
+++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/UnionGenerator.kt
@@ -67,7 +67,7 @@ class UnionGenerator(
 
         renderUnion(unionSymbol)
         renderImplBlock(unionSymbol)
-        if (!unionSymbol.expectRustMetadata().derives.derives.contains(RuntimeType.Debug)) {
+        if (!containerMeta.hasDebugDerive()) {
             if (shape.hasTrait<SensitiveTrait>()) {
                 renderFullyRedactedDebugImpl()
             } else {
@@ -110,7 +110,7 @@ class UnionGenerator(
                 val variantName = symbolProvider.toMemberName(member)
 
                 if (sortedMembers.size == 1) {
-                    Attribute.Custom("allow(irrefutable_let_patterns)").render(this)
+                    Attribute.AllowIrrefutableLetPatterns.render(this)
                 }
                 writer.renderAsVariant(member, variantName, funcNamePart, unionSymbol, memberSymbol)
                 rust("/// Returns true if this is a [`$variantName`](#T::$variantName).", unionSymbol)
diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/OperationErrorGenerator.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/OperationErrorGenerator.kt
index 7e5bf08b7fdcdc6ef4eefaec39b48e83009ddd4d..69f1ad8639a54710c23a4aea426a3190221a0a80 100644
--- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/OperationErrorGenerator.kt
+++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/OperationErrorGenerator.kt
@@ -77,7 +77,7 @@ class OperationErrorGenerator(
         operationSymbol: Symbol,
     ) {
         val meta = RustMetadata(
-            derives = Attribute.Derives(setOf(RuntimeType.Debug)),
+            derives = setOf(RuntimeType.Debug),
             additionalAttributes = listOf(Attribute.NonExhaustive),
             visibility = Visibility.PUBLIC,
         )
diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt
index b2d9ad06556978914ce6a15318d30615c71a58c4..a8567c4db47e5cbfbb7d5886f307fa658b6706a7 100644
--- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt
+++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/Rust.kt
@@ -213,7 +213,7 @@ fun RustWriter.unitTest(
 fun RustWriter.unitTest(
     name: String,
     vararg args: Any,
-    attribute: Attribute = Attribute.Custom("test"),
+    attribute: Attribute = Attribute.Test,
     async: Boolean = false,
     block: Writable,
 ): RustWriter {
@@ -225,7 +225,7 @@ fun RustWriter.unitTest(
 }
 
 fun RustWriter.tokioTest(name: String, vararg args: Any, block: Writable) {
-    unitTest(name, attribute = TokioTest, async = true, block = block, args = args)
+    unitTest(name, attribute = Attribute.TokioTest, async = true, block = block, args = args)
 }
 
 /**
diff --git a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/TestHelpers.kt b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/TestHelpers.kt
index 40f0014327ae5174a18daa4b09aa496bbb9b4861..6c9310a309d70e676cb46a5b2f6cdc28abea2ead 100644
--- a/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/TestHelpers.kt
+++ b/codegen-core/src/main/kotlin/software/amazon/smithy/rust/codegen/core/testutil/TestHelpers.kt
@@ -11,7 +11,6 @@ import software.amazon.smithy.model.shapes.ServiceShape
 import software.amazon.smithy.model.shapes.ShapeId
 import software.amazon.smithy.model.shapes.StructureShape
 import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
-import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
 import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWordSymbolProvider
 import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
 import software.amazon.smithy.rust.codegen.core.smithy.BaseSymbolMetadataProvider
@@ -111,5 +110,3 @@ fun StructureShape.renderWithModelBuilder(
         modelBuilder.renderConvenienceMethod(this)
     }
 }
-
-val TokioTest = Attribute.Custom("tokio::test", listOf(CargoDependency.Tokio.toType()))
diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustTypeTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustTypeTest.kt
index 0191ee5c940ee01c2a792bb43fc0a118698ad753..41e0c100666ac62645acaaa6d01fa3c09340e02b 100644
--- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustTypeTest.kt
+++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustTypeTest.kt
@@ -8,6 +8,15 @@ package software.amazon.smithy.rust.codegen.core.rustlang
 import io.kotest.matchers.shouldBe
 import io.kotest.matchers.string.shouldContain
 import org.junit.jupiter.api.Test
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.all
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.any
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.cfg
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.deny
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.doc
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.not
+import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
+import software.amazon.smithy.rust.codegen.core.util.dq
 
 internal class RustTypesTest {
     private fun forInputExpectOutput(t: Writable, expectedOutput: String) {
@@ -140,4 +149,60 @@ internal class RustTypesTest {
         type.render(false) shouldBe "Box<Option<&'a Vec<String>>>"
         type.render(true) shouldBe "std::boxed::Box<std::option::Option<&'a std::vec::Vec<std::string::String>>>"
     }
+
+    @Test
+    fun `attribute macros from strings render properly`() {
+        val attributeMacro = Attribute(
+            Attribute.cfg(
+                Attribute.all(
+                    Attribute.pair("feature" to "unstable".dq()),
+                    Attribute.any(
+                        Attribute.pair("feature" to "serialize".dq()),
+                        Attribute.pair("feature" to "deserialize".dq()),
+                    ),
+                ),
+            ),
+        )
+        forInputExpectOutput(writable { attributeMacro.render(this) }, "#[cfg(all(feature = \"unstable\", any(feature = \"serialize\", feature = \"deserialize\")))]\n")
+    }
+
+    @Test
+    fun `attribute macros render writers properly`() {
+        val attributeMacro = Attribute(
+            cfg(
+                all(
+                    // Normally we'd use the `pair` fn to define these but this is a test
+                    writable { rustInline("""feature = "unstable"""") },
+                    writable { rustInline("""feature = "serialize"""") },
+                    writable { rustInline("""feature = "deserialize"""") },
+                ),
+            ),
+        )
+        forInputExpectOutput(writable { attributeMacro.render(this) }, "#[cfg(all(feature = \"unstable\", feature = \"serialize\", feature = \"deserialize\"))]\n")
+    }
+
+    @Test
+    fun `attribute macros render nothing when empty`() {
+        // All of these attributes require arguments. If none are supplied, then they shouldn't render at all
+        val attributeMacro = Attribute(cfg(all(any(doc(not(deny()))))))
+        forInputExpectOutput(writable { attributeMacro.render(this) }, "")
+    }
+
+    @Test
+    fun `derive attribute macros render properly`() {
+        val attributeMacro = Attribute(
+            derive(
+                RuntimeType.Clone,
+                RuntimeType.Debug,
+                RuntimeType.StdError,
+            ),
+        )
+        forInputExpectOutput(writable { attributeMacro.render(this) }, "#[derive(std::clone::Clone, std::error::Error, std::fmt::Debug)]\n")
+    }
+
+    @Test
+    fun `derive attribute macros don't render when empty`() {
+        val attributeMacro = Attribute(derive())
+        forInputExpectOutput(writable { attributeMacro.render(this) }, "")
+    }
 }
diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriterTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriterTest.kt
index a78ccecd6f797ae3095d983825c5ba6a7d4bd808..86d48a2602dfea65e09234edd2e83c2e20ef029e 100644
--- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriterTest.kt
+++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/rustlang/RustWriterTest.kt
@@ -14,6 +14,7 @@ import software.amazon.smithy.model.Model
 import software.amazon.smithy.model.shapes.SetShape
 import software.amazon.smithy.model.shapes.StringShape
 import software.amazon.smithy.model.shapes.StructureShape
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.deprecated
 import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
 import software.amazon.smithy.rust.codegen.core.testutil.asSmithyModel
 import software.amazon.smithy.rust.codegen.core.testutil.compileAndRun
@@ -107,44 +108,44 @@ class RustWriterTest {
     @Test
     fun `attributes with comments when using rust`() {
         val sut = RustWriter.forModule("lib")
-        Attribute.Custom("foo").render(sut)
-        sut.rust("// heres an attribute")
-        sut.toString().shouldContain("#[foo]// heres an attribute")
+        Attribute("foo").render(sut)
+        sut.rust(" // here's an attribute")
+        sut.toString().shouldContain("#[foo]\n// here's an attribute")
     }
 
     @Test
     fun `attributes with comments when using docs`() {
         val sut = RustWriter.forModule("lib")
-        Attribute.Custom("foo").render(sut)
-        sut.docs("heres an attribute")
-        sut.toString().shouldContain("#[foo]\n/// heres an attribute")
+        Attribute("foo").render(sut)
+        sut.docs("here's an attribute")
+        sut.toString().shouldContain("#[foo]\n/// here's an attribute")
     }
 
     @Test
     fun `deprecated attribute without any field`() {
         val sut = RustWriter.forModule("lib")
-        Attribute.Custom.deprecated().render(sut)
+        Attribute.Deprecated.render(sut)
         sut.toString() shouldContain "#[deprecated]"
     }
 
     @Test
     fun `deprecated attribute with a note`() {
         val sut = RustWriter.forModule("lib")
-        Attribute.Custom.deprecated("custom").render(sut)
+        Attribute(deprecated(note = "custom")).render(sut)
         sut.toString() shouldContain "#[deprecated(note = \"custom\")]"
     }
 
     @Test
     fun `deprecated attribute with a since`() {
         val sut = RustWriter.forModule("lib")
-        Attribute.Custom.deprecated(since = "1.2.3").render(sut)
+        Attribute(deprecated(since = "1.2.3")).render(sut)
         sut.toString() shouldContain "#[deprecated(since = \"1.2.3\")]"
     }
 
     @Test
     fun `deprecated attribute with a note and a since`() {
         val sut = RustWriter.forModule("lib")
-        Attribute.Custom.deprecated("custom", "1.2.3").render(sut)
+        Attribute(deprecated("1.2.3", "custom")).render(sut)
         sut.toString() shouldContain "#[deprecated(note = \"custom\", since = \"1.2.3\")]"
     }
 
diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGeneratorTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGeneratorTest.kt
index 870abefdc6a1a9bb172c9baae69328cf84ce2ff8..37d73291ef8a004a89395b89696e7d76169e28bf 100644
--- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGeneratorTest.kt
+++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/StructureGeneratorTest.kt
@@ -13,7 +13,6 @@ import software.amazon.smithy.model.shapes.StructureShape
 import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
 import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
 import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
-import software.amazon.smithy.rust.codegen.core.rustlang.raw
 import software.amazon.smithy.rust.codegen.core.rustlang.rust
 import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
 import software.amazon.smithy.rust.codegen.core.smithy.ModelsModule
@@ -115,7 +114,7 @@ class StructureGeneratorTest {
         val project = TestWorkspace.testProject()
         val provider = testSymbolProvider(model)
 
-        project.lib { rust("##![allow(deprecated)]") }
+        project.lib { Attribute.AllowDeprecated.render(this) }
         project.withModule(ModelsModule) {
             val innerGenerator = StructureGenerator(model, provider, this, inner)
             innerGenerator.render()
@@ -127,7 +126,7 @@ class StructureGeneratorTest {
         // By putting the test in another module, it can't access the struct
         // fields if they are private
         project.unitTest {
-            raw("#[test]")
+            Attribute.Test.render(this)
             rustBlock("fn test_public_fields()") {
                 write(
                     """
@@ -238,7 +237,7 @@ class StructureGeneratorTest {
         val provider = testSymbolProvider(model)
         val project = TestWorkspace.testProject(provider)
         project.lib {
-            Attribute.Custom("deny(missing_docs)").render(this)
+            Attribute.DenyMissingDocs.render(this)
         }
         project.withModule(ModelsModule) {
             StructureGenerator(model, provider, this, model.lookup("com.test#Inner")).render()
diff --git a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ServiceErrorGeneratorTest.kt b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ServiceErrorGeneratorTest.kt
index 35552743276b9caef29c96f497260d8a83fe7ddd..746927d134bd94347c5077ee054a38951f98a8b0 100644
--- a/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ServiceErrorGeneratorTest.kt
+++ b/codegen-core/src/test/kotlin/software/amazon/smithy/rust/codegen/core/smithy/generators/error/ServiceErrorGeneratorTest.kt
@@ -10,6 +10,7 @@ import software.amazon.smithy.model.shapes.ServiceShape
 import software.amazon.smithy.model.shapes.ShapeId
 import software.amazon.smithy.model.shapes.StructureShape
 import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.AttributeKind
 import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
 import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
 import software.amazon.smithy.rust.codegen.core.smithy.CodegenTarget
@@ -80,7 +81,7 @@ internal class ServiceErrorGeneratorTest {
         )
 
         rustCrate.lib {
-            Attribute.AllowDeprecated.copy(container = true).render(this)
+            Attribute.AllowDeprecated.render(this, AttributeKind.Inner)
         }
         rustCrate.withModule(RustModule.Error) {
             for (operation in model.operationShapes) {
diff --git a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerEnumGenerator.kt b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerEnumGenerator.kt
index c4542c1025e8c85e128d30926d2db5c2361c7eed..90bdcc6e444dcd6548dc224600e82109665e7a20 100644
--- a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerEnumGenerator.kt
+++ b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerEnumGenerator.kt
@@ -29,7 +29,7 @@ class PythonServerEnumGenerator(
     shape: StringShape,
 ) : ServerEnumGenerator(codegenContext, writer, shape) {
 
-    private val pyo3Symbols = listOf(PythonServerCargoDependency.PyO3.toType())
+    private val pyO3 = PythonServerCargoDependency.PyO3.toType()
 
     override fun render() {
         renderPyClass()
@@ -38,11 +38,11 @@ class PythonServerEnumGenerator(
     }
 
     private fun renderPyClass() {
-        Attribute.Custom("pyo3::pyclass", symbols = pyo3Symbols).render(writer)
+        Attribute(pyO3.resolve("pyclass")).render(writer)
     }
 
     private fun renderPyO3Methods() {
-        Attribute.Custom("pyo3::pymethods", symbols = pyo3Symbols).render(writer)
+        Attribute(pyO3.resolve("pymethods")).render(writer)
         writer.rustTemplate(
             """
             impl $enumName {
diff --git a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerStructureGenerator.kt b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerStructureGenerator.kt
index 468c1c531344996c5af5b2fc91e32222c722d6f4..436d956fa29fe527d2d74b36365a9fc99d4604d5 100644
--- a/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerStructureGenerator.kt
+++ b/codegen-server/python/src/main/kotlin/software/amazon/smithy/rust/codegen/server/python/smithy/generators/PythonServerStructureGenerator.kt
@@ -15,6 +15,7 @@ import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
 import software.amazon.smithy.rust.codegen.core.rustlang.Writable
 import software.amazon.smithy.rust.codegen.core.rustlang.render
 import software.amazon.smithy.rust.codegen.core.rustlang.rust
+import software.amazon.smithy.rust.codegen.core.rustlang.rustInlineTemplate
 import software.amazon.smithy.rust.codegen.core.rustlang.rustTemplate
 import software.amazon.smithy.rust.codegen.core.rustlang.writable
 import software.amazon.smithy.rust.codegen.core.smithy.RustSymbolProvider
@@ -35,26 +36,41 @@ class PythonServerStructureGenerator(
     private val shape: StructureShape,
 ) : StructureGenerator(model, symbolProvider, writer, shape) {
 
-    private val pyo3Symbols = listOf(PythonServerCargoDependency.PyO3.toType())
+    private val pyO3 = PythonServerCargoDependency.PyO3.toType()
 
     override fun renderStructure() {
         if (shape.hasTrait<ErrorTrait>()) {
-            Attribute.Custom("pyo3::pyclass(extends = pyo3::exceptions::PyException)", symbols = pyo3Symbols).render(writer)
+            Attribute(
+                writable {
+                    rustInlineTemplate(
+                        "#{pyclass}(extends = #{PyException})",
+                        "pyclass" to pyO3.resolve("pyclass"),
+                        "PyException" to pyO3.resolve("exceptions::PyException"),
+                    )
+                },
+            ).render(writer)
         } else {
-            Attribute.Custom("pyo3::pyclass", symbols = pyo3Symbols).render(writer)
+            Attribute(pyO3.resolve("pyclass")).render(writer)
         }
         super.renderStructure()
         renderPyO3Methods()
     }
 
-    override fun renderStructureMember(writer: RustWriter, member: MemberShape, memberName: String, memberSymbol: Symbol) {
-        Attribute.Custom("pyo3(get, set)", symbols = pyo3Symbols).render(writer)
+    override fun renderStructureMember(
+        writer: RustWriter,
+        member: MemberShape,
+        memberName: String,
+        memberSymbol: Symbol,
+    ) {
+        writer.addDependency(PythonServerCargoDependency.PyO3)
+        // Above, we manually add dependency since we can't use a `RuntimeType` below
+        Attribute("pyo3(get, set)").render(writer)
         super.renderStructureMember(writer, member, memberName, memberSymbol)
     }
 
     private fun renderPyO3Methods() {
-        Attribute.Custom("allow(clippy::new_without_default)").render(writer)
-        Attribute.Custom("pyo3::pymethods", symbols = pyo3Symbols).render(writer)
+        Attribute.AllowClippyNewWithoutDefault.render(writer)
+        Attribute(pyO3.resolve("pymethods")).render(writer)
         writer.rustTemplate(
             """
             impl $name {
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PubCrateConstraintViolationSymbolProvider.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PubCrateConstraintViolationSymbolProvider.kt
index 4e7b5ab6c72930ff5fa4ba859dc72abfda97b361..c01112ed89986625c60a78c685afd9bc02a89e15 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PubCrateConstraintViolationSymbolProvider.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/PubCrateConstraintViolationSymbolProvider.kt
@@ -8,7 +8,6 @@ package software.amazon.smithy.rust.codegen.server.smithy
 import software.amazon.smithy.codegen.core.Symbol
 import software.amazon.smithy.model.shapes.Shape
 import software.amazon.smithy.model.shapes.StructureShape
-import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
 import software.amazon.smithy.rust.codegen.core.rustlang.RustType
 import software.amazon.smithy.rust.codegen.core.smithy.WrappingSymbolProvider
 import software.amazon.smithy.rust.codegen.core.smithy.locatedIn
@@ -31,7 +30,7 @@ class PubCrateConstraintViolationSymbolProvider(
             return baseSymbol
         }
         val baseRustType = baseSymbol.rustType()
-        val oldModule = baseSymbol.module() as RustModule.LeafModule
+        val oldModule = baseSymbol.module()
         val newModule = oldModule.copy(name = oldModule.name + "_internal")
         return baseSymbol.toBuilder()
             .rustType(RustType.Opaque(baseRustType.name, newModule.fullyQualifiedPath()))
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedBlobGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedBlobGenerator.kt
index e3ef8ec2bbf63198d056945f43ef3bd51e3aa12a..acaee289ae0e99fdea13689cd8499b68ff4e9574 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedBlobGenerator.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedBlobGenerator.kt
@@ -61,7 +61,7 @@ class ConstrainedBlobGenerator(
             Visibility.PUBCRATE
         }
         val constrainedTypeMetadata = RustMetadata(
-            Attribute.Derives(setOf(RuntimeType.Debug, RuntimeType.Clone, RuntimeType.PartialEq)),
+            setOf(RuntimeType.Debug, RuntimeType.Clone, RuntimeType.PartialEq),
             visibility = constrainedTypeVisibility,
         )
 
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedCollectionGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedCollectionGenerator.kt
index 2e472607d050f5fb211c76c38fa3ae0543cb7400..9d8aeb1e46f4d1acd6feab890df0f892eff7c276 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedCollectionGenerator.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedCollectionGenerator.kt
@@ -74,7 +74,7 @@ class ConstrainedCollectionGenerator(
         val constraintViolation = constraintViolationSymbolProvider.toSymbol(shape)
         val constrainedTypeVisibility = Visibility.publicIf(publicConstrainedTypes, Visibility.PUBCRATE)
         val constrainedTypeMetadata = RustMetadata(
-            Attribute.Derives(setOf(RuntimeType.Debug, RuntimeType.Clone, RuntimeType.PartialEq)),
+            setOf(RuntimeType.Debug, RuntimeType.Clone, RuntimeType.PartialEq),
             visibility = constrainedTypeVisibility,
         )
 
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedMapGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedMapGenerator.kt
index 48cd3420587cfb9d8d496fd3d3eb49e9d19c80e4..a97bf3da72115f80cc0347ae1633c5dbf1a59c64 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedMapGenerator.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedMapGenerator.kt
@@ -63,7 +63,7 @@ class ConstrainedMapGenerator(
 
         val constrainedTypeVisibility = Visibility.publicIf(publicConstrainedTypes, Visibility.PUBCRATE)
         val constrainedTypeMetadata = RustMetadata(
-            Attribute.Derives(setOf(RuntimeType.Debug, RuntimeType.Clone, RuntimeType.PartialEq)),
+            setOf(RuntimeType.Debug, RuntimeType.Clone, RuntimeType.PartialEq),
             visibility = constrainedTypeVisibility,
         )
 
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedNumberGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedNumberGenerator.kt
index ce3ee6aaa96d142418ab28b8f77188d1ff2abba5..6e85ac5c8f77c64675d0a076061373c70f8b9f02 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedNumberGenerator.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedNumberGenerator.kt
@@ -82,15 +82,15 @@ class ConstrainedNumberGenerator(
             Visibility.PUBCRATE
         }
         val constrainedTypeMetadata = RustMetadata(
-            Attribute.Derives(
-                setOf(
-                    RuntimeType.Debug,
-                    RuntimeType.Clone,
-                    RuntimeType.PartialEq,
-                    RuntimeType.Eq,
-                    RuntimeType.Hash,
-                ),
+
+            setOf(
+                RuntimeType.Debug,
+                RuntimeType.Clone,
+                RuntimeType.PartialEq,
+                RuntimeType.Eq,
+                RuntimeType.Hash,
             ),
+
             visibility = constrainedTypeVisibility,
         )
 
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt
index 88bebc5050bc434331f7e35a357c75ece8279f33..9ee0c6c289ce9754f4a6e29fc9f98821d5363611 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ConstrainedStringGenerator.kt
@@ -74,7 +74,7 @@ class ConstrainedStringGenerator(
             Visibility.PUBCRATE
         }
         val constrainedTypeMetadata = RustMetadata(
-            Attribute.Derives(setOf(RuntimeType.Debug, RuntimeType.Clone, RuntimeType.PartialEq, RuntimeType.Eq, RuntimeType.Hash)),
+            setOf(RuntimeType.Debug, RuntimeType.Clone, RuntimeType.PartialEq, RuntimeType.Eq, RuntimeType.Hash),
             visibility = constrainedTypeVisibility,
         )
 
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderConstraintViolations.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderConstraintViolations.kt
index 4d4efc4c7cd28507be22fb35d3b0b59119816254..55eae76c510f227eccd4c941750fa25f5c7cf53f 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderConstraintViolations.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderConstraintViolations.kt
@@ -10,6 +10,7 @@ import software.amazon.smithy.model.Model
 import software.amazon.smithy.model.shapes.MemberShape
 import software.amazon.smithy.model.shapes.StructureShape
 import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive
 import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
 import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
 import software.amazon.smithy.rust.codegen.core.rustlang.docs
@@ -66,7 +67,7 @@ class ServerBuilderConstraintViolations(
             "Attempted to render constraint violations for the builder for structure shape ${shape.id}, but calculation of the constraint violations resulted in no variants"
         }
 
-        Attribute.Derives(setOf(RuntimeType.Debug, RuntimeType.PartialEq)).render(writer)
+        Attribute(derive(RuntimeType.Debug, RuntimeType.PartialEq)).render(writer)
         writer.docs("Holds one variant for each of the ways the builder can fail.")
         if (nonExhaustive) Attribute.NonExhaustive.render(writer)
         val constraintViolationSymbolName = constraintViolationSymbolProvider.toSymbol(shape).name
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGenerator.kt
index d4267178040cc45fb2b8780062e1adc00ffddb22..27bfa69d32c05920f652ecc45a2c6da852714f03 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGenerator.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGenerator.kt
@@ -12,6 +12,7 @@ import software.amazon.smithy.model.shapes.MemberShape
 import software.amazon.smithy.model.shapes.StructureShape
 import software.amazon.smithy.model.shapes.UnionShape
 import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive
 import software.amazon.smithy.rust.codegen.core.rustlang.RustType
 import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
 import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
@@ -185,9 +186,10 @@ class ServerBuilderGenerator(
         // Matching derives to the main structure, - `PartialEq` (see class documentation for why), + `Default`
         // since we are a builder and everything is optional.
         val baseDerives = structureSymbol.expectRustMetadata().derives
-        val builderDerives = baseDerives.derives.intersect(setOf(RuntimeType.Debug, RuntimeType.Clone)) + RuntimeType.Default
-        baseDerives.copy(derives = builderDerives).render(writer)
-        writer.rustBlock("pub${ if (visibility == Visibility.PUBCRATE) " (crate)" else "" } struct Builder") {
+        // Filter out any derive that isn't Debug or Clone. Then add a Default derive
+        val builderDerives = baseDerives.filter { it == RuntimeType.Debug || it == RuntimeType.Clone } + RuntimeType.Default
+        Attribute(derive(builderDerives)).render(writer)
+        writer.rustBlock("${visibility.toRustQualifier()} struct Builder") {
             members.forEach { renderBuilderMember(this, it) }
         }
 
@@ -204,7 +206,7 @@ class ServerBuilderGenerator(
             renderBuildFn(this)
         }
 
-        if (!builderDerives.contains(RuntimeType.Debug)) {
+        if (!structureSymbol.expectRustMetadata().hasDebugDerive()) {
             renderImplDebugForBuilder(writer)
         }
     }
@@ -325,7 +327,7 @@ class ServerBuilderGenerator(
             // to the heap. However, that will make the builder take in a value whose type does not exactly match the
             // shape member's type.
             // We don't want to introduce API asymmetry just for this particular case, so we disable the lint.
-            Attribute.Custom("allow(clippy::boxed_local)").render(writer)
+            Attribute.AllowClippyBoxedLocal.render(writer)
         }
         writer.rustBlock("pub fn $memberName(mut self, input: ${symbol.rustType().render()}) -> Self") {
             withBlock("self.$memberName = ", "; self") {
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorWithoutPublicConstrainedTypes.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorWithoutPublicConstrainedTypes.kt
index 3e03a9d1a9e4becf984f541e4f6354efd9db1b73..b30252a8a74941b4bb86e815c9a40ed95fd2515a 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorWithoutPublicConstrainedTypes.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerBuilderGeneratorWithoutPublicConstrainedTypes.kt
@@ -9,6 +9,8 @@ import software.amazon.smithy.codegen.core.Symbol
 import software.amazon.smithy.codegen.core.SymbolProvider
 import software.amazon.smithy.model.shapes.MemberShape
 import software.amazon.smithy.model.shapes.StructureShape
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive
 import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
 import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
 import software.amazon.smithy.rust.codegen.core.rustlang.conditionalBlock
@@ -115,8 +117,9 @@ class ServerBuilderGeneratorWithoutPublicConstrainedTypes(
         // Matching derives to the main structure, - `PartialEq` (to be consistent with [ServerBuilderGenerator]), + `Default`
         // since we are a builder and everything is optional.
         val baseDerives = structureSymbol.expectRustMetadata().derives
-        val derives = baseDerives.derives.intersect(setOf(RuntimeType.Debug, RuntimeType.Clone)) + RuntimeType.Default
-        baseDerives.copy(derives = derives).render(writer)
+        // Filter out any derive that isn't Debug or Clone. Then add a Default derive
+        val derives = baseDerives.filter { it == RuntimeType.Debug || it == RuntimeType.Clone } + RuntimeType.Default
+        Attribute(derive(derives)).render(writer)
         writer.rustBlock("pub struct Builder") {
             members.forEach { renderBuilderMember(this, it) }
         }
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerEnumGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerEnumGenerator.kt
index 893f4d14f481e5e48be5bc8350d30ff9213db730..1a55cb88796a3a4d5c111e53c75c032486862503 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerEnumGenerator.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerEnumGenerator.kt
@@ -5,7 +5,6 @@
 package software.amazon.smithy.rust.codegen.server.smithy.generators
 
 import software.amazon.smithy.model.shapes.StringShape
-import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
 import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
 import software.amazon.smithy.rust.codegen.core.rustlang.rust
 import software.amazon.smithy.rust.codegen.core.rustlang.rustBlock
@@ -43,9 +42,7 @@ open class ServerEnumGenerator(
     )
 
     override fun renderFromForStr() {
-        writer.withInlineModule(
-            constraintViolationSymbol.module() as RustModule.LeafModule,
-        ) {
+        writer.withInlineModule(constraintViolationSymbol.module()) {
             rustTemplate(
                 """
                 ##[derive(Debug, PartialEq)]
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationErrorGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationErrorGenerator.kt
index 0d2a982903f79ee11ca35b83ce07e1c92a97ed0f..44048276d8e542c544c4ef3548f96989eb9b3c17 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationErrorGenerator.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationErrorGenerator.kt
@@ -8,7 +8,6 @@ package software.amazon.smithy.rust.codegen.server.smithy.generators
 import software.amazon.smithy.codegen.core.Symbol
 import software.amazon.smithy.model.Model
 import software.amazon.smithy.model.shapes.StructureShape
-import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
 import software.amazon.smithy.rust.codegen.core.rustlang.RustMetadata
 import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
 import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
@@ -44,7 +43,7 @@ open class ServerOperationErrorGenerator(
         operationSymbol: Symbol,
     ) {
         val meta = RustMetadata(
-            derives = Attribute.Derives(setOf(RuntimeType.Debug)),
+            derives = setOf(RuntimeType.Debug),
             visibility = Visibility.PUBLIC,
         )
 
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationRegistryGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationRegistryGenerator.kt
index fe7905914dfecc13676440478f8f53cb2ad9bbd4..10b11978d0abdd211192f7e8f655dfa723161d32 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationRegistryGenerator.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerOperationRegistryGenerator.kt
@@ -8,6 +8,7 @@ package software.amazon.smithy.rust.codegen.server.smithy.generators
 import software.amazon.smithy.model.shapes.OperationShape
 import software.amazon.smithy.model.traits.DocumentationTrait
 import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive
 import software.amazon.smithy.rust.codegen.core.rustlang.CargoDependency
 import software.amazon.smithy.rust.codegen.core.rustlang.DependencyScope
 import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords
@@ -202,7 +203,7 @@ ${operationImplementationStubs(operations)}
      * This is an enum deriving `Debug` and implementing `Display` and `std::error::Error`.
      */
     private fun renderOperationRegistryBuilderError(writer: RustWriter) {
-        Attribute.Derives(setOf(RuntimeType.Debug)).render(writer)
+        Attribute(derive(RuntimeType.Debug)).render(writer)
         writer.rustTemplate(
             """
             pub enum $operationRegistryErrorName {
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGenerator.kt
index 1625b56976196b05c981d2de6265c74a0026d1bd..b5da3fd0014960a5b1f1387e8d363da09c34ed2b 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGenerator.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/ServerServiceGenerator.kt
@@ -8,6 +8,7 @@ package software.amazon.smithy.rust.codegen.server.smithy.generators
 import software.amazon.smithy.model.knowledge.TopDownIndex
 import software.amazon.smithy.model.shapes.OperationShape
 import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.deprecated
 import software.amazon.smithy.rust.codegen.core.rustlang.RustMetadata
 import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
 import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords
@@ -263,7 +264,7 @@ open class ServerServiceGenerator(
                 RustMetadata(
                     visibility = Visibility.PUBLIC,
                     additionalAttributes = listOf(
-                        Attribute.Deprecated("0.52.0", "This module exports the deprecated `OperationRegistry`. Use the service builder exported from your root crate."),
+                        Attribute(deprecated("0.52.0", "This module exports the deprecated `OperationRegistry`. Use the service builder exported from your root crate.")),
                     ),
                 ),
                 """
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedUnionGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedUnionGenerator.kt
index af97976aebdec9c35ae1805a917ee073d8a1338a..72655675a03075851fc124193660ce4e28229c2a 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedUnionGenerator.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/UnconstrainedUnionGenerator.kt
@@ -10,6 +10,7 @@ import software.amazon.smithy.model.shapes.StructureShape
 import software.amazon.smithy.model.shapes.UnionShape
 import software.amazon.smithy.model.traits.EnumTrait
 import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.derive
 import software.amazon.smithy.rust.codegen.core.rustlang.RustWriter
 import software.amazon.smithy.rust.codegen.core.rustlang.Visibility
 import software.amazon.smithy.rust.codegen.core.rustlang.rust
@@ -134,7 +135,7 @@ class UnconstrainedUnionGenerator(
         modelsModuleWriter.withInlineModule(
             constraintViolationSymbol.module(),
         ) {
-            Attribute.Derives(setOf(RuntimeType.Debug, RuntimeType.PartialEq)).render(this)
+            Attribute(derive(RuntimeType.Debug, RuntimeType.PartialEq)).render(this)
             rustBlock("pub${if (constraintViolationVisibility == Visibility.PUBCRATE) " (crate)" else ""} enum $constraintViolationName") {
                 constraintViolations().forEach { renderConstraintViolation(this, it) }
             }
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt
index 955696b316ee11ceb29a213f0349976c6b5b19c2..cad3786179b3afdff774c67d8bedb435a93fba57 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/generators/protocol/ServerProtocolTestGenerator.kt
@@ -25,6 +25,7 @@ import software.amazon.smithy.protocoltests.traits.HttpRequestTestsTrait
 import software.amazon.smithy.protocoltests.traits.HttpResponseTestCase
 import software.amazon.smithy.protocoltests.traits.HttpResponseTestsTrait
 import software.amazon.smithy.rust.codegen.core.rustlang.Attribute
+import software.amazon.smithy.rust.codegen.core.rustlang.Attribute.Companion.allow
 import software.amazon.smithy.rust.codegen.core.rustlang.RustMetadata
 import software.amazon.smithy.rust.codegen.core.rustlang.RustModule
 import software.amazon.smithy.rust.codegen.core.rustlang.RustReservedWords
@@ -42,7 +43,6 @@ import software.amazon.smithy.rust.codegen.core.smithy.CodegenContext
 import software.amazon.smithy.rust.codegen.core.smithy.RuntimeType
 import software.amazon.smithy.rust.codegen.core.smithy.generators.protocol.ProtocolSupport
 import software.amazon.smithy.rust.codegen.core.smithy.transformers.allErrors
-import software.amazon.smithy.rust.codegen.core.testutil.TokioTest
 import software.amazon.smithy.rust.codegen.core.util.dq
 import software.amazon.smithy.rust.codegen.core.util.getTrait
 import software.amazon.smithy.rust.codegen.core.util.hasStreamingMember
@@ -179,7 +179,7 @@ class ServerProtocolTestGenerator(
             PROTOCOL_TEST_HELPER_MODULE_NAME,
             RustMetadata(
                 additionalAttributes = listOf(
-                    Attribute.Cfg("test"),
+                    Attribute.CfgTest,
                     Attribute.AllowDeadCode,
                 ),
                 visibility = Visibility.PUBCRATE,
@@ -257,8 +257,8 @@ class ServerProtocolTestGenerator(
                 "server_${operationName.toSnakeCase()}_test",
                 RustMetadata(
                     additionalAttributes = listOf(
-                        Attribute.Cfg("test"),
-                        Attribute.Custom("allow(unreachable_code, unused_variables)"),
+                        Attribute.CfgTest,
+                        Attribute(allow("unreachable_code", "unused_variables")),
                     ),
                     visibility = Visibility.PRIVATE,
                 ),
@@ -365,7 +365,7 @@ class ServerProtocolTestGenerator(
         testModuleWriter.rust("Test ID: ${testCase.id}")
         testModuleWriter.newlinePrefix = ""
 
-        TokioTest.render(testModuleWriter)
+        Attribute.TokioTest.render(testModuleWriter)
 
         if (expectFail(testCase)) {
             testModuleWriter.writeWithNoFormatting("#[should_panic]")
diff --git a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/ServerHttpBoundProtocolGenerator.kt b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/ServerHttpBoundProtocolGenerator.kt
index f6b5c51c5da96c605f5c50e21612e0fa0ede6ee5..390eab30202cc2726cb760a6074a5118e7076d00 100644
--- a/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/ServerHttpBoundProtocolGenerator.kt
+++ b/codegen-server/src/main/kotlin/software/amazon/smithy/rust/codegen/server/smithy/protocols/ServerHttpBoundProtocolGenerator.kt
@@ -393,7 +393,7 @@ private class ServerHttpBoundProtocolTraitImplGenerator(
         val inputSymbol = symbolProvider.toSymbol(inputShape)
 
         return RuntimeType.forInlineFun(fnName, operationDeserModule) {
-            Attribute.Custom("allow(clippy::unnecessary_wraps)").render(this)
+            Attribute.AllowClippyUnnecessaryWraps.render(this)
             // The last conversion trait bound is needed by the `hyper::body::to_bytes(body).await?` call.
             rustBlockTemplate(
                 """
@@ -428,7 +428,7 @@ private class ServerHttpBoundProtocolTraitImplGenerator(
         val outputSymbol = symbolProvider.toSymbol(outputShape)
 
         return RuntimeType.forInlineFun(fnName, operationSerModule) {
-            Attribute.Custom("allow(clippy::unnecessary_wraps)").render(this)
+            Attribute.AllowClippyUnnecessaryWraps.render(this)
 
             // Note we only need to take ownership of the output in the case that it contains streaming members.
             // However, we currently always take ownership here, but worth noting in case in the future we want
@@ -459,7 +459,7 @@ private class ServerHttpBoundProtocolTraitImplGenerator(
         val fnName = "serialize_${operationShape.id.name.toSnakeCase()}_error"
         val errorSymbol = operationShape.errorSymbol(symbolProvider)
         return RuntimeType.forInlineFun(fnName, operationSerModule) {
-            Attribute.Custom("allow(clippy::unnecessary_wraps)").render(this)
+            Attribute.AllowClippyUnnecessaryWraps.render(this)
             rustBlockTemplate(
                 "pub fn $fnName(error: &#{E}) -> std::result::Result<#{SmithyHttpServer}::response::Response, #{ResponseRejection}>",
                 *codegenScope,