From c7389d5de98e76f370366628e12962d14211dddc Mon Sep 17 00:00:00 2001 From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com> Date: Mon, 8 Dec 2025 11:57:18 -0600 Subject: [PATCH 1/8] Draft docs for Rust --- .../General-Code-Example-Standards.md | 196 +++++++ steering_docs/Rust-code-example-standards.md | 215 +++++++ steering_docs/rust-tech.md | 11 + steering_docs/rust-tech/README.md | 227 ++++++++ steering_docs/rust-tech/basics.md | 497 ++++++++++++++++ steering_docs/rust-tech/hello.md | 336 +++++++++++ steering_docs/rust-tech/metadata.md | 329 +++++++++++ steering_docs/rust-tech/orchestration.md | 397 +++++++++++++ steering_docs/rust-tech/readme_writeme.md | 387 +++++++++++++ steering_docs/rust-tech/tests.md | 529 ++++++++++++++++++ steering_docs/rust-tech/wrapper.md | 461 +++++++++++++++ 11 files changed, 3585 insertions(+) create mode 100644 steering_docs/General-Code-Example-Standards.md create mode 100644 steering_docs/Rust-code-example-standards.md create mode 100644 steering_docs/rust-tech/README.md create mode 100644 steering_docs/rust-tech/basics.md create mode 100644 steering_docs/rust-tech/hello.md create mode 100644 steering_docs/rust-tech/metadata.md create mode 100644 steering_docs/rust-tech/orchestration.md create mode 100644 steering_docs/rust-tech/readme_writeme.md create mode 100644 steering_docs/rust-tech/tests.md create mode 100644 steering_docs/rust-tech/wrapper.md diff --git a/steering_docs/General-Code-Example-Standards.md b/steering_docs/General-Code-Example-Standards.md new file mode 100644 index 00000000000..470c942f53a --- /dev/null +++ b/steering_docs/General-Code-Example-Standards.md @@ -0,0 +1,196 @@ +
+

Program Flow and Readability

+ +### Guidelines +* Code examples should prioritize educational value and readability: +* Examples should not include code that only duplicates an API reference without additional information or context. Examples should do _something_ with response objects that contain information specific to the call. +* Function and variable names must be clear and free of typos, with consistent capitalization and naming patterns. +* Complex logic should include descriptive comments. +* Examples must not include unreacheable code, code that does not compile or run, or code that is commented out. +* Prefer to create files as part of the examples to limit including multiple "[example].txt" files throughout the repository. +* When constructing SQL queries, use parameterized values instead of directly concatenating commands with user input. +* AWS Console setup should not be required in order for the example to run. Any setup should happen as part of a CFN or CDK template or script, or in the program itself. The only exception is for "feature access", such as enabling access to a Bedrock model. +* Provide customers with appropriate context in published documentation through metadata and snippet organization. + +### Specification Instructions +* Describe the program flow and instructions for readability, file creation, and CFN or CDK resource deployment. +
+ +
+

Code Comments

+ +### Guidelines +* Code comments must be descriptive, use complete sentences (with punctuation), and be free of typos or grammatical errors. +* Language owners may establish their own patterns for comments (such as parameter and method descriptions) and should follow those patterns consistently. + +### Specification Instructions +* Describe any important comments that should be included in all implementations. +
+ +
+

Pagination

+ +### Guidelines +* When pagination is available (determined by checking the service client for paginators), the available paginator must be used. +* In cases where a subset of items is intentionally fetched, it should be noted in the code comments. +* If the intent is listing "all" items, pagination should be included to return all pages of data. +* Hello Service examples should still demonstrate pagination if available, but can log the total count of items (along with a subset of the list, if desired) instead of listing all items. + +### Specification Instructions +* Indicate where pagination is required. +
+ +
+

Waiters

+ +### Guidelines +* When a waiter is available, it should be used in the example. +* If no waiter is available, the program should poll for the applicable status before continuing. +* If a sleep() function is used, a descriptive comment must be included explaining why. + +### Specification Instructions +* Indicate where a waiter is required. +
+ +
+

Error Handling

+ +### Guidelines +* Each discrete service call with specific exceptions (such as "resource not found" or "resource already exists") should follow the appropriate spec to handle the error gracefully. + * If a spec is not available for an API call, exceptions should be extended with additional information about the action that the API call is being made for, and then raised as appropriate for the language. +* Examples should not break/quit for their exception handling unless there is a reason to do so, so that resources can be cleaned up gracefully. + +### Specification Instructions +* Each discrete service call with specific exceptions (such as "resource not found" or "resource already exists") should have an appropriate action to inform the user. + * **Examples:** + * A call to Create a resource that results in Resource Exists + * If the scenario can continue with any resource having the same name, the scenario spec may opt to inform the user and continue. + * If the scenario must have the specific resource it attempted to create, the scenario spec should inform the user with an error and finish (possibly without cleanup). + * A call to Describe resource that does not exist + * The scenario should warn the user that the resource does not exist, and may either continue execution if the resource is optional to the completion of the scenario or may skip to clean up of created resources if the resource was critical to the completion of the scenario. +* Some SDKs have modeled exceptions, while others have generic exceptions with string error codes and messages. The latter can be especially confusing to users, and scenarios should take care to properly identify either type of exception. +
+ +
+

Resource Strings

+ +### Guidelines +* Resource ARNs or names should either be entered by the user, provided as program arguments, or loaded from a separate configuration. +* Code should not use hard-coded strings or "" placeholders to access necessary resources. +* Examples should not use hard-coded Regions unless necessary for the example. + +### Specification Instructions +* Specify how program arguments should be loaded. +
+ +
+

S3 Bucket Names

+ +### Guidelines +* When creating a new bucket + * A user provided prefix for bucket names is required. If a prefix cannot be found, the example should exit without creating a bucket. + * Assume the existence of an environment variable, `S3_BUCKET_NAME_PREFIX`, that can be used if needed. + * The bucket prefix is postfixed with a unique id before use. e.g. `${S3_BUCKET_NAME_PREFIX}-${uuid()}` +* When referencing an existing bucket + * Access existing bucket names in whatever fashion is most appropriate, just don’t use `S3_BUCKET_NAME_PREFIX` +* Integration tests + * The same rules for new buckets apply to integration tests. If a user chooses to run our tests, but does not provide `S3_BUCKET_NAME_PREFIX`, the tests should fail. + +### Specification Instructions +* Specifications should identify places where use of the `S3_BUCKET_NAME_PREFIX` environment variable is required. + +
+ +
+

Security (Username/Passwords)

+ +### Guidelines +* User names and passwords or other security artifacts should be entered by the user and not referenced as hard-coded strings. +* They should not be stored or retained, and only pass through to the necessary service, in cases such as Cognito user setup or RDS admin setup actions which require a password. + +### Specification Instructions +* Describe any special handling for security items. +
+ +
+

Test Coverage

+ +### Guidelines +* New code should have test coverage (can be unit or integration) for each method or logical operation block. +* Refer to the SDK language specification page for test tool details. + +### Specification Instructions +* Follow general guidance for testing, no additional specification requirements. +
+ +
+

Configuration Explanations

+ +### Guidelines +* If any user configuration, program Args, or other setup is required, they should be described in the code comments and/or the README for that service or services(s). + +### Specification Instructions +* Include descriptions for configurations if they are language-agnostic. +
+ +
+

Resource Creation and Cleanup

+ +### Guidelines +* Scenarios should include one or more “clean up” functions. These should be highly error resistant, logging but not stopping on any resource removal errors. +* The clean up function should always run, possibly by storing any error(s) from the main body and reporting them after attempting clean up. +* Resources created as part of an example should be cleaned up as part of the program. +* Clean up can include a y/n question to the user before deleting resources. +* If a scenario does not complete due to errors, it should attempt to run the cleanup operation before exiting. + +### Specification Instructions +* Include a description if anything other than a y/n question is needed. +
+ +
+

Digital Assets and Sample Files

+ +### Guidelines +* Examples should follow the repository standards for adding and managing digital assets and sample files. + +### Specification Instructions +* Include instructions for retrieving/using any shared digital assets. Prefer shared assets over duplication in each language folder. +
+ +
+

Hello Service

+ +### Guidelines +* Should demonstrate a single service action to get customers started using an SDK with a service. +* Should be copy-paste runnable to reduce any blocks for the user, ideally in a main function or similar. +* Include imports, service client creation, etc. +* Make a single service call to something that requires no input (ListBuckets, etc.). If Hello Service exists for other languages, use the same Action so they are all consistent. +* If pagination is appropriate/available, use it. You may also limit the number of results. +* Print something useful about the output, don't just dump the output (bucket names, etc.). +* Error handling is optional and only if it makes sense. + +### Specification Instructions +* The first implementation for an example (Basic or Workflow) must also include the Hello Service as part of the specification. +
+ +
+

SDK Language Tools

+ +| Language | Package | Version | Formatter | Linter | Checker | Unit | Base Language Guide | +| -------- | ------- | --------| --------- | ------ | ------- | ---- | -------------- | +|CLI | | | |shellcheck | | |[Shellcheck linter](https://github.com/koalaman/shellcheck)| | +|C++ |git |main | | | | |[C++ Coding Standards Guide](https://github.com/aws/aws-sdk-cpp/blob/main/docs/CODING_STANDARDS.md) | +|.NET |nuget |SDK V3 (.NET 6 or later) |dotnet format | dotnet format | dotnet build | XUnit | [C# (.NET) Code Conventions](https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/coding-style.md)| +|Go |Go Mod |go-v2 v1.15.3 |gofmt |`golangci-lint` |go build |testing (builtin) |[Go dev](https://go.dev/) | +|Java |Maven |2 | |checkstyle |checkstyle |JUnit |[Oracle Java Code Conventions](https://www.oracle.com/java/technologies/javase/codeconventions-contents.html) | +|JavaScript |NPM |^3.210.0 |prettier |eslint |typescript |vitest |[AirBnB base guide](https://github.com/airbnb/javascript) | +|Kotlin |gradle |0.30.1-beta |ktfmt |ktlint |kotlin | |[Kotlin Coding Conventions](https://kotlinlang.org/docs/coding-conventions.html) | +|PHP |composer |3.283.2 |phpcs (PSR-12) |phpcs |php |phpunit |[PSR-12 Basic Coding Standard for PHP](https://www.php-fig.org/psr/psr-12/) | +|Python |Pip |boto3>= 1.26.79 |Black |pylint |mypy |pytest |[PEP 8 - Style Guide for Python Code](https://peps.python.org/pep-0008/) | +|Ruby |gem | | | | | |[Ruby Style Guide.](https://github.com/rubocop/ruby-style-guide) | +|Rust |Cargo |next |cargo fmt |cargo clippy |cargo check |cargo test |[Rust Style Guide](https://doc.rust-lang.org/nightly/style-guide/) | +|Swift | |0.28.0 | | | | |[Swift Style Guide](https://google.github.io/swift/) | +
+ + + diff --git a/steering_docs/Rust-code-example-standards.md b/steering_docs/Rust-code-example-standards.md new file mode 100644 index 00000000000..4a299a5ed7f --- /dev/null +++ b/steering_docs/Rust-code-example-standards.md @@ -0,0 +1,215 @@ +## General Structure + +* One example crate per SDK service, with the same name as the published crate minus `aws-sdk-`. +* Each crate has a `lib.rs` in the root, which uses pub mod for each module part. + * Skipped if crates only have bin examples. +* `bin` dir for all binaries (CLI programs and Scenario runners). + * Keep user interaction mainly in the bin areas, do not put user interaction in supporting libraries. + * Use the [inquire](https://crates.io/crates/inquire) crate for complex user input, and `stdin().read_line()` for simple user input. +* Decompose scenario actions into functions in `lib/[scenario_name].rs`, possibly as part of a struct with scenario information to manage the communication saga as en Epic. (See [aurora scenario](https://github.com/awsdocs/aws-doc-sdk-examples/blob/de7b1ee3fae2e3cd7d81a24c17345040f76b1d75/rustv1/examples/aurora/src/aurora_scenario/mod.rs)) +* One client wrapper for integration test mocking, if necessary. (See [aurora/src/rds.rs](https://github.com/awsdocs/aws-doc-sdk-examples/blob/de7b1ee3fae2e3cd7d81a24c17345040f76b1d75/rustv1/examples/aurora/src/rds.rs)) +* Initialize client once, in main, using behavior_subject, and clone when necessary. + +## General Program Flow and Readability + +* When to prefer Loop vs Iterator: + * If there is a clear transformation from `T` to `U`, prefer an iterator. + * Do not nest control flow inside an iterator. + * Extract the logic to a dedicated function. + * Prefer a for loop if the function would be difficult to extract. + * Prefer an extracted function if the logic is nuanced and should be tested. +* How deep to go in nesting vs when to extract a new function? + * Two deep is fine, three deep is pushing it, four deep is probably too much. + * Prefer an extracted function if the logic is nuanced and should be tested. +* When to Trait vs bare functions? + * Examples rarely have enough complexity that a Trait is worth the mental overhead. + * bare functions or a struct to manage the Epic communication saga, if necessary. + +## Pagination, Waiters, and Error Handling + +* All operations are async. +* Use [tokio](https://tokio.rs/) for async runtime. +* List operations typically provide `.into_paginator()` as an async iterator. Use them whenever available, unless the example is specifically showing non-paginator pieces. + ```rust + let page_size = page_size.unwrap_or(10); + let items: Result, _> = client + .scan() + .table_name(table) + .limit(page_size) + .into_paginator() + .items() + .send() + .collect() + .await; + + println!("Items in table (up to {page_size}):"); + for item in items? { + println!(" {:?}", item); + } + ``` +* Use builtin waters as `client.wait_until_...` whenever available. (Example in [EC2](https://github.com/awsdocs/aws-doc-sdk-examples/blob/2546e4ac8c7963c5a97ac838917e9b9dcbe0ba29/rustv1/examples/ec2/src/bin/reboot-instance.rs#L29-L51)) + ```rust + let wait_status_ok = client + .wait_until_instance_status_ok() + .instance_ids(id) + .wait(Duration::from_secs(60)) + .await; + + match wait_status_ok { + Ok(_) => println!("Rebooted instance {id}, it is started with status OK."), + Err(err) => return Err(err.into()), + } + ``` +* Modeled errors for scenarios (see [Aurora](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/rustv1/examples/aurora/src/aurora_scenario/mod.rs#L55-L85)) (hand-modeled or thiserror). + ```rust + #[derive(Debug, PartialEq, Eq)] + pub struct ScenarioError { + message: String, + context: Option, + } + + impl ScenarioError { + pub fn with(message: impl Into) -> Self { + ScenarioError { + message: message.into(), + context: None, + } + } + + pub fn new(message: impl Into, err: &dyn ProvideErrorMetadata) -> Self { + ScenarioError { + message: message.into(), + context: Some(MetadataError::from(err)), + } + } + } + + impl std::error::Error for ScenarioError {} + impl Display for ScenarioError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.context { + Some(c) => write!(f, "{}: {}", self.message, c), + None => write!(f, "{}", self.message), + } + } + } + ``` +* Client::Error for one-off “scripts” or single-action examples. (See example in [EC2](https://github.com/awsdocs/aws-doc-sdk-examples/blob/de7b1ee3fae2e3cd7d81a24c17345040f76b1d75/rustv1/examples/ec2/src/bin/ec2-helloworld.rs#L21-L33)). + ```rust + use aws_sdk_ec2::{Client, Error}; + + async fn show_regions(client: &Client) -> Result<(), Error> { + let rsp = client.describe_regions().send().await?; + + for region in rsp.regions() { + // ... + } + + Ok(()) + } + ``` +* Anyhow and .into_service_error for things in the middle. (See example in [SES](https://github.com/awsdocs/aws-doc-sdk-examples/blob/208abff74308c11f700d8321eab0f625393ffdb4/rustv1/examples/ses/src/newsletter.rs#L239-L256)). + ```rust + let contacts: Vec = match self + .client + .list_contacts() + .contact_list_name(CONTACT_LIST_NAME) + .send() + .await + { + Ok(list_contacts_output) => { + list_contacts_output.contacts.unwrap().into_iter().collect() + } + Err(e) => { + return Err(anyhow!( + "Error retrieving contact list {}: {}", + CONTACT_LIST_NAME, + e + )) + } + }; + ``` + +## Runtime Resources + +* include_bytes! or include_str! for compile-time data. +* fs::read_to_string for glob load, fs::File for streaming, tie to SdkBody when needed (see example in s3). +* When showing examples that handle PII, use the [secrets](https://crates.io/crates/secrets) crate. + +## Test Coverage + +* Unit tests in `#[cfg(test)] mod test` or `test.rs`, integration tests in the tests folder. + * See fully worked example in [Aurora](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/rustv1/examples/aurora/src/aurora_scenario/tests.rs) + * Unit test + ```rust + use crate::rds::MockRdsImpl; + + #[tokio::test] + async fn test_scenario_set_engine_not_create() { + let mut mock_rds = MockRdsImpl::default(); + + mock_rds + .expect_create_db_cluster_parameter_group() + .with( + eq("RustSDKCodeExamplesDBParameterGroup"), + eq("Parameter Group created by Rust SDK Code Example"), + eq("aurora-mysql"), + ) + .return_once(|_, _, _| Ok(CreateDbClusterParameterGroupOutput::builder().build())); + + let mut scenario = AuroraScenario::new(mock_rds); + + let set_engine = scenario.set_engine("aurora-mysql", "aurora-mysql8.0").await; + + assert!(set_engine.is_err()); + } + ``` + * Mocks + ```rust + #[cfg(test)] + use mockall::automock; + + #[cfg(test)] + pub use MockRdsImpl as Rds; + #[cfg(not(test))] + pub use RdsImpl as Rds; + + pub struct RdsImpl { + pub inner: RdsClient, + } + + #[cfg_attr(test, automock)] + impl RdsImpl { + pub fn new(inner: RdsClient) -> Self { + RdsImpl { inner } + } + + pub async fn describe_db_engine_versions( + &self, + engine: &str, + ) -> Result> { + self.inner + .describe_db_engine_versions() + .engine(engine) + .send() + .await + } + + // etc + } + ``` + +* Coverage with [cargo-llm-cov](https://lib.rs/crates/cargo-llvm-cov) + +## Configuration Explanations + +* For command line examples, prefer using clap and command line args. +* For server and lambda examples, prefer using args. +* Use the actix-web crate for HTTP servers. + +## AWS Resource Creation and Cleanup + +* For standalone scenarios, there are generally one or more “clean up” functions. These should be highly error resistant, logging but not stopping on any resource removal errors. The clean up function should always run, possibly by storing any error(s) from the main body and reporting them after attempting clean up. +* When implementing a scenario, try to start with the clean up. +* Scenarios requiring clean up may add a “—no-cleanup” or "—cleanup=false" flag to skip performing the cleanup step. +* Follow the spec for other cleanup decisions. \ No newline at end of file diff --git a/steering_docs/rust-tech.md b/steering_docs/rust-tech.md index 90826c4267d..4ec2e598069 100644 --- a/steering_docs/rust-tech.md +++ b/steering_docs/rust-tech.md @@ -1,5 +1,16 @@ # Rust Technology Stack & Build System +## Overview +This document provides an overview of the Rust technology stack and build system for AWS SDK code examples. For detailed guidance on specific components, see the modular steering documents in the `rust-tech/` directory: + +- **[basics.md](rust-tech/basics.md)** - Interactive scenario generation +- **[hello.md](rust-tech/hello.md)** - Hello example generation +- **[wrapper.md](rust-tech/wrapper.md)** - Service wrapper generation +- **[tests.md](rust-tech/tests.md)** - Test generation and patterns +- **[metadata.md](rust-tech/metadata.md)** - Metadata generation +- **[orchestration.md](rust-tech/orchestration.md)** - Component orchestration +- **[readme_writeme.md](rust-tech/readme_writeme.md)** - README generation + ## Rust SDK v1 Development Environment ### Build Tools & Dependencies diff --git a/steering_docs/rust-tech/README.md b/steering_docs/rust-tech/README.md new file mode 100644 index 00000000000..f820a2b3159 --- /dev/null +++ b/steering_docs/rust-tech/README.md @@ -0,0 +1,227 @@ +# Rust Steering Documentation + +This directory contains modular steering documents for generating AWS SDK for Rust code examples. Each document focuses on a specific aspect of the code generation process. + +## Document Overview + +### Core Generation Documents + +#### [basics.md](basics.md) +**Purpose**: Interactive scenario generation +**Use When**: Creating complete workflow scenarios with user interaction +**Key Topics**: +- Specification-driven development +- Scenario phase structure (setup, demonstration, examination, cleanup) +- User interaction patterns with inquire crate +- Custom ScenarioError type +- Async/await patterns with tokio + +#### [hello.md](hello.md) +**Purpose**: Hello example generation +**Use When**: Creating the simplest possible service introduction +**Key Topics**: +- Mandatory hello examples for every service +- Pagination patterns for list operations +- Basic async/await with tokio +- Minimal example structure +- Direct client usage + +#### [wrapper.md](wrapper.md) +**Purpose**: Service wrapper generation +**Use When**: Mocking is needed for integration tests +**Key Topics**: +- Optional wrapper creation (only when needed) +- Mockall integration for testing +- Pagination patterns +- Waiter patterns +- Error handling strategies +- When to use traits vs bare functions + +### Testing and Quality + +#### [tests.md](tests.md) +**Purpose**: Test generation and patterns +**Use When**: Creating unit and integration tests +**Key Topics**: +- Unit tests with #[cfg(test)] +- Integration tests in tests/ folders +- Mockall for mocking AWS services +- Async testing with #[tokio::test] +- Code coverage with cargo-llvm-cov +- Test organization patterns + +### Documentation and Metadata + +#### [metadata.md](metadata.md) +**Purpose**: Metadata generation for documentation pipeline +**Use When**: Integrating examples with AWS documentation +**Key Topics**: +- Specification-based metadata keys +- Snippet tag format and conventions +- Service abbreviations +- Metadata validation +- Cross-service examples + +#### [readme_writeme.md](readme_writeme.md) +**Purpose**: README generation with writeme tool +**Use When**: Creating or updating service documentation +**Key Topics**: +- Writeme tool setup and usage +- README structure and content +- Documentation dependencies +- Cargo.toml documentation +- CI/CD integration + +### Orchestration + +#### [orchestration.md](orchestration.md) +**Purpose**: Component coordination and workflows +**Use When**: Understanding the complete development process +**Key Topics**: +- Full service implementation workflow +- Individual component updates +- Quality gates and validation +- Error recovery strategies +- CI/CD pipeline integration + +## Quick Start Guide + +### For New Service Implementation +1. **Read**: [orchestration.md](orchestration.md) for overall workflow +2. **Start with**: [hello.md](hello.md) to create basic connectivity example +3. **Add wrapper**: [wrapper.md](wrapper.md) if mocking is needed for tests +4. **Build scenario**: [basics.md](basics.md) following service specification +5. **Write tests**: [tests.md](tests.md) for coverage +6. **Add metadata**: [metadata.md](metadata.md) for documentation +7. **Generate docs**: [readme_writeme.md](readme_writeme.md) for README + +### For Updating Existing Examples +1. **Identify component**: Determine which document applies +2. **Read guidance**: Review the specific document +3. **Make changes**: Follow the patterns and requirements +4. **Validate**: Use the validation commands in the document +5. **Update docs**: Regenerate README if needed + +## Common Patterns + +### File Structure +``` +rustv1/examples/{service}/ +├── Cargo.toml +├── README.md +├── src/ +│ ├── lib.rs +│ ├── {service}.rs # Optional wrapper +│ ├── bin/ +│ │ ├── hello.rs # MANDATORY +│ │ └── {scenario-name}.rs +│ └── {scenario_name}/ +│ ├── mod.rs +│ ├── scenario.rs +│ └── tests/ +│ └── mod.rs +``` + +### Snippet Tag Format +```rust +// snippet-start:[{service}.rust.{action_name}] +// Code here +// snippet-end:[{service}.rust.{action_name}] +``` + +### Error Handling +```rust +// Custom error type for scenarios +#[derive(Debug, PartialEq, Eq)] +pub struct ScenarioError { + message: String, + context: Option, +} +``` + +### Async Pattern +```rust +#[tokio::main] +async fn main() -> Result<(), Error> { + let sdk_config = aws_config::defaults(BehaviorVersion::latest()) + .load() + .await; + let client = Client::new(&sdk_config); + // Operations here +} +``` + +## Standards and Requirements + +### General Code Standards +All Rust examples must follow: +- ✅ **Rust 2021 edition** or later +- ✅ **Async/await** with tokio runtime +- ✅ **Pagination** for list operations when available +- ✅ **Error handling** with Result types +- ✅ **Snake_case** naming conventions +- ✅ **Module documentation** with //! comments +- ✅ **Snippet tags** for all code examples + +### Mandatory Components +Every service MUST include: +- ✅ **Hello example** in `src/bin/hello.rs` +- ✅ **Cargo.toml** with proper dependencies +- ✅ **README.md** (generated with writeme) +- ✅ **Metadata** in `.doc_gen/metadata/{service}_metadata.yaml` + +### Optional Components +Create only when needed: +- Service wrapper in `src/{service}.rs` (for mocking) +- Integration tests in scenario tests/ folders +- Multiple scenario binaries + +## Validation Commands + +### Build and Test +```bash +cargo build # Build all examples +cargo test # Run all tests +cargo fmt --check # Check formatting +cargo clippy -- -D warnings # Run linter +``` + +### Documentation +```bash +cd .tools/readmes +source .venv/bin/activate +python -m writeme --languages Rust:1 --services {service} +``` + +## Related Documents + +### Parent Document +- [rust-tech.md](../rust-tech.md) - Main Rust technology overview + +### General Standards +- [General-Code-Example-Standards.md](../General-Code-Example-Standards.md) +- [Rust-code-example-standards.md](../Rust-code-example-standards.md) + +## Getting Help + +### Common Issues +- **Compilation errors**: Check [wrapper.md](wrapper.md) for error handling patterns +- **Test failures**: See [tests.md](tests.md) for testing patterns +- **Metadata errors**: Review [metadata.md](metadata.md) for correct format +- **Documentation issues**: Check [readme_writeme.md](readme_writeme.md) + +### Best Practices +- Always consult knowledge bases before starting +- Follow service specification exactly +- Use existing examples (like EC2) as reference +- Validate frequently during development +- Test both happy and error paths + +## Contributing + +When adding or updating steering documents: +1. Follow the established structure and format +2. Include practical examples and code snippets +3. Add validation commands and quality gates +4. Update this README with any new documents +5. Cross-reference related documents diff --git a/steering_docs/rust-tech/basics.md b/steering_docs/rust-tech/basics.md new file mode 100644 index 00000000000..0d1e5582b3b --- /dev/null +++ b/steering_docs/rust-tech/basics.md @@ -0,0 +1,497 @@ +# Rust Interactive Scenario Generation + +## MANDATORY: Knowledge Base Consultation (FIRST STEP) +**🚨 CRITICAL - Must be completed BEFORE any code generation** + +```bash +# Step 1: List available knowledge bases +ListKnowledgeBases() + +# Step 2: Query coding standards (REQUIRED) +QueryKnowledgeBases("coding-standards-KB", "Rust-code-example-standards") + +# Step 3: Query implementation patterns (REQUIRED) +QueryKnowledgeBases("Rust-premium-KB", "Rust implementation patterns structure") + +# Step 4: AWS service research (REQUIRED) +search_documentation("What is [AWS Service] and what are its key API operations?") +read_documentation("https://docs.aws.amazon.com/[service]/latest/[relevant-page]") +``` + +**FAILURE TO COMPLETE KNOWLEDGE BASE CONSULTATION WILL RESULT IN INCORRECT CODE STRUCTURE** + +## Purpose +Generate interactive scenarios that demonstrate complete workflows using multiple service operations in a guided, educational manner. Implementation must be based on the service SPECIFICATION.md file. + +## Requirements +- **Specification-Driven**: MUST read the `scenarios/basics/{service}/SPECIFICATION.md` +- **Interactive**: Use inquire crate for complex input, stdin().read_line() for simple input +- **Educational**: Break complex workflows into logical phases +- **Comprehensive**: Cover setup, demonstration, examination, and cleanup +- **Error Handling**: Use custom ScenarioError type for graceful error handling +- **Async Operations**: All AWS operations must be async with tokio runtime +- **Module Organization**: Decompose scenario actions into functions in `lib/{scenario_name}.rs` + +## File Structure +``` +rustv1/examples/{service}/ +├── Cargo.toml +├── README.md +├── src/ +│ ├── lib.rs +│ ├── {service}.rs # Service wrapper (if needed for mocking) +│ ├── bin/ +│ │ ├── hello.rs # MANDATORY: Hello scenario +│ │ └── {scenario-name}.rs # Scenario entry point +│ └── {scenario_name}/ +│ ├── mod.rs # Module exports +│ ├── scenario.rs # Main scenario logic +│ └── tests/ +│ └── mod.rs # Integration tests +``` + +## MANDATORY Pre-Implementation Steps + +### Step 1: Read Service Specification +**CRITICAL**: Always read `scenarios/basics/{service}/SPECIFICATION.md` first to understand: +- **API Actions Used**: Exact operations to implement +- **Proposed Example Structure**: Setup, demonstration, examination, cleanup phases +- **Error Handling**: Specific error codes and handling requirements +- **Scenario Flow**: Step-by-step workflow description + +### Step 2: Extract Implementation Requirements +From the specification, identify: +- **Setup Phase**: What resources need to be created/configured +- **Demonstration Phase**: What operations to demonstrate +- **Examination Phase**: What data to display and how to filter/analyze +- **Cleanup Phase**: What resources to clean up and user options + +## Scenario Binary Pattern (src/bin/{scenario-name}.rs) +```rust +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Purpose +//! +//! Shows how to use {AWS Service} to {scenario description}. This scenario demonstrates: +//! +//! 1. {Phase 1 description} +//! 2. {Phase 2 description} +//! 3. {Phase 3 description} +//! 4. {Phase 4 description} + +use aws_config::BehaviorVersion; +use {service}_code_examples::{run_scenario, ScenarioError}; + +#[tokio::main] +async fn main() -> Result<(), ScenarioError> { + tracing_subscriber::fmt::init(); + + let sdk_config = aws_config::defaults(BehaviorVersion::latest()) + .load() + .await; + let client = aws_sdk_{service}::Client::new(&sdk_config); + + run_scenario(client).await +} +``` + +## Scenario Implementation Pattern (src/{scenario_name}/scenario.rs) +```rust +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use aws_sdk_{service}::Client; +use std::fmt::Display; + +/// Custom error type for scenario operations +#[derive(Debug, PartialEq, Eq)] +pub struct ScenarioError { + message: String, + context: Option, +} + +impl ScenarioError { + pub fn with(message: impl Into) -> Self { + ScenarioError { + message: message.into(), + context: None, + } + } + + pub fn new(message: impl Into, err: &dyn std::error::Error) -> Self { + ScenarioError { + message: message.into(), + context: Some(err.to_string()), + } + } +} + +impl std::error::Error for ScenarioError {} + +impl Display for ScenarioError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.context { + Some(c) => write!(f, "{}: {}", self.message, c), + None => write!(f, "{}", self.message), + } + } +} + +/// Main scenario runner +pub async fn run_scenario(client: Client) -> Result<(), ScenarioError> { + println!("{}", "-".repeat(88)); + println!("Welcome to the {AWS Service} basics scenario!"); + println!("{}", "-".repeat(88)); + println!("{Service description and what users will learn}"); + println!(); + + let mut resource_id: Option = None; + + let result = async { + resource_id = Some(setup_phase(&client).await?); + demonstration_phase(&client, resource_id.as_ref().unwrap()).await?; + examination_phase(&client, resource_id.as_ref().unwrap()).await?; + Ok::<(), ScenarioError>(()) + } + .await; + + // Always attempt cleanup + if let Some(id) = resource_id { + cleanup_phase(&client, &id).await?; + } + + result +} + +/// Setup phase: Implement based on specification's Setup section +async fn setup_phase(client: &Client) -> Result { + println!("Setting up {AWS Service}..."); + println!(); + + // Check for existing resources + let existing_resources = list_resources(client).await?; + if !existing_resources.is_empty() { + println!("Found {} existing resource(s):", existing_resources.len()); + for resource in &existing_resources { + println!(" - {}", resource); + } + + let use_existing = inquire::Confirm::new("Would you like to use an existing resource?") + .with_default(false) + .prompt() + .map_err(|e| ScenarioError::with(format!("Failed to get user input: {}", e)))?; + + if use_existing { + return Ok(existing_resources[0].clone()); + } + } + + // Create new resource + let resource_name = inquire::Text::new("Enter resource name:") + .prompt() + .map_err(|e| ScenarioError::with(format!("Failed to get resource name: {}", e)))?; + + create_resource(client, &resource_name).await +} + +/// Demonstration phase: Implement operations from specification +async fn demonstration_phase(client: &Client, resource_id: &str) -> Result<(), ScenarioError> { + println!("Demonstrating {AWS Service} capabilities..."); + println!(); + + // Implement specific operations from specification + generate_sample_data(client, resource_id).await?; + println!("✓ Sample data created successfully"); + + // Wait if specified in the specification + println!("Waiting for data to be processed..."); + tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; + + Ok(()) +} + +/// Examination phase: Implement data analysis from specification +async fn examination_phase(client: &Client, resource_id: &str) -> Result<(), ScenarioError> { + println!("Examining {AWS Service} data..."); + println!(); + + // List and examine data as specified + let data_items = list_data(client, resource_id).await?; + if data_items.is_empty() { + println!("No data found. Data may take a few minutes to appear."); + return Ok(()); + } + + println!("Found {} data item(s)", data_items.len()); + + // Get detailed information as specified + let detailed_data = get_data_details(client, resource_id, &data_items[..5.min(data_items.len())]).await?; + display_data_summary(&detailed_data); + + // Show detailed view if specified + if !detailed_data.is_empty() { + let show_details = inquire::Confirm::new("Would you like to see detailed information?") + .with_default(false) + .prompt() + .map_err(|e| ScenarioError::with(format!("Failed to get user input: {}", e)))?; + + if show_details { + display_data_details(&detailed_data[0]); + } + } + + // Filter data as specified + filter_data_by_criteria(&data_items); + + Ok(()) +} + +/// Cleanup phase: Implement cleanup options from specification +async fn cleanup_phase(client: &Client, resource_id: &str) -> Result<(), ScenarioError> { + println!("Cleanup options:"); + println!("Note: Deleting the resource will stop all monitoring/processing."); + + let delete_resource = inquire::Confirm::new("Would you like to delete the resource?") + .with_default(false) + .prompt() + .map_err(|e| ScenarioError::with(format!("Failed to get user input: {}", e)))?; + + if delete_resource { + match delete_resource_impl(client, resource_id).await { + Ok(_) => println!("✓ Deleted resource: {}", resource_id), + Err(e) => println!("Error deleting resource: {}", e), + } + } else { + println!("Resource {} will continue running.", resource_id); + println!("You can manage it through the AWS Console or delete it later."); + } + + Ok(()) +} + +// Helper functions for AWS operations +async fn list_resources(client: &Client) -> Result, ScenarioError> { + // Implementation + Ok(vec![]) +} + +async fn create_resource(client: &Client, name: &str) -> Result { + // Implementation + Ok(name.to_string()) +} + +async fn generate_sample_data(client: &Client, resource_id: &str) -> Result<(), ScenarioError> { + // Implementation + Ok(()) +} + +async fn list_data(client: &Client, resource_id: &str) -> Result, ScenarioError> { + // Implementation + Ok(vec![]) +} + +async fn get_data_details(client: &Client, resource_id: &str, items: &[String]) -> Result, ScenarioError> { + // Implementation + Ok(items.to_vec()) +} + +async fn delete_resource_impl(client: &Client, resource_id: &str) -> Result<(), ScenarioError> { + // Implementation + Ok(()) +} + +fn display_data_summary(data: &[String]) { + println!("Data Summary:"); + for item in data { + println!(" • {}", item); + } +} + +fn display_data_details(item: &str) { + println!("Detailed Information:"); + println!(" {}", item); +} + +fn filter_data_by_criteria(items: &[String]) { + println!("Filtering data..."); + // Implementation +} +``` + +## Scenario Phase Structure (Based on Specification) + +### Setup Phase +- **Read specification Setup section** for exact requirements +- Check for existing resources as specified +- Create necessary resources using async operations +- Configure service settings per specification +- Verify setup completion as described + +### Demonstration Phase +- **Follow specification Demonstration section** exactly +- Perform core service operations using async operations +- Generate sample data if specified in the specification +- Show service capabilities as outlined +- Provide educational context from specification + +### Examination Phase +- **Implement specification Examination section** requirements +- List and examine results using async operations +- Filter and analyze data as specified +- Display detailed information per specification format +- Allow user interaction as described in specification + +### Cleanup Phase +- **Follow specification Cleanup section** guidance +- Offer cleanup options with warnings from specification +- Handle cleanup errors gracefully +- Provide alternative management options as specified +- Confirm completion per specification + +## User Interaction Patterns + +### Using inquire Crate (Complex Input) +```rust +use inquire::{Confirm, Text, Select}; + +// Yes/No questions +let use_existing = Confirm::new("Use existing resource?") + .with_default(false) + .prompt() + .map_err(|e| ScenarioError::with(format!("Failed to get input: {}", e)))?; + +// Text input +let resource_name = Text::new("Enter resource name:") + .prompt() + .map_err(|e| ScenarioError::with(format!("Failed to get name: {}", e)))?; + +// Selection from list +let choice = Select::new("Select an option:", vec!["Option 1", "Option 2"]) + .prompt() + .map_err(|e| ScenarioError::with(format!("Failed to get selection: {}", e)))?; +``` + +### Using stdin (Simple Input) +```rust +use std::io::{stdin, stdout, Write}; + +let mut input = String::new(); +print!("Enter value: "); +stdout().flush().unwrap(); +stdin().read_line(&mut input) + .map_err(|e| ScenarioError::with(format!("Failed to read input: {}", e)))?; +let value = input.trim(); +``` + +### Information Display +```rust +// Progress indicators +println!("✓ Operation completed successfully"); +println!("⚠ Warning message"); +println!("✗ Error occurred"); + +// Formatted output +println!("{}", "-".repeat(60)); +println!("Found {} items:", items.len()); +for item in items { + println!(" • {}", item); +} +``` + +## Error Handling Requirements + +### Custom ScenarioError Type +```rust +#[derive(Debug, PartialEq, Eq)] +pub struct ScenarioError { + message: String, + context: Option, +} + +impl ScenarioError { + pub fn with(message: impl Into) -> Self { + ScenarioError { + message: message.into(), + context: None, + } + } + + pub fn new(message: impl Into, err: &dyn std::error::Error) -> Self { + ScenarioError { + message: message.into(), + context: Some(err.to_string()), + } + } +} +``` + +### Error Handling from Specification +```rust +// Handle service-specific errors based on specification +match operation(client).await { + Ok(result) => result, + Err(e) => { + if let Some(service_err) = e.as_service_error() { + match service_err { + // Handle specific errors per specification + _ => return Err(ScenarioError::new("Operation failed", &e)), + } + } else { + return Err(ScenarioError::new("Operation failed", &e)); + } + } +} +``` + +## Cargo.toml Configuration +```toml +[package] +name = "{service}-examples" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "hello" +path = "src/bin/hello.rs" + +[[bin]] +name = "{scenario-name}" +path = "src/bin/{scenario-name}.rs" + +[dependencies] +aws-config = { version = "1.0", features = ["behavior-version-latest"] } +aws-sdk-{service} = "1.0" +tokio = { version = "1.0", features = ["full"] } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +inquire = "0.7" +``` + +## Scenario Requirements +- ✅ **ALWAYS** read and implement based on `scenarios/basics/{service}/SPECIFICATION.md` +- ✅ **ALWAYS** include module-level documentation explaining scenario steps from specification +- ✅ **ALWAYS** use inquire for complex input, stdin for simple input +- ✅ **ALWAYS** use async/await with tokio runtime +- ✅ **ALWAYS** implement proper cleanup that always runs +- ✅ **ALWAYS** break scenario into logical phases per specification +- ✅ **ALWAYS** include error handling per specification's Errors section +- ✅ **ALWAYS** provide educational context and explanations from specification +- ✅ **ALWAYS** handle edge cases (no resources found, etc.) as specified +- ✅ **ALWAYS** use custom ScenarioError type for error handling + +## Implementation Workflow + +### Step-by-Step Implementation Process +1. **Read Specification**: Study `scenarios/basics/{service}/SPECIFICATION.md` thoroughly +2. **Extract API Actions**: Note all API actions listed in "API Actions Used" section +3. **Map to Operations**: Ensure all required actions are available +4. **Implement Phases**: Follow the "Proposed example structure" section exactly +5. **Add Error Handling**: Implement error handling per the "Errors" section +6. **Test Against Specification**: Verify implementation matches specification requirements + +## Educational Elements +- **Use specification descriptions**: Explain operations using specification language +- Show before/after states as outlined in specification +- Provide context about service capabilities from specification +- Include tips and best practices mentioned in specification +- Follow the educational flow described in specification structure diff --git a/steering_docs/rust-tech/hello.md b/steering_docs/rust-tech/hello.md new file mode 100644 index 00000000000..4304c08954a --- /dev/null +++ b/steering_docs/rust-tech/hello.md @@ -0,0 +1,336 @@ +# Rust Hello Examples Generation + +## MANDATORY: Knowledge Base Consultation (FIRST STEP) +**🚨 CRITICAL - Must be completed BEFORE any code generation** + +```bash +# Step 1: List available knowledge bases +ListKnowledgeBases() + +# Step 2: Query coding standards (REQUIRED) +QueryKnowledgeBases("coding-standards-KB", "Rust-code-example-standards") + +# Step 3: Query implementation patterns (REQUIRED) +QueryKnowledgeBases("Rust-premium-KB", "Rust implementation patterns") + +# Step 4: AWS service research (REQUIRED) +search_documentation("What is [AWS Service] and what are its key API operations?") +read_documentation("https://docs.aws.amazon.com/[service]/latest/[relevant-page]") +``` + +**FAILURE TO COMPLETE KNOWLEDGE BASE CONSULTATION WILL RESULT IN INCORRECT CODE STRUCTURE** + +## Purpose +Generate simple "Hello" examples that demonstrate basic service connectivity and the most fundamental operation using direct AWS SDK for Rust client calls. + +## Requirements +- **MANDATORY**: Every AWS service MUST include a "Hello" scenario +- **Simplicity**: Should be the most basic, minimal example possible +- **Standalone**: Must work independently of other examples +- **Direct Client**: Use AWS SDK for Rust client directly +- **Async**: Use tokio runtime for async operations +- **Pagination**: Use paginators when available for list operations + +## File Structure +``` +rustv1/examples/{service}/ +├── Cargo.toml +├── src/ +│ ├── lib.rs # Module exports (if needed) +│ └── bin/ +│ └── hello.rs # MANDATORY: Hello scenario +``` + +**CRITICAL:** +- ✅ Hello example MUST be in `src/bin/hello.rs` +- ✅ MUST be the simplest possible example +- ✅ MUST use async/await with tokio runtime +- ✅ MUST use pagination for list operations when available + +## Hello Example Pattern +```rust +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Purpose +//! +//! Shows how to list {resources} using the AWS SDK for Rust. + +use aws_config::BehaviorVersion; +use aws_sdk_{service}::Client; + +// snippet-start:[{service}.rust.hello] +#[tokio::main] +async fn main() -> Result<(), aws_sdk_{service}::Error> { + tracing_subscriber::fmt::init(); + + let sdk_config = aws_config::defaults(BehaviorVersion::latest()) + .load() + .await; + let client = Client::new(&sdk_config); + + println!("Hello, {AWS Service}! Let's list available {resources}:"); + println!(); + + // Use pagination to retrieve all {resources} + let mut {resources}_paginator = client + .list_{resources}() + .into_paginator() + .send(); + + let mut {resources} = Vec::new(); + while let Some(page) = {resources}_paginator.next().await { + let page = page?; + {resources}.extend(page.{resources}().to_vec()); + } + + println!("{} {resource}(s) retrieved.", {resources}.len()); + + for {resource} in {resources} { + println!("\t{}", {resource}.name().unwrap_or("Unknown")); + } + + Ok(()) +} +// snippet-end:[{service}.rust.hello] +``` + +## Hello Example with Simple List (No Pagination) +For services where pagination is not available or not needed: + +```rust +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! Purpose +//! +//! Shows how to list {resources} using the AWS SDK for Rust. + +use aws_config::BehaviorVersion; +use aws_sdk_{service}::Client; + +// snippet-start:[{service}.rust.hello] +#[tokio::main] +async fn main() -> Result<(), aws_sdk_{service}::Error> { + tracing_subscriber::fmt::init(); + + let sdk_config = aws_config::defaults(BehaviorVersion::latest()) + .load() + .await; + let client = Client::new(&sdk_config); + + println!("Hello, {AWS Service}! Let's list available {resources}:"); + println!(); + + let response = client.list_{resources}().send().await?; + let {resources} = response.{resources}(); + + println!("{} {resource}(s) retrieved.", {resources}.len()); + + for {resource} in {resources} { + println!("\t{}", {resource}.name().unwrap_or("Unknown")); + } + + Ok(()) +} +// snippet-end:[{service}.rust.hello] +``` + +## Hello Examples by Service Type + +### List-Based Services (S3, DynamoDB, etc.) +- **Operation**: List primary resources (buckets, tables, etc.) +- **Message**: Show count and names of resources +- **Pagination**: Use `.into_paginator()` when available + +### Status-Based Services (GuardDuty, Config, etc.) +- **Operation**: Check service status or list detectors/configurations +- **Message**: Show service availability and basic status +- **Pagination**: Use if available for list operations + +### Compute Services (EC2, Lambda, etc.) +- **Operation**: List instances/functions or describe regions +- **Message**: Show available resources or regions +- **Pagination**: Essential for potentially large result sets + +## Pagination Pattern +```rust +// Use pagination to retrieve all results +let mut items_paginator = client + .list_items() + .into_paginator() + .send(); + +let mut items = Vec::new(); +while let Some(page) = items_paginator.next().await { + let page = page?; + items.extend(page.items().to_vec()); +} + +println!("Retrieved {} items", items.len()); +``` + +## Alternative Pagination Pattern (Using collect) +```rust +// Collect all items from paginator +let items: Result, _> = client + .list_items() + .into_paginator() + .items() + .send() + .collect() + .await; + +let items = items?; +println!("Retrieved {} items", items.len()); +``` + +## Error Handling Pattern +```rust +#[tokio::main] +async fn main() -> Result<(), aws_sdk_{service}::Error> { + // All AWS operations return Result types + // Use ? operator for simple error propagation + let response = client.list_resources().send().await?; + + // Or handle errors explicitly + match client.list_resources().send().await { + Ok(response) => { + println!("Success: {} resources", response.resources().len()); + } + Err(e) => { + eprintln!("Error listing resources: {}", e); + return Err(e); + } + } + + Ok(()) +} +``` + +## Cargo.toml Configuration +```toml +[package] +name = "{service}-examples" +version = "0.1.0" +edition = "2021" + +[[bin]] +name = "hello" +path = "src/bin/hello.rs" + +[dependencies] +aws-config = { version = "1.0", features = ["behavior-version-latest"] } +aws-sdk-{service} = "1.0" +tokio = { version = "1.0", features = ["full"] } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } +``` + +## Validation Requirements +- ✅ **Must run without errors** (with proper credentials) +- ✅ **Must handle credential issues gracefully** +- ✅ **Must display meaningful output** +- ✅ **Must use tokio runtime with #[tokio::main]** +- ✅ **Must use pagination for list operations when available** +- ✅ **Must include proper module-level documentation** +- ✅ **Must use correct snippet tag format**: `[{service}.rust.hello]` + +## Common Patterns +- Use `#[tokio::main]` for async main function +- Initialize tracing with `tracing_subscriber::fmt::init()` +- Load AWS config with `aws_config::defaults(BehaviorVersion::latest()).load().await` +- Create client with `Client::new(&sdk_config)` +- Use pagination to retrieve all results when available +- Return `Result<(), aws_sdk_{service}::Error>` from main +- Keep it as simple as possible - all logic in main function +- Use `?` operator for error propagation + +## Snippet Tag Format +**CRITICAL**: Use the correct snippet tag format: + +```rust +// ✅ CORRECT +// snippet-start:[{service}.rust.hello] +#[tokio::main] +async fn main() -> Result<(), aws_sdk_{service}::Error> { + // Implementation +} +// snippet-end:[{service}.rust.hello] + +// ❌ WRONG - Old format +// snippet-start:[rust.example_code.{service}.hello] +// snippet-end:[rust.example_code.{service}.hello] +``` + +**Format**: `[{service}.rust.hello]` +- Service name in snake_case (e.g., s3, dynamodb, ec2) +- Always use `.rust.hello` for Hello examples + +## Module Documentation +```rust +//! Purpose +//! +//! Shows how to {operation description} using the AWS SDK for Rust. +//! +//! # Arguments +//! +//! * None - uses default AWS configuration +//! +//! # Example +//! +//! ```no_run +//! cargo run --bin hello +//! ``` +``` + +## Running the Hello Example +```bash +# From the service directory +cargo run --bin hello + +# With specific AWS profile +AWS_PROFILE=myprofile cargo run --bin hello + +# With specific region +AWS_REGION=us-west-2 cargo run --bin hello + +# With debug logging +RUST_LOG=debug cargo run --bin hello +``` + +## Common Hello Operations by Service + +### S3 +- **Operation**: `list_buckets()` +- **Display**: Bucket names and creation dates +- **Pagination**: Not needed (typically small result set) + +### DynamoDB +- **Operation**: `list_tables()` +- **Display**: Table names +- **Pagination**: Use `.into_paginator()` for many tables + +### EC2 +- **Operation**: `describe_regions()` or `describe_instances()` +- **Display**: Region names or instance IDs +- **Pagination**: Use for `describe_instances()` + +### Lambda +- **Operation**: `list_functions()` +- **Display**: Function names and runtimes +- **Pagination**: Use `.into_paginator()` for many functions + +### IAM +- **Operation**: `list_users()` or `list_roles()` +- **Display**: User/role names +- **Pagination**: Use `.into_paginator()` for large organizations + +## Best Practices +- ✅ **Keep it simple**: Hello examples should be minimal +- ✅ **Use pagination**: Always use paginators when available +- ✅ **Show meaningful output**: Display useful information, not raw dumps +- ✅ **Handle errors gracefully**: Use Result types and ? operator +- ✅ **Include documentation**: Module-level docs explaining the example +- ✅ **Follow naming conventions**: Use snake_case for Rust identifiers +- ✅ **Initialize tracing**: Include tracing_subscriber for debugging +- ✅ **Use latest SDK patterns**: BehaviorVersion::latest() for config diff --git a/steering_docs/rust-tech/metadata.md b/steering_docs/rust-tech/metadata.md new file mode 100644 index 00000000000..4e5fce4fb3e --- /dev/null +++ b/steering_docs/rust-tech/metadata.md @@ -0,0 +1,329 @@ +# Rust Metadata Generation + +## Purpose +Generate documentation metadata files that integrate with AWS Documentation pipeline for snippet extraction and cross-referencing. + +## Requirements +- **Specification First**: Always check service specification for exact metadata keys +- **Snippet Tags**: Match snippet tags in code exactly +- **Complete Coverage**: Include all actions and scenarios from specification + +## File Structure +``` +.doc_gen/metadata/ +├── {service}_metadata.yaml # Service metadata file +``` + +## Metadata Discovery Process + +### Step 1: Check Service Specification +**CRITICAL**: Always read `scenarios/basics/{service}/SPECIFICATION.md` first for metadata requirements. + +Look for the metadata table: +```markdown +## Metadata + +|action / scenario |metadata file |metadata key | +|--- |--- |--- | +|`CreateDetector` |{service}_metadata.yaml |{service}_CreateDetector | +|`GetDetector` |{service}_metadata.yaml |{service}_GetDetector | +|`Service Basics Scenario` |{service}_metadata.yaml |{service}_Scenario | +``` + +### Step 2: Use Exact Metadata Keys +**NEVER** create custom metadata keys when specification defines them. Use the exact keys from the specification table. + +## Metadata File Pattern +```yaml +# .doc_gen/metadata/{service}_metadata.yaml + +{service}_CreateResource: + title: Create a &{ServiceAbbrev}; resource + title_abbrev: Create a resource + synopsis: create a &{ServiceAbbrev}; resource. + category: Actions + languages: + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/{service} + excerpts: + - description: + snippet_tags: + - {service}.rust.create_resource + services: + {service}: {CreateResource} + +{service}_GetResource: + title: Get a &{ServiceAbbrev}; resource + title_abbrev: Get a resource + synopsis: get a &{ServiceAbbrev}; resource. + category: Actions + languages: + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/{service} + excerpts: + - description: + snippet_tags: + - {service}.rust.get_resource + services: + {service}: {GetResource} + +{service}_Scenario: + title: Get started with &{ServiceAbbrev}; resources + title_abbrev: Get started with resources + synopsis: learn the basics of &{ServiceAbbrev}; by creating resources and managing them. + category: Scenarios + languages: + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/{service} + excerpts: + - description: Run an interactive scenario demonstrating {Service} basics. + snippet_tags: + - {service}.rust.scenario + services: + {service}: {CreateResource, GetResource, ListResources, DeleteResource} + +{service}_Hello: + title: Hello &{ServiceAbbrev}; + title_abbrev: Hello &{ServiceAbbrev}; + synopsis: get started using &{ServiceAbbrev};. + category: Hello + languages: + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/{service} + excerpts: + - description: + snippet_tags: + - {service}.rust.hello + services: + {service}: {ListResources} +``` + +## Snippet Tag Requirements + +### Code Snippet Tags +All code must include proper snippet tags that match metadata: + +```rust +// snippet-start:[{service}.rust.{action_name}] +pub async fn action_function(client: &Client) -> Result<(), Error> { + // Action implementation +} +// snippet-end:[{service}.rust.{action_name}] +``` + +### Hello Tags +```rust +// snippet-start:[{service}.rust.hello] +#[tokio::main] +async fn main() -> Result<(), aws_sdk_{service}::Error> { + // Hello implementation +} +// snippet-end:[{service}.rust.hello] +``` + +### Scenario Tags +```rust +// snippet-start:[{service}.rust.scenario] +pub async fn run_scenario(client: Client) -> Result<(), ScenarioError> { + // Scenario implementation +} +// snippet-end:[{service}.rust.scenario] +``` + +## Snippet Tag Naming Conventions + +### Format +`[{service}.rust.{action_name}]` + +- **Service name**: Use snake_case (e.g., `s3`, `dynamodb`, `ec2`) +- **Language**: Always `rust` for Rust examples +- **Action name**: Use snake_case (e.g., `create_bucket`, `list_tables`, `hello`) + +### Examples +```rust +// ✅ CORRECT +// snippet-start:[s3.rust.create_bucket] +// snippet-start:[dynamodb.rust.list_tables] +// snippet-start:[ec2.rust.describe_instances] +// snippet-start:[guardduty.rust.hello] + +// ❌ WRONG - Don't use these formats +// snippet-start:[rust.example_code.s3.create_bucket] +// snippet-start:[S3.Rust.CreateBucket] +// snippet-start:[s3-rust-create-bucket] +``` + +## Service Abbreviations + +Common service abbreviations for metadata: +- **GuardDuty**: GD +- **DynamoDB**: DDB +- **Simple Storage Service**: S3 +- **Elastic Compute Cloud**: EC2 +- **Identity and Access Management**: IAM +- **Key Management Service**: KMS +- **Simple Notification Service**: SNS +- **Simple Queue Service**: SQS +- **Relational Database Service**: RDS +- **Lambda**: Lambda +- **CloudWatch**: CW + +## Metadata Categories + +### Actions +Individual service operations (CreateResource, GetResource, etc.) + +### Scenarios +Multi-step workflows demonstrating service usage + +### Hello +Simple introduction examples + +### Cross-service +Examples spanning multiple AWS services + +## Metadata Validation + +### Required Fields +- ✅ **title**: Descriptive title with service abbreviation +- ✅ **title_abbrev**: Shortened title +- ✅ **synopsis**: Brief description of what the example does +- ✅ **category**: Actions, Scenarios, Hello, or Cross-service +- ✅ **languages.Rust.versions**: SDK version information (sdk_version: 1) +- ✅ **github**: Path to example code (rustv1/examples/{service}) +- ✅ **snippet_tags**: Matching tags from code +- ✅ **services**: Service operations used + +### Validation Commands +```bash +# Validate metadata with writeme tool +cd .tools/readmes +python -m writeme --languages Rust:1 --services {service} +``` + +## Multiple Excerpts Pattern +For scenarios with multiple code sections: + +```yaml +{service}_Scenario: + title: Get started with &{ServiceAbbrev}; resources + title_abbrev: Get started with resources + synopsis: learn the basics of &{ServiceAbbrev}; by creating resources and managing them. + category: Scenarios + languages: + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/{service} + excerpts: + - description: Define the scenario error type. + snippet_tags: + - {service}.rust.scenario_error + - description: Run the main scenario workflow. + snippet_tags: + - {service}.rust.scenario + - description: Implement the setup phase. + snippet_tags: + - {service}.rust.setup_phase + - description: Implement the cleanup phase. + snippet_tags: + - {service}.rust.cleanup_phase + services: + {service}: {CreateResource, GetResource, ListResources, DeleteResource} +``` + +## Cross-Service Examples +For examples using multiple AWS services: + +```yaml +{service1}_{service2}_CrossService: + title: Use &{Service1Abbrev}; with &{Service2Abbrev}; + title_abbrev: Cross-service example + synopsis: use &{Service1Abbrev}; with &{Service2Abbrev}; to accomplish a task. + category: Cross-service + languages: + Rust: + versions: + - sdk_version: 1 + github: rustv1/cross_service/{example_name} + excerpts: + - description: + snippet_tags: + - {service1}.{service2}.rust.cross_service + services: + {service1}: {Operation1, Operation2} + {service2}: {Operation3, Operation4} +``` + +## Common Metadata Errors +- ❌ **Custom metadata keys** when specification exists +- ❌ **Mismatched snippet tags** between code and metadata +- ❌ **Missing service operations** in services section +- ❌ **Incorrect github paths** to example code (should be rustv1/examples/{service}) +- ❌ **Wrong service abbreviations** in titles +- ❌ **Missing required fields** in metadata structure +- ❌ **Wrong SDK version** (should be sdk_version: 1 for Rust) +- ❌ **Incorrect snippet tag format** (should be {service}.rust.{action}) + +## Metadata Generation Workflow +1. **Read specification** for exact metadata requirements +2. **Extract metadata table** from specification +3. **Create metadata file** using specification keys +4. **Add snippet tags** to all code files +5. **Validate metadata** with writeme tool +6. **Fix any validation errors** before completion + +## Example: Complete Metadata Entry +```yaml +s3_ListBuckets: + title: List &S3; buckets using an &AWS; SDK + title_abbrev: List buckets + synopsis: list &S3; buckets. + category: Actions + languages: + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/s3 + excerpts: + - description: List all buckets in your account. + snippet_tags: + - s3.rust.list_buckets + services: + s3: {ListBuckets} + +s3_Hello: + title: Hello &S3; + title_abbrev: Hello &S3; + synopsis: get started using &S3;. + category: Hello + languages: + Rust: + versions: + - sdk_version: 1 + github: rustv1/examples/s3 + excerpts: + - description: List buckets to demonstrate basic &S3; connectivity. + snippet_tags: + - s3.rust.hello + services: + s3: {ListBuckets} +``` + +## Best Practices +- ✅ **Follow specification exactly** for metadata keys +- ✅ **Use consistent snippet tag format** across all Rust examples +- ✅ **Include all service operations** in services section +- ✅ **Provide clear descriptions** for each excerpt +- ✅ **Validate before committing** using writeme tool +- ✅ **Use correct SDK version** (1 for Rust) +- ✅ **Match github paths** to actual file locations +- ✅ **Use snake_case** for service names in snippet tags diff --git a/steering_docs/rust-tech/orchestration.md b/steering_docs/rust-tech/orchestration.md new file mode 100644 index 00000000000..b1e8ead5ac9 --- /dev/null +++ b/steering_docs/rust-tech/orchestration.md @@ -0,0 +1,397 @@ +# Rust Code Generation Orchestration + +## Purpose +Coordinate the modular components to generate complete AWS SDK code examples. Each component can be used independently or in sequence. + +## Component Dependencies + +```mermaid +graph TD + A[Knowledge Base Consultation] --> B[Hello Example] + A --> C[Wrapper Module] + A --> D[Scenario] + + C --> E[Tests - Unit Tests] + C --> F[Tests - Integration Tests] + + B --> G[Metadata Generation] + D --> G + + G --> H[README Generation] + + I[Service Specification] --> G + I --> C + I --> D +``` + +## Execution Workflows + +### Full Service Implementation +Complete implementation of a new AWS service: + +```bash +# 1. Knowledge Base Consultation (MANDATORY FIRST) +# Use ListKnowledgeBases + QueryKnowledgeBases for standards and patterns + +# 2. Generate Core Components +# - Hello example: src/bin/hello.rs +# - Wrapper module: src/{service}.rs (if mocking needed) +# - Scenario: src/bin/{scenario-name}.rs and src/{scenario_name}/scenario.rs +# - Cargo.toml: Package configuration + +# 3. Generate Test Suite +# - Unit tests: #[cfg(test)] mod tests in source files +# - Integration tests: src/{scenario_name}/tests/mod.rs + +# 4. Generate Metadata +# - Read service specification for exact metadata keys +# - Create .doc_gen/metadata/{service}_metadata.yaml + +# 5. Generate Documentation +# - Run writeme tool to create/update README.md + +# 6. Build and Validate +# - Build with cargo build +# - Run tests with cargo test +# - Check formatting with cargo fmt --check +# - Run linter with cargo clippy +``` + +### Individual Component Updates + +#### Update Hello Example Only +```bash +# Focus: hello.md guidance +# Files: src/bin/hello.rs +# Validation: cargo run --bin hello +``` + +#### Update Wrapper Module Only +```bash +# Focus: wrapper.md guidance +# Files: src/{service}.rs +# Validation: cargo test --lib +``` + +#### Update Scenario Only +```bash +# Focus: basics.md guidance +# Files: src/bin/{scenario-name}.rs, src/{scenario_name}/scenario.rs +# Validation: cargo run --bin {scenario-name} +``` + +#### Update Tests Only +```bash +# Focus: tests.md guidance +# Files: Unit tests in source files, integration tests in tests/ folders +# Validation: cargo test +``` + +#### Update Metadata Only +```bash +# Focus: metadata.md guidance +# Files: .doc_gen/metadata/{service}_metadata.yaml +# Validation: Run writeme tool validation +``` + +#### Update Documentation Only +```bash +# Focus: README generation +# Files: README.md (generated) +# Validation: Check README completeness and accuracy +``` + +## Quality Gates + +### Component-Level Validation +Each component has specific validation requirements: + +#### Hello Example Validation +```bash +# Build hello example +cargo build --bin hello + +# Run hello example +cargo run --bin hello + +# Check for compilation errors +cargo check --bin hello +``` + +#### Wrapper Module Validation +```bash +# Build library +cargo build --lib + +# Run unit tests +cargo test --lib + +# Check for warnings +cargo clippy --lib +``` + +#### Scenario Validation +```bash +# Build scenario +cargo build --bin {scenario-name} + +# Run scenario (may require AWS credentials) +cargo run --bin {scenario-name} + +# Check for compilation errors +cargo check --bin {scenario-name} +``` + +#### Test Validation +```bash +# Run all tests +cargo test + +# Run tests with output +cargo test -- --nocapture + +# Run specific test module +cargo test --lib {module_name} + +# Run integration tests +cargo test --test '*' +``` + +#### Code Quality Validation +```bash +# Format code +cargo fmt + +# Check formatting +cargo fmt --check + +# Run linter +cargo clippy -- -D warnings + +# Check for errors +cargo check +``` + +#### Documentation Validation +```bash +cd .tools/readmes +source .venv/bin/activate +python -m writeme --languages Rust:1 --services {service} +``` + +### Integration Validation +Full integration testing across all components: + +```bash +# 1. All tests pass +cargo test + +# 2. All examples compile +cargo build --bins + +# 3. Code quality passes +cargo fmt --check +cargo clippy -- -D warnings + +# 4. Examples execute successfully (with credentials) +cargo run --bin hello +cargo run --bin {scenario-name} + +# 5. Documentation generates successfully +cd .tools/readmes && source .venv/bin/activate && python -m writeme --languages Rust:1 --services {service} +``` + +## Component Selection Guide + +### When to Use Individual Components + +#### Hello Example Only +- Quick service introduction needed +- Testing basic service connectivity +- Creating minimal working example + +#### Wrapper Module Only +- Need mockable service operations for testing +- Building foundation for testable scenarios +- Focusing on error handling patterns + +#### Scenario Only +- Demonstrating complete workflows +- Educational/tutorial content +- Interactive user experiences + +#### Tests Only +- Improving test coverage +- Adding new test cases +- Fixing test infrastructure + +#### Metadata Only +- Documentation pipeline integration +- Updating snippet references +- Fixing metadata validation errors + +#### Documentation Only +- README updates needed +- Documentation refresh +- Link validation and updates + +### When to Use Full Workflow +- New service implementation +- Complete service overhaul +- Major structural changes +- Initial service setup + +## Error Recovery + +### Component Failure Handling +If any component fails, you can: + +1. **Fix and retry** the specific component +2. **Skip and continue** with other components +3. **Rollback changes** and restart from known good state + +### Common Recovery Scenarios + +#### Compilation Failures +```bash +# Check for errors +cargo check + +# Get detailed error information +cargo build --verbose + +# Fix errors and rebuild +cargo build +``` + +#### Test Failures +```bash +# Run tests with output to see failures +cargo test -- --nocapture + +# Run specific failing test +cargo test test_name -- --nocapture + +# Fix and re-run +cargo test +``` + +#### Metadata Validation Failures +```bash +# Check metadata syntax +python -c "import yaml; yaml.safe_load(open('.doc_gen/metadata/{service}_metadata.yaml'))" + +# Validate against specification +# Compare with scenarios/basics/{service}/SPECIFICATION.md +``` + +#### Documentation Generation Failures +```bash +# Check for missing dependencies +cd .tools/readmes && source .venv/bin/activate && pip list + +# Validate metadata first +python -m writeme --languages Rust:1 --services {service} --verbose +``` + +## Build System Integration + +### Cargo Workspace +Rust examples are typically organized in a Cargo workspace: + +```toml +# rustv1/Cargo.toml +[workspace] +members = [ + "examples/s3", + "examples/dynamodb", + "examples/{service}", +] +``` + +### Building All Examples +```bash +# From rustv1 directory +cargo build --workspace + +# Run all tests +cargo test --workspace + +# Format all code +cargo fmt --all + +# Lint all code +cargo clippy --workspace -- -D warnings +``` + +## Continuous Integration + +### CI Pipeline Steps +```bash +# 1. Check formatting +cargo fmt --all --check + +# 2. Run linter +cargo clippy --workspace -- -D warnings + +# 3. Build all examples +cargo build --workspace + +# 4. Run all tests +cargo test --workspace + +# 5. Validate metadata +cd .tools/readmes +source .venv/bin/activate +python -m writeme --languages Rust:1 --services {service} + +# 6. Check for uncommitted changes +git diff --exit-code +``` + +## Development Workflow + +### Typical Development Cycle +1. **Read specification** for requirements +2. **Consult knowledge bases** for patterns +3. **Create hello example** for basic connectivity +4. **Implement wrapper** if mocking needed +5. **Build scenario** following specification +6. **Write tests** for coverage +7. **Add metadata** for documentation +8. **Generate README** with writeme +9. **Validate everything** with quality gates +10. **Commit changes** after validation + +### Iterative Development +- Start with hello example to verify connectivity +- Add wrapper if testing requires mocking +- Implement scenario incrementally by phase +- Write tests as you go +- Update metadata and docs at the end + +## Best Practices +- ✅ **Always consult knowledge bases first** +- ✅ **Follow specification exactly** for scenarios +- ✅ **Test as you develop** don't wait until the end +- ✅ **Use cargo check frequently** for quick feedback +- ✅ **Run cargo clippy** to catch common issues +- ✅ **Format code regularly** with cargo fmt +- ✅ **Validate metadata early** to catch issues +- ✅ **Generate docs frequently** to verify completeness +- ✅ **Commit working increments** not just final code +- ✅ **Document as you go** don't defer documentation + +## Common Pitfalls to Avoid +- ❌ **Skipping knowledge base consultation** +- ❌ **Not following specification exactly** +- ❌ **Creating wrappers when not needed** +- ❌ **Forgetting to add snippet tags** +- ❌ **Not testing error paths** +- ❌ **Ignoring compiler warnings** +- ❌ **Deferring metadata until the end** +- ❌ **Not validating generated documentation** +- ❌ **Committing unformatted code** +- ❌ **Skipping integration validation** + +This modular approach allows for targeted updates, easier debugging, and more maintainable code generation processes. diff --git a/steering_docs/rust-tech/readme_writeme.md b/steering_docs/rust-tech/readme_writeme.md new file mode 100644 index 00000000000..866dd32ac5c --- /dev/null +++ b/steering_docs/rust-tech/readme_writeme.md @@ -0,0 +1,387 @@ +# Rust README/WRITEME and Documentation Generation + +## Purpose +Generate and update README files and documentation using the writeme tool to ensure consistency and completeness. + +## Requirements +- **Automated Generation**: Use writeme tool for README generation +- **Metadata Dependency**: Requires complete metadata files +- **Virtual Environment**: Run writeme in isolated environment +- **Validation**: Ensure all documentation is up-to-date + +## File Structure +``` +rustv1/examples/{service}/ +├── README.md # Generated service README +├── Cargo.toml # Package configuration +└── {service}_metadata.yaml # Metadata (in .doc_gen/metadata/) +``` + +## README Generation Process + +### Step 1: Setup Writeme Environment +```bash +cd .tools/readmes + +# Create virtual environment +python -m venv .venv + +# Activate environment (Linux/macOS) +source .venv/bin/activate + +# Activate environment (Windows) +.venv\Scripts\activate + +# Install dependencies +python -m pip install -r requirements_freeze.txt +``` + +### Step 2: Generate README +```bash +# Generate README for specific service +python -m writeme --languages Rust:1 --services {service} +``` + +### Step 3: Validate Generation +- ✅ **README.md created/updated** in service directory +- ✅ **No generation errors** in writeme output +- ✅ **All examples listed** in README +- ✅ **Proper formatting** and structure +- ✅ **Working links** to code files + +## README Content Structure + +### Generated README Sections +1. **Service Overview**: Description of AWS service +2. **Code Examples**: List of available examples +3. **Prerequisites**: Setup requirements +4. **Installation**: Dependency installation +5. **Usage**: How to run examples +6. **Tests**: Testing instructions +7. **Additional Resources**: Links to documentation + +### Example README Content +```markdown +# {AWS Service} code examples for the SDK for Rust + +## Overview + +This is a workspace where you can find the following AWS SDK for Rust +{AWS Service} examples. + +## ⚠ Important + +* Running this code might result in charges to your AWS account. +* Running the tests might result in charges to your AWS account. +* We recommend that you grant your code least privilege. + +## Code examples + +### Actions + +The following examples show you how to perform actions using the AWS SDK for Rust. + +* [Create a resource](src/{service}.rs#L123) (`CreateResource`) +* [Get a resource](src/{service}.rs#L456) (`GetResource`) + +### Scenarios + +The following examples show you how to implement common scenarios. + +* [Get started with resources](src/bin/{scenario-name}.rs) - Learn the basics by creating and managing resources. + +### Hello + +* [Hello {Service}](src/bin/hello.rs) - Get started with {AWS Service}. + +## Prerequisites + +- You must have an AWS account, and have your default credentials and AWS Region configured. +- Rust 1.70 or later +- Cargo (Rust package manager) + +## Install + +Install the prerequisites using Cargo: + +``` +cargo build +``` + +## Run the examples + +### Instructions + +All examples can be run individually. For example: + +``` +cargo run --bin hello +``` + +### Hello {Service} + +This example shows you how to get started using {AWS Service}. + +``` +cargo run --bin hello +``` + +### Get started with {Service} resources + +This interactive scenario runs at a command prompt and shows you how to use {AWS Service} to do the following: + +1. Create a resource +2. Use the resource +3. Clean up resources + +``` +cargo run --bin {scenario-name} +``` + +## Run the tests + +Unit and integration tests in this module use the built-in Rust testing framework. To run all tests: + +``` +cargo test +``` + +To run tests with output: + +``` +cargo test -- --nocapture +``` + +## Additional resources + +- [{AWS Service} User Guide](https://docs.aws.amazon.com/{service}/latest/ug/) +- [{AWS Service} API Reference](https://docs.aws.amazon.com/{service}/latest/APIReference/) +- [AWS SDK for Rust ({AWS Service})](https://docs.rs/aws-sdk-{service}/latest/aws_sdk_{service}/) +``` + +## Documentation Dependencies + +### Required Files for README Generation +- ✅ **Metadata file**: `.doc_gen/metadata/{service}_metadata.yaml` +- ✅ **Code files**: All referenced Rust files must exist +- ✅ **Snippet tags**: All snippet tags in metadata must exist in code +- ✅ **Cargo.toml**: Package configuration with dependencies + +### Metadata Integration +The writeme tool uses metadata to: +- Generate example lists and descriptions +- Create links to specific code sections +- Include proper service information +- Format documentation consistently + +## Troubleshooting README Generation + +### Common Issues +- **Missing metadata**: Ensure metadata file exists and is valid +- **Broken snippet tags**: Verify all snippet tags exist in code +- **File not found**: Check all file paths in metadata +- **Invalid YAML**: Validate metadata YAML syntax + +### Error Resolution +```bash +# Check for metadata errors +python -m writeme --languages Rust:1 --services {service} --verbose + +# Validate specific metadata file +python -c "import yaml; yaml.safe_load(open('.doc_gen/metadata/{service}_metadata.yaml'))" + +# Check for missing snippet tags +grep -r "snippet-start" rustv1/examples/{service}/ +``` + +## README Maintenance + +### When to Regenerate README +- ✅ **After adding new examples** +- ✅ **After updating metadata** +- ✅ **After changing code structure** +- ✅ **Before committing changes** +- ✅ **During regular maintenance** + +### README Quality Checklist +- ✅ **All examples listed** and properly linked +- ✅ **Prerequisites accurate** and complete +- ✅ **Installation instructions** work correctly +- ✅ **Usage examples** are clear and correct +- ✅ **Links functional** and point to right locations +- ✅ **Formatting consistent** with other services + +## Integration with CI/CD + +### Automated README Validation +```bash +# In CI/CD pipeline, validate README is up-to-date +cd .tools/readmes +source .venv/bin/activate +python -m writeme --languages Rust:1 --services {service} --check + +# Exit with error if README needs updates +if git diff --exit-code rustv1/examples/{service}/README.md; then + echo "README is up-to-date" +else + echo "README needs to be regenerated" + exit 1 +fi +``` + +## Cargo.toml Documentation + +### Package Metadata +Ensure Cargo.toml includes proper metadata: + +```toml +[package] +name = "{service}-examples" +version = "0.1.0" +edition = "2021" +authors = ["AWS SDK for Rust Team"] +description = "AWS SDK for Rust code examples for {Service}" +license = "Apache-2.0" +repository = "https://github.com/awsdocs/aws-doc-sdk-examples" + +[dependencies] +aws-config = { version = "1.0", features = ["behavior-version-latest"] } +aws-sdk-{service} = "1.0" +tokio = { version = "1.0", features = ["full"] } +tracing-subscriber = { version = "0.3", features = ["env-filter"] } + +[dev-dependencies] +mockall = "0.12" +``` + +## Documentation Comments + +### Module-Level Documentation +```rust +//! # {AWS Service} Examples +//! +//! This module contains examples demonstrating how to use the AWS SDK for Rust +//! with {AWS Service}. +//! +//! ## Examples +//! +//! - `hello.rs` - Basic connectivity example +//! - `{scenario-name}.rs` - Interactive scenario demonstrating service features +``` + +### Function Documentation +```rust +/// Creates a new resource in {AWS Service}. +/// +/// # Arguments +/// +/// * `client` - The {Service} client +/// * `name` - The name for the new resource +/// +/// # Returns +/// +/// Returns the resource ID on success, or an error if the operation fails. +/// +/// # Example +/// +/// ```no_run +/// let resource_id = create_resource(&client, "my-resource").await?; +/// ``` +pub async fn create_resource( + client: &Client, + name: &str, +) -> Result { + // Implementation +} +``` + +## README Customization + +### Service-Specific Sections +Add service-specific information to README: + +```markdown +## Service-Specific Notes + +### {Service} Quotas +Be aware of the following service quotas: +- Maximum resources per account: 100 +- Maximum operations per second: 10 + +### Best Practices +- Always clean up resources after use +- Use pagination for large result sets +- Implement proper error handling +``` + +### Example Output +Include example output in README: + +```markdown +## Example Output + +When you run the hello example, you should see output similar to: + +``` +Hello, Amazon {Service}! Let's list available resources: + +3 resource(s) retrieved. + resource-1 + resource-2 + resource-3 +``` +``` + +## Linking to Code + +### Direct File Links +Link to specific files: +```markdown +[Hello Example](src/bin/hello.rs) +[Scenario](src/bin/{scenario-name}.rs) +[Wrapper Module](src/{service}.rs) +``` + +### Line Number Links +Link to specific lines (GitHub): +```markdown +[Create Resource Function](src/{service}.rs#L123-L145) +``` + +## Multi-Language Documentation + +### Cross-Language References +When documenting cross-language examples: + +```markdown +## Related Examples + +This example is also available in other languages: +- [Python](../../python/example_code/{service}/) +- [Java](../../javav2/{service}/) +- [.NET](../../dotnetv4/{Service}/) +``` + +## Documentation Best Practices +- ✅ **Keep README concise** but complete +- ✅ **Include working examples** that users can copy +- ✅ **Link to official docs** for detailed information +- ✅ **Show example output** to set expectations +- ✅ **Document prerequisites** clearly +- ✅ **Include troubleshooting** for common issues +- ✅ **Update regularly** as code changes +- ✅ **Test all commands** in README +- ✅ **Use consistent formatting** across services +- ✅ **Validate links** before committing + +## Common Documentation Issues +- ❌ **Outdated examples** that don't match current code +- ❌ **Broken links** to moved or renamed files +- ❌ **Missing prerequisites** causing user confusion +- ❌ **Incorrect commands** that don't work +- ❌ **Inconsistent formatting** across services +- ❌ **Missing error handling** in examples +- ❌ **No example output** leaving users uncertain +- ❌ **Incomplete installation** instructions + +This ensures documentation stays synchronized with code changes and provides a great user experience. diff --git a/steering_docs/rust-tech/tests.md b/steering_docs/rust-tech/tests.md new file mode 100644 index 00000000000..5244a169a08 --- /dev/null +++ b/steering_docs/rust-tech/tests.md @@ -0,0 +1,529 @@ +# Rust Test Generation + +## MANDATORY: Knowledge Base Consultation (FIRST STEP) +**🚨 CRITICAL - Must be completed BEFORE any code generation** + +```bash +# Step 1: List available knowledge bases +ListKnowledgeBases() + +# Step 2: Query coding standards (REQUIRED) +QueryKnowledgeBases("coding-standards-KB", "Rust-code-example-standards") + +# Step 3: Query implementation patterns (REQUIRED) +QueryKnowledgeBases("Rust-premium-KB", "Rust implementation patterns testing") + +# Step 4: AWS service research (REQUIRED) +search_documentation("What is [AWS Service] and what are its key API operations?") +read_documentation("https://docs.aws.amazon.com/[service]/latest/[relevant-page]") +``` + +**FAILURE TO COMPLETE KNOWLEDGE BASE CONSULTATION WILL RESULT IN INCORRECT CODE STRUCTURE** + +## Purpose +Generate test suites using Rust's built-in testing framework and mockall for mocking. Tests validate scenario workflows and individual operations. + +## Requirements +- **Unit Tests**: Use `#[cfg(test)] mod test` or separate `test.rs` files +- **Integration Tests**: Place in `tests/` folder within scenario modules +- **Mocking**: Use mockall crate for mocking AWS service calls +- **Async Testing**: Use `#[tokio::test]` for async test functions +- **Coverage**: Use cargo-llvm-cov for code coverage + +## File Structure +``` +rustv1/examples/{service}/ +├── src/ +│ ├── {service}.rs # Service wrapper with mockall +│ ├── {scenario_name}/ +│ │ ├── mod.rs +│ │ ├── scenario.rs # Scenario logic +│ │ └── tests/ +│ │ └── mod.rs # Integration tests +│ └── bin/ +│ └── {action}.rs +└── Cargo.toml +``` + +## Test Dependencies in Cargo.toml +```toml +[dev-dependencies] +mockall = "0.12" +tokio-test = "0.4" + +[dependencies] +tokio = { version = "1.0", features = ["full", "test-util"] } +``` + +## Unit Test Pattern (In Source File) +```rust +// In src/{scenario_name}/scenario.rs + +#[cfg(test)] +mod tests { + use super::*; + use crate::{service}::Mock{Service}Impl; + use mockall::predicate::*; + + #[tokio::test] + async fn test_setup_phase_success() { + let mut mock_{service} = Mock{Service}Impl::default(); + + // Setup mock expectations + mock_{service} + .expect_list_resources() + .return_once(|| Ok(vec![])); + + mock_{service} + .expect_create_resource() + .with(eq("test-resource")) + .return_once(|_| Ok("resource-123".to_string())); + + // Test the function + let result = setup_phase(&mock_{service}, "test-resource").await; + + assert!(result.is_ok()); + assert_eq!(result.unwrap(), "resource-123"); + } + + #[tokio::test] + async fn test_setup_phase_error() { + let mut mock_{service} = Mock{Service}Impl::default(); + + mock_{service} + .expect_list_resources() + .return_once(|| Err(ScenarioError::with("Failed to list resources"))); + + let result = setup_phase(&mock_{service}, "test-resource").await; + + assert!(result.is_err()); + } +} +``` + +## Integration Test Pattern (In tests/ folder) +```rust +// In src/{scenario_name}/tests/mod.rs + +use crate::{service}::Mock{Service}Impl; +use crate::{scenario_name}::{run_scenario, ScenarioError}; +use mockall::predicate::*; + +#[tokio::test] +async fn test_scenario_complete_workflow() { + let mut mock_{service} = Mock{Service}Impl::default(); + + // Setup expectations for entire scenario + mock_{service} + .expect_list_resources() + .return_once(|| Ok(vec![])); + + mock_{service} + .expect_create_resource() + .with(eq("test-resource")) + .return_once(|_| Ok("resource-123".to_string())); + + mock_{service} + .expect_generate_sample_data() + .with(eq("resource-123")) + .return_once(|_| Ok(())); + + mock_{service} + .expect_list_data() + .with(eq("resource-123")) + .return_once(|_| Ok(vec!["data1".to_string(), "data2".to_string()])); + + mock_{service} + .expect_delete_resource() + .with(eq("resource-123")) + .return_once(|_| Ok(())); + + // Run the scenario + let result = run_scenario(mock_{service}).await; + + assert!(result.is_ok()); +} + +#[tokio::test] +async fn test_scenario_handles_errors() { + let mut mock_{service} = Mock{Service}Impl::default(); + + mock_{service} + .expect_list_resources() + .return_once(|| Err(ScenarioError::with("Service unavailable"))); + + let result = run_scenario(mock_{service}).await; + + assert!(result.is_err()); +} +``` + +## Mock Setup Patterns + +### Basic Mock Expectation +```rust +mock_{service} + .expect_operation() + .return_once(|| Ok(expected_result)); +``` + +### Mock with Parameter Matching +```rust +mock_{service} + .expect_operation() + .with(eq("expected-param")) + .return_once(|_| Ok(expected_result)); +``` + +### Mock with Multiple Parameters +```rust +mock_{service} + .expect_operation() + .with(eq("param1"), eq("param2")) + .return_once(|_, _| Ok(expected_result)); +``` + +### Mock Called Multiple Times +```rust +mock_{service} + .expect_operation() + .times(3) + .returning(|| Ok(expected_result)); +``` + +### Mock with Different Return Values +```rust +mock_{service} + .expect_operation() + .times(2) + .returning(|| Ok(expected_result)) + .expect_operation() + .times(1) + .returning(|| Err(ScenarioError::with("Error on third call"))); +``` + +## Service Wrapper with Mockall +```rust +// In src/{service}.rs + +use aws_sdk_{service}::Client as {Service}Client; + +#[cfg(test)] +use mockall::automock; + +#[cfg(test)] +pub use Mock{Service}Impl as {Service}; +#[cfg(not(test))] +pub use {Service}Impl as {Service}; + +pub struct {Service}Impl { + pub inner: {Service}Client, +} + +#[cfg_attr(test, automock)] +impl {Service}Impl { + pub fn new(inner: {Service}Client) -> Self { + {Service}Impl { inner } + } + + pub async fn list_resources(&self) -> Result, ScenarioError> { + let response = self.inner + .list_resources() + .send() + .await + .map_err(|e| ScenarioError::new("Failed to list resources", &e))?; + + Ok(response + .resources() + .iter() + .filter_map(|r| r.name()) + .map(String::from) + .collect()) + } + + pub async fn create_resource(&self, name: &str) -> Result { + let response = self.inner + .create_resource() + .name(name) + .send() + .await + .map_err(|e| ScenarioError::new("Failed to create resource", &e))?; + + Ok(response.resource_id().unwrap_or("").to_string()) + } +} +``` + +## Testing Async Operations + +### Basic Async Test +```rust +#[tokio::test] +async fn test_async_operation() { + let result = async_function().await; + assert!(result.is_ok()); +} +``` + +### Async Test with Timeout +```rust +#[tokio::test(flavor = "multi_thread")] +async fn test_with_timeout() { + let result = tokio::time::timeout( + std::time::Duration::from_secs(5), + async_function() + ).await; + + assert!(result.is_ok()); +} +``` + +### Testing Concurrent Operations +```rust +#[tokio::test] +async fn test_concurrent_operations() { + let handle1 = tokio::spawn(async { operation1().await }); + let handle2 = tokio::spawn(async { operation2().await }); + + let (result1, result2) = tokio::join!(handle1, handle2); + + assert!(result1.is_ok()); + assert!(result2.is_ok()); +} +``` + +## Error Testing Patterns + +### Testing Expected Errors +```rust +#[tokio::test] +async fn test_error_handling() { + let mut mock_{service} = Mock{Service}Impl::default(); + + mock_{service} + .expect_operation() + .return_once(|| Err(ScenarioError::with("Expected error"))); + + let result = function_under_test(&mock_{service}).await; + + assert!(result.is_err()); + assert_eq!( + result.unwrap_err().to_string(), + "Expected error" + ); +} +``` + +### Testing Error Recovery +```rust +#[tokio::test] +async fn test_error_recovery() { + let mut mock_{service} = Mock{Service}Impl::default(); + + // First call fails + mock_{service} + .expect_operation() + .times(1) + .return_once(|| Err(ScenarioError::with("Temporary error"))); + + // Second call succeeds + mock_{service} + .expect_operation() + .times(1) + .return_once(|| Ok(expected_result)); + + let result = retry_function(&mock_{service}).await; + + assert!(result.is_ok()); +} +``` + +## Test Organization + +### Unit Tests in Source Files +```rust +// At the bottom of src/{scenario_name}/scenario.rs + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn test_function_one() { + // Test implementation + } + + #[tokio::test] + async fn test_function_two() { + // Test implementation + } +} +``` + +### Integration Tests in Separate Module +```rust +// In src/{scenario_name}/tests/mod.rs + +use crate::{service}::Mock{Service}Impl; +use crate::{scenario_name}::*; + +#[tokio::test] +async fn integration_test_one() { + // Test implementation +} + +#[tokio::test] +async fn integration_test_two() { + // Test implementation +} +``` + +## Code Coverage + +### Running Tests with Coverage +```bash +# Install cargo-llvm-cov +cargo install cargo-llvm-cov + +# Run tests with coverage +cargo llvm-cov --all-features --workspace + +# Generate HTML report +cargo llvm-cov --all-features --workspace --html + +# Generate lcov report for CI +cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info +``` + +### Coverage Configuration in Cargo.toml +```toml +[package.metadata.coverage] +exclude = [ + "src/bin/*", # Exclude binary files from coverage +] +``` + +## Test Execution Commands + +### Run All Tests +```bash +cargo test +``` + +### Run Tests for Specific Module +```bash +cargo test --lib {module_name} +``` + +### Run Integration Tests Only +```bash +cargo test --test '*' +``` + +### Run Tests with Output +```bash +cargo test -- --nocapture +``` + +### Run Tests in Parallel +```bash +cargo test -- --test-threads=4 +``` + +### Run Specific Test +```bash +cargo test test_function_name +``` + +## Test Requirements Checklist +- ✅ **Unit tests** for individual functions in `#[cfg(test)] mod tests` +- ✅ **Integration tests** for complete scenarios in `tests/` folder +- ✅ **Mock setup** using mockall for AWS service calls +- ✅ **Async testing** with `#[tokio::test]` attribute +- ✅ **Error testing** for expected error conditions +- ✅ **Coverage** using cargo-llvm-cov +- ✅ **Documentation** for test purpose and setup + +## Common Test Patterns + +### Testing Pagination +```rust +#[tokio::test] +async fn test_pagination() { + let mut mock_{service} = Mock{Service}Impl::default(); + + mock_{service} + .expect_list_items_paginated() + .return_once(|| Ok(vec![ + "item1".to_string(), + "item2".to_string(), + "item3".to_string(), + ])); + + let result = list_all_items(&mock_{service}).await; + + assert!(result.is_ok()); + assert_eq!(result.unwrap().len(), 3); +} +``` + +### Testing Waiters +```rust +#[tokio::test] +async fn test_waiter_success() { + let mut mock_{service} = Mock{Service}Impl::default(); + + mock_{service} + .expect_wait_for_ready() + .with(eq("resource-123")) + .return_once(|_| Ok(())); + + let result = wait_for_resource(&mock_{service}, "resource-123").await; + + assert!(result.is_ok()); +} +``` + +### Testing Cleanup +```rust +#[tokio::test] +async fn test_cleanup_always_runs() { + let mut mock_{service} = Mock{Service}Impl::default(); + + // Setup fails + mock_{service} + .expect_create_resource() + .return_once(|_| Err(ScenarioError::with("Creation failed"))); + + // Cleanup should still be called + mock_{service} + .expect_delete_resource() + .times(0); // Not called because resource wasn't created + + let result = run_scenario(mock_{service}).await; + + assert!(result.is_err()); +} +``` + +## Best Practices +- ✅ **Test one thing per test**: Keep tests focused and simple +- ✅ **Use descriptive test names**: Clearly indicate what is being tested +- ✅ **Mock external dependencies**: Use mockall for AWS service calls +- ✅ **Test error paths**: Don't just test happy paths +- ✅ **Use async tests**: Always use `#[tokio::test]` for async functions +- ✅ **Verify mock expectations**: Ensure mocks are called as expected +- ✅ **Clean up resources**: Even in tests, practice good resource management +- ✅ **Document test purpose**: Add comments explaining complex test setups +- ✅ **Run tests regularly**: Integrate into development workflow +- ✅ **Measure coverage**: Use cargo-llvm-cov to track test coverage + +## Common Testing Mistakes to Avoid +- ❌ **Not using #[tokio::test]** for async tests +- ❌ **Forgetting to set mock expectations** before calling functions +- ❌ **Not testing error conditions** and edge cases +- ❌ **Creating overly complex test setups** that are hard to maintain +- ❌ **Not cleaning up test resources** properly +- ❌ **Testing implementation details** instead of behavior +- ❌ **Ignoring test failures** or flaky tests +- ❌ **Not running tests before committing** code changes diff --git a/steering_docs/rust-tech/wrapper.md b/steering_docs/rust-tech/wrapper.md new file mode 100644 index 00000000000..7b8b20eb131 --- /dev/null +++ b/steering_docs/rust-tech/wrapper.md @@ -0,0 +1,461 @@ +# Rust Service Wrapper Generation + +## MANDATORY: Knowledge Base Consultation (FIRST STEP) +**🚨 CRITICAL - Must be completed BEFORE any code generation** + +```bash +# Step 1: List available knowledge bases +ListKnowledgeBases() + +# Step 2: Query coding standards (REQUIRED) +QueryKnowledgeBases("coding-standards-KB", "Rust-code-example-standards") + +# Step 3: Query implementation patterns (REQUIRED) +QueryKnowledgeBases("Rust-premium-KB", "Rust implementation patterns") + +# Step 4: AWS service research (REQUIRED) +search_documentation("What is [AWS Service] and what are its key API operations?") +read_documentation("https://docs.aws.amazon.com/[service]/latest/[relevant-page]") +``` + +**FAILURE TO COMPLETE KNOWLEDGE BASE CONSULTATION WILL RESULT IN INCORRECT CODE STRUCTURE** + +## Purpose +Generate service wrapper modules that encapsulate AWS service functionality for integration test mocking when necessary. Wrappers are optional and only needed when mocking is required for testing. + +## Requirements +- **Optional**: Only create wrappers when integration test mocking is needed +- **Mockable**: Use mockall crate for test mocking support +- **Async Operations**: All AWS operations must be async +- **Error Handling**: Return Result types with appropriate error types +- **Trait-Based**: Use traits for mockability (only when needed) + +## When to Create a Wrapper + +### Create Wrapper When: +- ✅ **Integration tests need mocking** for complex scenarios +- ✅ **Multiple operations need coordination** in a testable way +- ✅ **Scenario has complex state management** requiring testing + +### Skip Wrapper When: +- ❌ **Simple one-off scripts** or single-action examples +- ❌ **Hello examples** (use client directly) +- ❌ **No testing requirements** for the example + +## File Structure +``` +rustv1/examples/{service}/ +├── src/ +│ ├── lib.rs # Module exports +│ ├── {service}.rs # Service wrapper (if needed) +│ └── bin/ +│ └── {action}.rs # Action examples +``` + +## Wrapper Module Pattern (src/{service}.rs) +```rust +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use aws_sdk_{service}::{ + Client as {Service}Client, + operation::{ + operation_one::OperationOneOutput, + operation_two::OperationTwoOutput, + }, + error::SdkError, +}; + +#[cfg(test)] +use mockall::automock; + +// Use the mock in tests, real implementation in production +#[cfg(test)] +pub use Mock{Service}Impl as {Service}; +#[cfg(not(test))] +pub use {Service}Impl as {Service}; + +/// Wrapper for {Service} operations to enable mocking in tests +pub struct {Service}Impl { + pub inner: {Service}Client, +} + +#[cfg_attr(test, automock)] +impl {Service}Impl { + /// Create a new {Service} wrapper + pub fn new(inner: {Service}Client) -> Self { + {Service}Impl { inner } + } + + /// Perform operation one + pub async fn operation_one( + &self, + param: &str, + ) -> Result> { + self.inner + .operation_one() + .parameter(param) + .send() + .await + } + + /// Perform operation two with pagination + pub async fn operation_two_paginated( + &self, + param: &str, + ) -> Result, SdkError> { + let mut items = Vec::new(); + let mut paginator = self.inner + .operation_two() + .parameter(param) + .into_paginator() + .send(); + + while let Some(page) = paginator.next().await { + let page = page?; + items.extend(page.items().to_vec()); + } + + Ok(items) + } +} +``` + +## Wrapper Without Mocking (Simple Cases) +For simpler cases where mocking isn't needed, use bare functions: + +```rust +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +use aws_sdk_{service}::Client; + +/// List all resources +pub async fn list_resources( + client: &Client, +) -> Result, aws_sdk_{service}::Error> { + let mut resources = Vec::new(); + let mut paginator = client + .list_resources() + .into_paginator() + .send(); + + while let Some(page) = paginator.next().await { + let page = page?; + resources.extend( + page.resources() + .iter() + .filter_map(|r| r.name()) + .map(String::from) + ); + } + + Ok(resources) +} + +/// Create a resource +pub async fn create_resource( + client: &Client, + name: &str, +) -> Result { + let response = client + .create_resource() + .name(name) + .send() + .await?; + + Ok(response.resource_id().unwrap_or("").to_string()) +} +``` + +## Pagination Patterns + +### Using Paginator with while let +```rust +pub async fn list_all_items( + &self, +) -> Result, SdkError> { + let mut items = Vec::new(); + let mut paginator = self.inner + .list_items() + .into_paginator() + .send(); + + while let Some(page) = paginator.next().await { + let page = page?; + items.extend(page.items().to_vec()); + } + + Ok(items) +} +``` + +### Using Paginator with collect +```rust +pub async fn list_all_items( + &self, +) -> Result, SdkError> { + let items: Result, _> = self.inner + .list_items() + .into_paginator() + .items() + .send() + .collect() + .await; + + items +} +``` + +### Paginator with Limit +```rust +pub async fn list_items_limited( + &self, + page_size: i32, +) -> Result, SdkError> { + let items: Result, _> = self.inner + .list_items() + .limit(page_size) + .into_paginator() + .items() + .send() + .collect() + .await; + + items +} +``` + +## Error Handling Patterns + +### Using Client::Error for Simple Cases +```rust +use aws_sdk_{service}::{Client, Error}; + +pub async fn simple_operation(client: &Client) -> Result<(), Error> { + let response = client.operation().send().await?; + println!("Operation successful"); + Ok(()) +} +``` + +### Using SdkError for Detailed Error Handling +```rust +use aws_sdk_{service}::error::SdkError; +use aws_sdk_{service}::operation::operation_name::OperationNameError; + +pub async fn detailed_operation( + &self, +) -> Result> { + match self.inner.operation().send().await { + Ok(response) => Ok(response), + Err(e) => { + if let Some(service_err) = e.as_service_error() { + // Handle specific service errors + eprintln!("Service error: {:?}", service_err); + } + Err(e) + } + } +} +``` + +### Using into_service_error for Error Matching +```rust +use aws_sdk_{service}::operation::operation_name::OperationNameError; + +pub async fn handle_specific_errors( + &self, +) -> Result> { + match self.inner.operation().send().await { + Ok(response) => Ok(response), + Err(e) => { + match e.into_service_error() { + OperationNameError::ResourceNotFoundException(_) => { + eprintln!("Resource not found"); + } + OperationNameError::ValidationException(_) => { + eprintln!("Invalid parameters"); + } + e => { + eprintln!("Other error: {:?}", e); + } + } + Err(Box::new(e)) + } + } +} +``` + +## Waiter Patterns + +### Using Built-in Waiters +```rust +use std::time::Duration; + +pub async fn wait_for_resource_ready( + &self, + resource_id: &str, +) -> Result<(), Box> { + let wait_result = self.inner + .wait_until_resource_ready() + .resource_id(resource_id) + .wait(Duration::from_secs(60)) + .await; + + match wait_result { + Ok(_) => { + println!("Resource {} is ready", resource_id); + Ok(()) + } + Err(err) => { + eprintln!("Error waiting for resource: {:?}", err); + Err(Box::new(err)) + } + } +} +``` + +### Custom Polling When No Waiter Available +```rust +use tokio::time::{sleep, Duration}; + +pub async fn poll_until_ready( + &self, + resource_id: &str, + max_attempts: u32, +) -> Result<(), Box> { + for attempt in 1..=max_attempts { + let status = self.get_resource_status(resource_id).await?; + + if status == "READY" { + println!("Resource ready after {} attempts", attempt); + return Ok(()); + } + + println!("Attempt {}/{}: Status is {}, waiting...", attempt, max_attempts, status); + sleep(Duration::from_secs(5)).await; + } + + Err("Resource did not become ready in time".into()) +} +``` + +## Module Organization in lib.rs +```rust +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +//! AWS SDK for Rust code examples for {Service} + +// Export wrapper module if it exists +pub mod {service}; + +// Export scenario modules +pub mod scenario_name; + +// Re-export commonly used types +pub use scenario_name::{run_scenario, ScenarioError}; +``` + +## When to Use Traits vs Bare Functions + +### Use Traits When: +- ✅ **Mocking is required** for integration tests +- ✅ **Multiple implementations** are needed +- ✅ **Complex state management** requires abstraction + +### Use Bare Functions When: +- ✅ **Simple operations** without state +- ✅ **No mocking needed** for tests +- ✅ **One-off scripts** or examples +- ✅ **Minimal complexity** is desired + +## Testing with Mocks + +### Mock Setup in Tests +```rust +#[cfg(test)] +mod tests { + use super::*; + use mockall::predicate::*; + + #[tokio::test] + async fn test_operation_success() { + let mut mock_{service} = Mock{Service}Impl::default(); + + mock_{service} + .expect_operation_one() + .with(eq("test-param")) + .return_once(|_| { + Ok(OperationOneOutput::builder().build()) + }); + + let result = mock_{service}.operation_one("test-param").await; + assert!(result.is_ok()); + } + + #[tokio::test] + async fn test_operation_error() { + let mut mock_{service} = Mock{Service}Impl::default(); + + mock_{service} + .expect_operation_one() + .with(eq("invalid")) + .return_once(|_| { + Err(SdkError::service_error( + OperationOneError::ValidationException( + ValidationException::builder().build() + ), + Response::new(StatusCode::try_from(400).unwrap(), SdkBody::empty()) + )) + }); + + let result = mock_{service}.operation_one("invalid").await; + assert!(result.is_err()); + } +} +``` + +## Wrapper Requirements +- ✅ **Only create when needed** for mocking or complex coordination +- ✅ **Use mockall** for test mocking when wrapper is needed +- ✅ **Include comprehensive documentation** for all public functions +- ✅ **Use async/await** pattern for all AWS operations +- ✅ **Return appropriate Result types** with proper error types +- ✅ **Use pagination** for list operations when available +- ✅ **Implement waiters** when available or custom polling when needed +- ✅ **Keep it simple** - prefer bare functions over traits when possible + +## Function Naming Conventions +- Use snake_case for all function names +- Match AWS API operation names in snake_case (e.g., `list_buckets` for `ListBuckets`) +- Use descriptive parameter names +- Return appropriate Result types + +## Documentation Requirements +- Include module-level documentation explaining wrapper purpose +- Document all public functions with `///` doc comments +- Document parameters and return values +- Include usage examples in documentation where helpful +- Document error conditions and handling + +## Cargo.toml Dependencies +```toml +[dependencies] +aws-config = { version = "1.0", features = ["behavior-version-latest"] } +aws-sdk-{service} = "1.0" +tokio = { version = "1.0", features = ["full"] } + +[dev-dependencies] +mockall = "0.12" +``` + +## Best Practices +- ✅ **Prefer simplicity**: Use bare functions unless mocking is needed +- ✅ **Use pagination**: Always use paginators for list operations +- ✅ **Handle errors appropriately**: Choose error type based on use case +- ✅ **Use waiters**: Prefer built-in waiters over custom polling +- ✅ **Document thoroughly**: Explain purpose and usage +- ✅ **Keep functions focused**: One operation per function +- ✅ **Avoid unnecessary abstraction**: Don't create traits unless needed From c389506b8c95f2be98c45ae68bc57f60828bb6f6 Mon Sep 17 00:00:00 2001 From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com> Date: Mon, 8 Dec 2025 14:41:07 -0600 Subject: [PATCH 2/8] Update basics.md --- steering_docs/rust-tech/basics.md | 277 +++++++++++++++++++++++++++++- 1 file changed, 276 insertions(+), 1 deletion(-) diff --git a/steering_docs/rust-tech/basics.md b/steering_docs/rust-tech/basics.md index 0d1e5582b3b..3ba6f4b3c5d 100644 --- a/steering_docs/rust-tech/basics.md +++ b/steering_docs/rust-tech/basics.md @@ -87,15 +87,37 @@ use {service}_code_examples::{run_scenario, ScenarioError}; async fn main() -> Result<(), ScenarioError> { tracing_subscriber::fmt::init(); + // Initialize client once in main let sdk_config = aws_config::defaults(BehaviorVersion::latest()) .load() .await; let client = aws_sdk_{service}::Client::new(&sdk_config); + // Pass client to scenario (clone when necessary) run_scenario(client).await } ``` +**Client Initialization Pattern:** +- ✅ **Initialize once**: Create the client once in main using `BehaviorVersion::latest()` +- ✅ **Pass by reference**: Pass `&Client` to functions when possible +- ✅ **Clone when needed**: Clone the client when passing to async tasks or storing in structs +- ✅ **Efficient cloning**: Client cloning is cheap (Arc internally), so don't worry about performance + +```rust +// Passing client by reference +async fn operation_one(client: &Client) -> Result<(), Error> { + client.list_resources().send().await?; + Ok(()) +} + +// Cloning client for async tasks +let client_clone = client.clone(); +tokio::spawn(async move { + client_clone.list_resources().send().await +}); +``` + ## Scenario Implementation Pattern (src/{scenario_name}/scenario.rs) ```rust // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. @@ -248,7 +270,13 @@ async fn examination_phase(client: &Client, resource_id: &str) -> Result<(), Sce } /// Cleanup phase: Implement cleanup options from specification -async fn cleanup_phase(client: &Client, resource_id: &str) -> Result<(), ScenarioError> { +async fn cleanup_phase(client: &Client, resource_id: &str, skip_cleanup: bool) -> Result<(), ScenarioError> { + if skip_cleanup { + println!("Skipping cleanup (--no-cleanup flag set)"); + println!("Resource {} will continue running.", resource_id); + return Ok(()); + } + println!("Cleanup options:"); println!("Note: Deleting the resource will stop all monitoring/processing."); @@ -348,6 +376,8 @@ fn filter_data_by_criteria(items: &[String]) { - Handle cleanup errors gracefully - Provide alternative management options as specified - Confirm completion per specification +- **Support --no-cleanup flag**: Scenarios may add a `--no-cleanup` or `--cleanup=false` flag to skip cleanup +- **Start with cleanup**: When implementing a scenario, try to start with the cleanup logic first ## User Interaction Patterns @@ -465,6 +495,174 @@ aws-sdk-{service} = "1.0" tokio = { version = "1.0", features = ["full"] } tracing-subscriber = { version = "0.3", features = ["env-filter"] } inquire = "0.7" +clap = { version = "4.0", features = ["derive"] } # For command line arguments +``` + +## Configuration and Command Line Arguments + +### Using clap for Command Line Arguments +For command line examples, prefer using clap with derive macros: + +```rust +use clap::Parser; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// Name of the resource to create + #[arg(short, long)] + name: String, + + /// AWS region to use + #[arg(short, long, default_value = "us-east-1")] + region: String, + + /// Enable verbose output + #[arg(short, long)] + verbose: bool, +} + +#[tokio::main] +async fn main() -> Result<(), ScenarioError> { + let args = Args::parse(); + + println!("Creating resource: {}", args.name); + // Use args.name, args.region, etc. +} +``` + +### Configuration Files +For server and lambda examples, prefer loading configuration from environment or config files: + +```rust +use std::env; + +let region = env::var("AWS_REGION").unwrap_or_else(|_| "us-east-1".to_string()); +let endpoint = env::var("SERVICE_ENDPOINT").ok(); +``` + +### S3 Bucket Naming +When creating S3 buckets, follow these guidelines: + +```rust +use std::env; +use uuid::Uuid; + +// When creating a new bucket +fn generate_bucket_name() -> Result { + let prefix = env::var("S3_BUCKET_NAME_PREFIX") + .map_err(|_| ScenarioError::with( + "S3_BUCKET_NAME_PREFIX environment variable not set. \ + Please set it to create buckets." + ))?; + + let unique_id = Uuid::new_v4(); + Ok(format!("{}-{}", prefix, unique_id)) +} + +// When referencing an existing bucket +fn get_existing_bucket() -> Result { + // Get from user input, config file, or command line args + // Do NOT use S3_BUCKET_NAME_PREFIX for existing buckets + let bucket_name = inquire::Text::new("Enter existing bucket name:") + .prompt() + .map_err(|e| ScenarioError::with(format!("Failed to get bucket name: {}", e)))?; + + Ok(bucket_name) +} +``` + +**Requirements:** +- ✅ **New buckets**: Require `S3_BUCKET_NAME_PREFIX` environment variable +- ✅ **Unique suffix**: Append UUID or timestamp to prefix +- ✅ **Exit if missing**: Don't create buckets without prefix +- ✅ **Existing buckets**: Don't use `S3_BUCKET_NAME_PREFIX` for existing buckets +- ✅ **Integration tests**: Same rules apply - tests should fail without prefix + +## Code Style and Readability + +### Loop vs Iterator +When to prefer Loop vs Iterator: +- **Use Iterator** if there is a clear transformation from `T` to `U` +- **Do not nest control flow** inside an iterator + - Extract the logic to a dedicated function + - Prefer a for loop if the function would be difficult to extract + - Prefer an extracted function if the logic is nuanced and should be tested + +```rust +// ✅ GOOD - Clear transformation +let names: Vec = resources + .iter() + .map(|r| r.name().to_string()) + .collect(); + +// ✅ GOOD - Simple for loop for complex logic +for resource in resources { + if resource.status() == "ACTIVE" { + process_resource(&resource).await?; + } +} + +// ❌ BAD - Nested control flow in iterator +let results: Vec<_> = resources + .iter() + .map(|r| { + if r.status() == "ACTIVE" { + Some(process_resource(r)) + } else { + None + } + }) + .collect(); +``` + +### Function Nesting Depth +How deep to go in nesting vs when to extract a new function: +- **Two levels deep** is fine +- **Three levels deep** is pushing it +- **Four levels deep** is probably too much +- **Extract a function** if the logic is nuanced and should be tested + +```rust +// ✅ GOOD - Extracted function for complex logic +async fn process_active_resources(resources: &[Resource]) -> Result<(), ScenarioError> { + for resource in resources { + if resource.status() == "ACTIVE" { + validate_and_process(resource).await?; + } + } + Ok(()) +} + +async fn validate_and_process(resource: &Resource) -> Result<(), ScenarioError> { + // Complex validation and processing logic + Ok(()) +} +``` + +### Trait vs Bare Functions +When to use Trait vs bare functions: +- **Examples rarely need traits** - they add mental overhead +- **Prefer bare functions** or a struct to manage the Epic communication saga +- **Use traits only** when mocking is required for testing + +```rust +// ✅ GOOD - Bare functions for simple cases +pub async fn create_resource(client: &Client, name: &str) -> Result { + // Implementation +} + +// ✅ GOOD - Struct for managing scenario state +pub struct ScenarioState { + client: Client, + resource_id: Option, +} + +impl ScenarioState { + pub async fn setup(&mut self) -> Result<(), ScenarioError> { + // Setup logic + } +} ``` ## Scenario Requirements @@ -478,6 +676,8 @@ inquire = "0.7" - ✅ **ALWAYS** provide educational context and explanations from specification - ✅ **ALWAYS** handle edge cases (no resources found, etc.) as specified - ✅ **ALWAYS** use custom ScenarioError type for error handling +- ✅ **ALWAYS** prefer iterators for clear transformations, loops for complex logic +- ✅ **ALWAYS** extract functions when nesting exceeds 2-3 levels ## Implementation Workflow @@ -489,6 +689,81 @@ inquire = "0.7" 5. **Add Error Handling**: Implement error handling per the "Errors" section 6. **Test Against Specification**: Verify implementation matches specification requirements +## Runtime Resources and File Handling + +### Compile-Time Data +Use `include_bytes!` or `include_str!` for compile-time data: + +```rust +// Include text file at compile time +const CONFIG_TEMPLATE: &str = include_str!("../resources/config_template.json"); + +// Include binary file at compile time +const SAMPLE_DATA: &[u8] = include_bytes!("../resources/sample.dat"); + +fn use_embedded_data() { + println!("Config template: {}", CONFIG_TEMPLATE); + println!("Sample data size: {} bytes", SAMPLE_DATA.len()); +} +``` + +### Runtime File Operations +For runtime file operations: + +```rust +use std::fs; +use aws_sdk_s3::primitives::ByteStream; + +// Read entire file as string +let content = fs::read_to_string("data.txt")?; + +// Stream file for upload to S3 +let body = ByteStream::from_path("large_file.dat").await?; +client.put_object() + .bucket("my-bucket") + .key("large_file.dat") + .body(body) + .send() + .await?; +``` + +### Handling PII and Sensitive Data +When showing examples that handle PII, use the [secrets](https://crates.io/crates/secrets) crate: + +```rust +use secrets::SecretString; + +// Store sensitive data securely +let password = SecretString::new("sensitive_password".to_string()); + +// Use the secret (automatically zeroed on drop) +let result = authenticate_user(&password).await?; + +// Password is automatically zeroed when dropped +``` + +### HTTP Servers +For HTTP server examples, use the actix-web crate: + +```rust +use actix_web::{web, App, HttpResponse, HttpServer}; + +async fn health_check() -> HttpResponse { + HttpResponse::Ok().body("OK") +} + +#[tokio::main] +async fn main() -> std::io::Result<()> { + HttpServer::new(|| { + App::new() + .route("/health", web::get().to(health_check)) + }) + .bind(("127.0.0.1", 8080))? + .run() + .await +} +``` + ## Educational Elements - **Use specification descriptions**: Explain operations using specification language - Show before/after states as outlined in specification From 647527f8610967fcfb4ce08fac71c3a62797c0d2 Mon Sep 17 00:00:00 2001 From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com> Date: Mon, 8 Dec 2025 15:25:11 -0600 Subject: [PATCH 3/8] Update code from standards. --- steering_docs/rust-tech/basics.md | 27 +++-- steering_docs/rust-tech/hello.md | 37 ++++--- steering_docs/rust-tech/tests.md | 77 +++++--------- steering_docs/rust-tech/wrapper.md | 164 ++++++++++++++--------------- 4 files changed, 149 insertions(+), 156 deletions(-) diff --git a/steering_docs/rust-tech/basics.md b/steering_docs/rust-tech/basics.md index 3ba6f4b3c5d..49cb0b0b0ef 100644 --- a/steering_docs/rust-tech/basics.md +++ b/steering_docs/rust-tech/basics.md @@ -124,13 +124,14 @@ tokio::spawn(async move { // SPDX-License-Identifier: Apache-2.0 use aws_sdk_{service}::Client; +use aws_sdk_{service}::error::ProvideErrorMetadata; use std::fmt::Display; /// Custom error type for scenario operations #[derive(Debug, PartialEq, Eq)] pub struct ScenarioError { message: String, - context: Option, + context: Option, } impl ScenarioError { @@ -141,10 +142,10 @@ impl ScenarioError { } } - pub fn new(message: impl Into, err: &dyn std::error::Error) -> Self { + pub fn new(message: impl Into, err: &dyn ProvideErrorMetadata) -> Self { ScenarioError { message: message.into(), - context: Some(err.to_string()), + context: Some(MetadataError::from(err)), } } } @@ -433,10 +434,13 @@ for item in items { ### Custom ScenarioError Type ```rust +use aws_sdk_{service}::error::ProvideErrorMetadata; +use std::fmt::Display; + #[derive(Debug, PartialEq, Eq)] pub struct ScenarioError { message: String, - context: Option, + context: Option, } impl ScenarioError { @@ -447,10 +451,21 @@ impl ScenarioError { } } - pub fn new(message: impl Into, err: &dyn std::error::Error) -> Self { + pub fn new(message: impl Into, err: &dyn ProvideErrorMetadata) -> Self { ScenarioError { message: message.into(), - context: Some(err.to_string()), + context: Some(MetadataError::from(err)), + } + } +} + +impl std::error::Error for ScenarioError {} + +impl Display for ScenarioError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.context { + Some(c) => write!(f, "{}: {}", self.message, c), + None => write!(f, "{}", self.message), } } } diff --git a/steering_docs/rust-tech/hello.md b/steering_docs/rust-tech/hello.md index 4304c08954a..5dba6b80dae 100644 --- a/steering_docs/rust-tech/hello.md +++ b/steering_docs/rust-tech/hello.md @@ -155,7 +155,27 @@ async fn main() -> Result<(), aws_sdk_{service}::Error> { ## Pagination Pattern ```rust -// Use pagination to retrieve all results +// Use pagination to retrieve all results with collect +let page_size = page_size.unwrap_or(10); +let items: Result, _> = client + .scan() + .table_name(table) + .limit(page_size) + .into_paginator() + .items() + .send() + .collect() + .await; + +println!("Items in table (up to {page_size}):"); +for item in items? { + println!(" {:?}", item); +} +``` + +## Alternative Pagination Pattern (Using while let) +```rust +// Use while let to iterate through pages let mut items_paginator = client .list_items() .into_paginator() @@ -170,21 +190,6 @@ while let Some(page) = items_paginator.next().await { println!("Retrieved {} items", items.len()); ``` -## Alternative Pagination Pattern (Using collect) -```rust -// Collect all items from paginator -let items: Result, _> = client - .list_items() - .into_paginator() - .items() - .send() - .collect() - .await; - -let items = items?; -println!("Retrieved {} items", items.len()); -``` - ## Error Handling Pattern ```rust #[tokio::main] diff --git a/steering_docs/rust-tech/tests.md b/steering_docs/rust-tech/tests.md index 5244a169a08..e001c981848 100644 --- a/steering_docs/rust-tech/tests.md +++ b/steering_docs/rust-tech/tests.md @@ -62,41 +62,27 @@ tokio = { version = "1.0", features = ["full", "test-util"] } #[cfg(test)] mod tests { use super::*; - use crate::{service}::Mock{Service}Impl; + use crate::rds::MockRdsImpl; use mockall::predicate::*; #[tokio::test] - async fn test_setup_phase_success() { - let mut mock_{service} = Mock{Service}Impl::default(); - - // Setup mock expectations - mock_{service} - .expect_list_resources() - .return_once(|| Ok(vec![])); - - mock_{service} - .expect_create_resource() - .with(eq("test-resource")) - .return_once(|_| Ok("resource-123".to_string())); - - // Test the function - let result = setup_phase(&mock_{service}, "test-resource").await; - - assert!(result.is_ok()); - assert_eq!(result.unwrap(), "resource-123"); - } + async fn test_scenario_set_engine_not_create() { + let mut mock_rds = MockRdsImpl::default(); - #[tokio::test] - async fn test_setup_phase_error() { - let mut mock_{service} = Mock{Service}Impl::default(); + mock_rds + .expect_create_db_cluster_parameter_group() + .with( + eq("RustSDKCodeExamplesDBParameterGroup"), + eq("Parameter Group created by Rust SDK Code Example"), + eq("aurora-mysql"), + ) + .return_once(|_, _, _| Ok(CreateDbClusterParameterGroupOutput::builder().build())); - mock_{service} - .expect_list_resources() - .return_once(|| Err(ScenarioError::with("Failed to list resources"))); + let mut scenario = AuroraScenario::new(mock_rds); - let result = setup_phase(&mock_{service}, "test-resource").await; - - assert!(result.is_err()); + let set_engine = scenario.set_engine("aurora-mysql", "aurora-mysql8.0").await; + + assert!(set_engine.is_err()); } } ``` @@ -207,6 +193,10 @@ mock_{service} // In src/{service}.rs use aws_sdk_{service}::Client as {Service}Client; +use aws_sdk_{service}::operation::describe_db_engine_versions::{ + DescribeDbEngineVersionsOutput, DescribeDBEngineVersionsError +}; +use aws_sdk_{service}::error::SdkError; #[cfg(test)] use mockall::automock; @@ -226,31 +216,18 @@ impl {Service}Impl { {Service}Impl { inner } } - pub async fn list_resources(&self) -> Result, ScenarioError> { - let response = self.inner - .list_resources() + pub async fn describe_db_engine_versions( + &self, + engine: &str, + ) -> Result> { + self.inner + .describe_db_engine_versions() + .engine(engine) .send() .await - .map_err(|e| ScenarioError::new("Failed to list resources", &e))?; - - Ok(response - .resources() - .iter() - .filter_map(|r| r.name()) - .map(String::from) - .collect()) } - pub async fn create_resource(&self, name: &str) -> Result { - let response = self.inner - .create_resource() - .name(name) - .send() - .await - .map_err(|e| ScenarioError::new("Failed to create resource", &e))?; - - Ok(response.resource_id().unwrap_or("").to_string()) - } + // etc } ``` diff --git a/steering_docs/rust-tech/wrapper.md b/steering_docs/rust-tech/wrapper.md index 7b8b20eb131..1198d9307ff 100644 --- a/steering_docs/rust-tech/wrapper.md +++ b/steering_docs/rust-tech/wrapper.md @@ -57,67 +57,46 @@ rustv1/examples/{service}/ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 -use aws_sdk_{service}::{ - Client as {Service}Client, - operation::{ - operation_one::OperationOneOutput, - operation_two::OperationTwoOutput, - }, - error::SdkError, +use aws_sdk_rds::Client as RdsClient; +use aws_sdk_rds::operation::describe_db_engine_versions::{ + DescribeDbEngineVersionsOutput, DescribeDBEngineVersionsError }; +use aws_sdk_rds::error::SdkError; #[cfg(test)] use mockall::automock; // Use the mock in tests, real implementation in production #[cfg(test)] -pub use Mock{Service}Impl as {Service}; +pub use MockRdsImpl as Rds; #[cfg(not(test))] -pub use {Service}Impl as {Service}; +pub use RdsImpl as Rds; -/// Wrapper for {Service} operations to enable mocking in tests -pub struct {Service}Impl { - pub inner: {Service}Client, +/// Wrapper for RDS operations to enable mocking in tests +pub struct RdsImpl { + pub inner: RdsClient, } #[cfg_attr(test, automock)] -impl {Service}Impl { - /// Create a new {Service} wrapper - pub fn new(inner: {Service}Client) -> Self { - {Service}Impl { inner } +impl RdsImpl { + /// Create a new RDS wrapper + pub fn new(inner: RdsClient) -> Self { + RdsImpl { inner } } - /// Perform operation one - pub async fn operation_one( + /// Describe DB engine versions + pub async fn describe_db_engine_versions( &self, - param: &str, - ) -> Result> { + engine: &str, + ) -> Result> { self.inner - .operation_one() - .parameter(param) + .describe_db_engine_versions() + .engine(engine) .send() .await } - /// Perform operation two with pagination - pub async fn operation_two_paginated( - &self, - param: &str, - ) -> Result, SdkError> { - let mut items = Vec::new(); - let mut paginator = self.inner - .operation_two() - .parameter(param) - .into_paginator() - .send(); - - while let Some(page) = paginator.next().await { - let page = page?; - items.extend(page.items().to_vec()); - } - - Ok(items) - } + // etc } ``` @@ -194,27 +173,38 @@ pub async fn list_all_items( ```rust pub async fn list_all_items( &self, + page_size: Option, ) -> Result, SdkError> { + let page_size = page_size.unwrap_or(10); let items: Result, _> = self.inner .list_items() + .limit(page_size) .into_paginator() .items() .send() .collect() .await; + println!("Items retrieved (up to {page_size} per page):"); + for item in items.as_ref().unwrap_or(&vec![]) { + println!(" {:?}", item); + } + items } ``` -### Paginator with Limit +### Paginator with Limit (DynamoDB Example) ```rust -pub async fn list_items_limited( +pub async fn scan_table( &self, - page_size: i32, -) -> Result, SdkError> { + table: &str, + page_size: Option, +) -> Result, SdkError> { + let page_size = page_size.unwrap_or(10); let items: Result, _> = self.inner - .list_items() + .scan() + .table_name(table) .limit(page_size) .into_paginator() .items() @@ -222,6 +212,11 @@ pub async fn list_items_limited( .collect() .await; + println!("Items in table (up to {page_size}):"); + for item in items.as_ref().unwrap_or(&vec![]) { + println!(" {:?}", item); + } + items } ``` @@ -230,11 +225,15 @@ pub async fn list_items_limited( ### Using Client::Error for Simple Cases ```rust -use aws_sdk_{service}::{Client, Error}; +use aws_sdk_ec2::{Client, Error}; + +async fn show_regions(client: &Client) -> Result<(), Error> { + let rsp = client.describe_regions().send().await?; + + for region in rsp.regions() { + // ... + } -pub async fn simple_operation(client: &Client) -> Result<(), Error> { - let response = client.operation().send().await?; - println!("Operation successful"); Ok(()) } ``` @@ -260,30 +259,31 @@ pub async fn detailed_operation( } ``` -### Using into_service_error for Error Matching +### Using anyhow and into_service_error ```rust -use aws_sdk_{service}::operation::operation_name::OperationNameError; +use anyhow::anyhow; -pub async fn handle_specific_errors( - &self, -) -> Result> { - match self.inner.operation().send().await { - Ok(response) => Ok(response), +pub async fn list_contacts(&self) -> Result, anyhow::Error> { + let contacts: Vec = match self + .client + .list_contacts() + .contact_list_name(CONTACT_LIST_NAME) + .send() + .await + { + Ok(list_contacts_output) => { + list_contacts_output.contacts.unwrap().into_iter().collect() + } Err(e) => { - match e.into_service_error() { - OperationNameError::ResourceNotFoundException(_) => { - eprintln!("Resource not found"); - } - OperationNameError::ValidationException(_) => { - eprintln!("Invalid parameters"); - } - e => { - eprintln!("Other error: {:?}", e); - } - } - Err(Box::new(e)) + return Err(anyhow!( + "Error retrieving contact list {}: {}", + CONTACT_LIST_NAME, + e + )) } - } + }; + + Ok(contacts) } ``` @@ -293,26 +293,22 @@ pub async fn handle_specific_errors( ```rust use std::time::Duration; -pub async fn wait_for_resource_ready( +pub async fn wait_for_instance_ready( &self, - resource_id: &str, + id: &str, ) -> Result<(), Box> { - let wait_result = self.inner - .wait_until_resource_ready() - .resource_id(resource_id) + let wait_status_ok = self.inner + .wait_until_instance_status_ok() + .instance_ids(id) .wait(Duration::from_secs(60)) .await; - match wait_result { - Ok(_) => { - println!("Resource {} is ready", resource_id); - Ok(()) - } - Err(err) => { - eprintln!("Error waiting for resource: {:?}", err); - Err(Box::new(err)) - } + match wait_status_ok { + Ok(_) => println!("Rebooted instance {id}, it is started with status OK."), + Err(err) => return Err(err.into()), } + + Ok(()) } ``` From bae0aad580f09797b575acae4036fb4b6d5b86e9 Mon Sep 17 00:00:00 2001 From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com> Date: Tue, 9 Dec 2025 11:57:11 -0600 Subject: [PATCH 4/8] Remove extra files. --- .../General-Code-Example-Standards.md | 196 ---------------- steering_docs/Rust-code-example-standards.md | 215 ------------------ 2 files changed, 411 deletions(-) delete mode 100644 steering_docs/General-Code-Example-Standards.md delete mode 100644 steering_docs/Rust-code-example-standards.md diff --git a/steering_docs/General-Code-Example-Standards.md b/steering_docs/General-Code-Example-Standards.md deleted file mode 100644 index 470c942f53a..00000000000 --- a/steering_docs/General-Code-Example-Standards.md +++ /dev/null @@ -1,196 +0,0 @@ -
-

Program Flow and Readability

- -### Guidelines -* Code examples should prioritize educational value and readability: -* Examples should not include code that only duplicates an API reference without additional information or context. Examples should do _something_ with response objects that contain information specific to the call. -* Function and variable names must be clear and free of typos, with consistent capitalization and naming patterns. -* Complex logic should include descriptive comments. -* Examples must not include unreacheable code, code that does not compile or run, or code that is commented out. -* Prefer to create files as part of the examples to limit including multiple "[example].txt" files throughout the repository. -* When constructing SQL queries, use parameterized values instead of directly concatenating commands with user input. -* AWS Console setup should not be required in order for the example to run. Any setup should happen as part of a CFN or CDK template or script, or in the program itself. The only exception is for "feature access", such as enabling access to a Bedrock model. -* Provide customers with appropriate context in published documentation through metadata and snippet organization. - -### Specification Instructions -* Describe the program flow and instructions for readability, file creation, and CFN or CDK resource deployment. -
- -
-

Code Comments

- -### Guidelines -* Code comments must be descriptive, use complete sentences (with punctuation), and be free of typos or grammatical errors. -* Language owners may establish their own patterns for comments (such as parameter and method descriptions) and should follow those patterns consistently. - -### Specification Instructions -* Describe any important comments that should be included in all implementations. -
- -
-

Pagination

- -### Guidelines -* When pagination is available (determined by checking the service client for paginators), the available paginator must be used. -* In cases where a subset of items is intentionally fetched, it should be noted in the code comments. -* If the intent is listing "all" items, pagination should be included to return all pages of data. -* Hello Service examples should still demonstrate pagination if available, but can log the total count of items (along with a subset of the list, if desired) instead of listing all items. - -### Specification Instructions -* Indicate where pagination is required. -
- -
-

Waiters

- -### Guidelines -* When a waiter is available, it should be used in the example. -* If no waiter is available, the program should poll for the applicable status before continuing. -* If a sleep() function is used, a descriptive comment must be included explaining why. - -### Specification Instructions -* Indicate where a waiter is required. -
- -
-

Error Handling

- -### Guidelines -* Each discrete service call with specific exceptions (such as "resource not found" or "resource already exists") should follow the appropriate spec to handle the error gracefully. - * If a spec is not available for an API call, exceptions should be extended with additional information about the action that the API call is being made for, and then raised as appropriate for the language. -* Examples should not break/quit for their exception handling unless there is a reason to do so, so that resources can be cleaned up gracefully. - -### Specification Instructions -* Each discrete service call with specific exceptions (such as "resource not found" or "resource already exists") should have an appropriate action to inform the user. - * **Examples:** - * A call to Create a resource that results in Resource Exists - * If the scenario can continue with any resource having the same name, the scenario spec may opt to inform the user and continue. - * If the scenario must have the specific resource it attempted to create, the scenario spec should inform the user with an error and finish (possibly without cleanup). - * A call to Describe resource that does not exist - * The scenario should warn the user that the resource does not exist, and may either continue execution if the resource is optional to the completion of the scenario or may skip to clean up of created resources if the resource was critical to the completion of the scenario. -* Some SDKs have modeled exceptions, while others have generic exceptions with string error codes and messages. The latter can be especially confusing to users, and scenarios should take care to properly identify either type of exception. -
- -
-

Resource Strings

- -### Guidelines -* Resource ARNs or names should either be entered by the user, provided as program arguments, or loaded from a separate configuration. -* Code should not use hard-coded strings or "" placeholders to access necessary resources. -* Examples should not use hard-coded Regions unless necessary for the example. - -### Specification Instructions -* Specify how program arguments should be loaded. -
- -
-

S3 Bucket Names

- -### Guidelines -* When creating a new bucket - * A user provided prefix for bucket names is required. If a prefix cannot be found, the example should exit without creating a bucket. - * Assume the existence of an environment variable, `S3_BUCKET_NAME_PREFIX`, that can be used if needed. - * The bucket prefix is postfixed with a unique id before use. e.g. `${S3_BUCKET_NAME_PREFIX}-${uuid()}` -* When referencing an existing bucket - * Access existing bucket names in whatever fashion is most appropriate, just don’t use `S3_BUCKET_NAME_PREFIX` -* Integration tests - * The same rules for new buckets apply to integration tests. If a user chooses to run our tests, but does not provide `S3_BUCKET_NAME_PREFIX`, the tests should fail. - -### Specification Instructions -* Specifications should identify places where use of the `S3_BUCKET_NAME_PREFIX` environment variable is required. - -
- -
-

Security (Username/Passwords)

- -### Guidelines -* User names and passwords or other security artifacts should be entered by the user and not referenced as hard-coded strings. -* They should not be stored or retained, and only pass through to the necessary service, in cases such as Cognito user setup or RDS admin setup actions which require a password. - -### Specification Instructions -* Describe any special handling for security items. -
- -
-

Test Coverage

- -### Guidelines -* New code should have test coverage (can be unit or integration) for each method or logical operation block. -* Refer to the SDK language specification page for test tool details. - -### Specification Instructions -* Follow general guidance for testing, no additional specification requirements. -
- -
-

Configuration Explanations

- -### Guidelines -* If any user configuration, program Args, or other setup is required, they should be described in the code comments and/or the README for that service or services(s). - -### Specification Instructions -* Include descriptions for configurations if they are language-agnostic. -
- -
-

Resource Creation and Cleanup

- -### Guidelines -* Scenarios should include one or more “clean up” functions. These should be highly error resistant, logging but not stopping on any resource removal errors. -* The clean up function should always run, possibly by storing any error(s) from the main body and reporting them after attempting clean up. -* Resources created as part of an example should be cleaned up as part of the program. -* Clean up can include a y/n question to the user before deleting resources. -* If a scenario does not complete due to errors, it should attempt to run the cleanup operation before exiting. - -### Specification Instructions -* Include a description if anything other than a y/n question is needed. -
- -
-

Digital Assets and Sample Files

- -### Guidelines -* Examples should follow the repository standards for adding and managing digital assets and sample files. - -### Specification Instructions -* Include instructions for retrieving/using any shared digital assets. Prefer shared assets over duplication in each language folder. -
- -
-

Hello Service

- -### Guidelines -* Should demonstrate a single service action to get customers started using an SDK with a service. -* Should be copy-paste runnable to reduce any blocks for the user, ideally in a main function or similar. -* Include imports, service client creation, etc. -* Make a single service call to something that requires no input (ListBuckets, etc.). If Hello Service exists for other languages, use the same Action so they are all consistent. -* If pagination is appropriate/available, use it. You may also limit the number of results. -* Print something useful about the output, don't just dump the output (bucket names, etc.). -* Error handling is optional and only if it makes sense. - -### Specification Instructions -* The first implementation for an example (Basic or Workflow) must also include the Hello Service as part of the specification. -
- -
-

SDK Language Tools

- -| Language | Package | Version | Formatter | Linter | Checker | Unit | Base Language Guide | -| -------- | ------- | --------| --------- | ------ | ------- | ---- | -------------- | -|CLI | | | |shellcheck | | |[Shellcheck linter](https://github.com/koalaman/shellcheck)| | -|C++ |git |main | | | | |[C++ Coding Standards Guide](https://github.com/aws/aws-sdk-cpp/blob/main/docs/CODING_STANDARDS.md) | -|.NET |nuget |SDK V3 (.NET 6 or later) |dotnet format | dotnet format | dotnet build | XUnit | [C# (.NET) Code Conventions](https://github.com/dotnet/runtime/blob/main/docs/coding-guidelines/coding-style.md)| -|Go |Go Mod |go-v2 v1.15.3 |gofmt |`golangci-lint` |go build |testing (builtin) |[Go dev](https://go.dev/) | -|Java |Maven |2 | |checkstyle |checkstyle |JUnit |[Oracle Java Code Conventions](https://www.oracle.com/java/technologies/javase/codeconventions-contents.html) | -|JavaScript |NPM |^3.210.0 |prettier |eslint |typescript |vitest |[AirBnB base guide](https://github.com/airbnb/javascript) | -|Kotlin |gradle |0.30.1-beta |ktfmt |ktlint |kotlin | |[Kotlin Coding Conventions](https://kotlinlang.org/docs/coding-conventions.html) | -|PHP |composer |3.283.2 |phpcs (PSR-12) |phpcs |php |phpunit |[PSR-12 Basic Coding Standard for PHP](https://www.php-fig.org/psr/psr-12/) | -|Python |Pip |boto3>= 1.26.79 |Black |pylint |mypy |pytest |[PEP 8 - Style Guide for Python Code](https://peps.python.org/pep-0008/) | -|Ruby |gem | | | | | |[Ruby Style Guide.](https://github.com/rubocop/ruby-style-guide) | -|Rust |Cargo |next |cargo fmt |cargo clippy |cargo check |cargo test |[Rust Style Guide](https://doc.rust-lang.org/nightly/style-guide/) | -|Swift | |0.28.0 | | | | |[Swift Style Guide](https://google.github.io/swift/) | -
- - - diff --git a/steering_docs/Rust-code-example-standards.md b/steering_docs/Rust-code-example-standards.md deleted file mode 100644 index 4a299a5ed7f..00000000000 --- a/steering_docs/Rust-code-example-standards.md +++ /dev/null @@ -1,215 +0,0 @@ -## General Structure - -* One example crate per SDK service, with the same name as the published crate minus `aws-sdk-`. -* Each crate has a `lib.rs` in the root, which uses pub mod for each module part. - * Skipped if crates only have bin examples. -* `bin` dir for all binaries (CLI programs and Scenario runners). - * Keep user interaction mainly in the bin areas, do not put user interaction in supporting libraries. - * Use the [inquire](https://crates.io/crates/inquire) crate for complex user input, and `stdin().read_line()` for simple user input. -* Decompose scenario actions into functions in `lib/[scenario_name].rs`, possibly as part of a struct with scenario information to manage the communication saga as en Epic. (See [aurora scenario](https://github.com/awsdocs/aws-doc-sdk-examples/blob/de7b1ee3fae2e3cd7d81a24c17345040f76b1d75/rustv1/examples/aurora/src/aurora_scenario/mod.rs)) -* One client wrapper for integration test mocking, if necessary. (See [aurora/src/rds.rs](https://github.com/awsdocs/aws-doc-sdk-examples/blob/de7b1ee3fae2e3cd7d81a24c17345040f76b1d75/rustv1/examples/aurora/src/rds.rs)) -* Initialize client once, in main, using behavior_subject, and clone when necessary. - -## General Program Flow and Readability - -* When to prefer Loop vs Iterator: - * If there is a clear transformation from `T` to `U`, prefer an iterator. - * Do not nest control flow inside an iterator. - * Extract the logic to a dedicated function. - * Prefer a for loop if the function would be difficult to extract. - * Prefer an extracted function if the logic is nuanced and should be tested. -* How deep to go in nesting vs when to extract a new function? - * Two deep is fine, three deep is pushing it, four deep is probably too much. - * Prefer an extracted function if the logic is nuanced and should be tested. -* When to Trait vs bare functions? - * Examples rarely have enough complexity that a Trait is worth the mental overhead. - * bare functions or a struct to manage the Epic communication saga, if necessary. - -## Pagination, Waiters, and Error Handling - -* All operations are async. -* Use [tokio](https://tokio.rs/) for async runtime. -* List operations typically provide `.into_paginator()` as an async iterator. Use them whenever available, unless the example is specifically showing non-paginator pieces. - ```rust - let page_size = page_size.unwrap_or(10); - let items: Result, _> = client - .scan() - .table_name(table) - .limit(page_size) - .into_paginator() - .items() - .send() - .collect() - .await; - - println!("Items in table (up to {page_size}):"); - for item in items? { - println!(" {:?}", item); - } - ``` -* Use builtin waters as `client.wait_until_...` whenever available. (Example in [EC2](https://github.com/awsdocs/aws-doc-sdk-examples/blob/2546e4ac8c7963c5a97ac838917e9b9dcbe0ba29/rustv1/examples/ec2/src/bin/reboot-instance.rs#L29-L51)) - ```rust - let wait_status_ok = client - .wait_until_instance_status_ok() - .instance_ids(id) - .wait(Duration::from_secs(60)) - .await; - - match wait_status_ok { - Ok(_) => println!("Rebooted instance {id}, it is started with status OK."), - Err(err) => return Err(err.into()), - } - ``` -* Modeled errors for scenarios (see [Aurora](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/rustv1/examples/aurora/src/aurora_scenario/mod.rs#L55-L85)) (hand-modeled or thiserror). - ```rust - #[derive(Debug, PartialEq, Eq)] - pub struct ScenarioError { - message: String, - context: Option, - } - - impl ScenarioError { - pub fn with(message: impl Into) -> Self { - ScenarioError { - message: message.into(), - context: None, - } - } - - pub fn new(message: impl Into, err: &dyn ProvideErrorMetadata) -> Self { - ScenarioError { - message: message.into(), - context: Some(MetadataError::from(err)), - } - } - } - - impl std::error::Error for ScenarioError {} - impl Display for ScenarioError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match &self.context { - Some(c) => write!(f, "{}: {}", self.message, c), - None => write!(f, "{}", self.message), - } - } - } - ``` -* Client::Error for one-off “scripts” or single-action examples. (See example in [EC2](https://github.com/awsdocs/aws-doc-sdk-examples/blob/de7b1ee3fae2e3cd7d81a24c17345040f76b1d75/rustv1/examples/ec2/src/bin/ec2-helloworld.rs#L21-L33)). - ```rust - use aws_sdk_ec2::{Client, Error}; - - async fn show_regions(client: &Client) -> Result<(), Error> { - let rsp = client.describe_regions().send().await?; - - for region in rsp.regions() { - // ... - } - - Ok(()) - } - ``` -* Anyhow and .into_service_error for things in the middle. (See example in [SES](https://github.com/awsdocs/aws-doc-sdk-examples/blob/208abff74308c11f700d8321eab0f625393ffdb4/rustv1/examples/ses/src/newsletter.rs#L239-L256)). - ```rust - let contacts: Vec = match self - .client - .list_contacts() - .contact_list_name(CONTACT_LIST_NAME) - .send() - .await - { - Ok(list_contacts_output) => { - list_contacts_output.contacts.unwrap().into_iter().collect() - } - Err(e) => { - return Err(anyhow!( - "Error retrieving contact list {}: {}", - CONTACT_LIST_NAME, - e - )) - } - }; - ``` - -## Runtime Resources - -* include_bytes! or include_str! for compile-time data. -* fs::read_to_string for glob load, fs::File for streaming, tie to SdkBody when needed (see example in s3). -* When showing examples that handle PII, use the [secrets](https://crates.io/crates/secrets) crate. - -## Test Coverage - -* Unit tests in `#[cfg(test)] mod test` or `test.rs`, integration tests in the tests folder. - * See fully worked example in [Aurora](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/rustv1/examples/aurora/src/aurora_scenario/tests.rs) - * Unit test - ```rust - use crate::rds::MockRdsImpl; - - #[tokio::test] - async fn test_scenario_set_engine_not_create() { - let mut mock_rds = MockRdsImpl::default(); - - mock_rds - .expect_create_db_cluster_parameter_group() - .with( - eq("RustSDKCodeExamplesDBParameterGroup"), - eq("Parameter Group created by Rust SDK Code Example"), - eq("aurora-mysql"), - ) - .return_once(|_, _, _| Ok(CreateDbClusterParameterGroupOutput::builder().build())); - - let mut scenario = AuroraScenario::new(mock_rds); - - let set_engine = scenario.set_engine("aurora-mysql", "aurora-mysql8.0").await; - - assert!(set_engine.is_err()); - } - ``` - * Mocks - ```rust - #[cfg(test)] - use mockall::automock; - - #[cfg(test)] - pub use MockRdsImpl as Rds; - #[cfg(not(test))] - pub use RdsImpl as Rds; - - pub struct RdsImpl { - pub inner: RdsClient, - } - - #[cfg_attr(test, automock)] - impl RdsImpl { - pub fn new(inner: RdsClient) -> Self { - RdsImpl { inner } - } - - pub async fn describe_db_engine_versions( - &self, - engine: &str, - ) -> Result> { - self.inner - .describe_db_engine_versions() - .engine(engine) - .send() - .await - } - - // etc - } - ``` - -* Coverage with [cargo-llm-cov](https://lib.rs/crates/cargo-llvm-cov) - -## Configuration Explanations - -* For command line examples, prefer using clap and command line args. -* For server and lambda examples, prefer using args. -* Use the actix-web crate for HTTP servers. - -## AWS Resource Creation and Cleanup - -* For standalone scenarios, there are generally one or more “clean up” functions. These should be highly error resistant, logging but not stopping on any resource removal errors. The clean up function should always run, possibly by storing any error(s) from the main body and reporting them after attempting clean up. -* When implementing a scenario, try to start with the clean up. -* Scenarios requiring clean up may add a “—no-cleanup” or "—cleanup=false" flag to skip performing the cleanup step. -* Follow the spec for other cleanup decisions. \ No newline at end of file From da003cc782bae748fe1197fd110225535913ed27 Mon Sep 17 00:00:00 2001 From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com> Date: Tue, 9 Dec 2025 12:01:50 -0600 Subject: [PATCH 5/8] Delete README.md --- steering_docs/rust-tech/README.md | 227 ------------------------------ 1 file changed, 227 deletions(-) delete mode 100644 steering_docs/rust-tech/README.md diff --git a/steering_docs/rust-tech/README.md b/steering_docs/rust-tech/README.md deleted file mode 100644 index f820a2b3159..00000000000 --- a/steering_docs/rust-tech/README.md +++ /dev/null @@ -1,227 +0,0 @@ -# Rust Steering Documentation - -This directory contains modular steering documents for generating AWS SDK for Rust code examples. Each document focuses on a specific aspect of the code generation process. - -## Document Overview - -### Core Generation Documents - -#### [basics.md](basics.md) -**Purpose**: Interactive scenario generation -**Use When**: Creating complete workflow scenarios with user interaction -**Key Topics**: -- Specification-driven development -- Scenario phase structure (setup, demonstration, examination, cleanup) -- User interaction patterns with inquire crate -- Custom ScenarioError type -- Async/await patterns with tokio - -#### [hello.md](hello.md) -**Purpose**: Hello example generation -**Use When**: Creating the simplest possible service introduction -**Key Topics**: -- Mandatory hello examples for every service -- Pagination patterns for list operations -- Basic async/await with tokio -- Minimal example structure -- Direct client usage - -#### [wrapper.md](wrapper.md) -**Purpose**: Service wrapper generation -**Use When**: Mocking is needed for integration tests -**Key Topics**: -- Optional wrapper creation (only when needed) -- Mockall integration for testing -- Pagination patterns -- Waiter patterns -- Error handling strategies -- When to use traits vs bare functions - -### Testing and Quality - -#### [tests.md](tests.md) -**Purpose**: Test generation and patterns -**Use When**: Creating unit and integration tests -**Key Topics**: -- Unit tests with #[cfg(test)] -- Integration tests in tests/ folders -- Mockall for mocking AWS services -- Async testing with #[tokio::test] -- Code coverage with cargo-llvm-cov -- Test organization patterns - -### Documentation and Metadata - -#### [metadata.md](metadata.md) -**Purpose**: Metadata generation for documentation pipeline -**Use When**: Integrating examples with AWS documentation -**Key Topics**: -- Specification-based metadata keys -- Snippet tag format and conventions -- Service abbreviations -- Metadata validation -- Cross-service examples - -#### [readme_writeme.md](readme_writeme.md) -**Purpose**: README generation with writeme tool -**Use When**: Creating or updating service documentation -**Key Topics**: -- Writeme tool setup and usage -- README structure and content -- Documentation dependencies -- Cargo.toml documentation -- CI/CD integration - -### Orchestration - -#### [orchestration.md](orchestration.md) -**Purpose**: Component coordination and workflows -**Use When**: Understanding the complete development process -**Key Topics**: -- Full service implementation workflow -- Individual component updates -- Quality gates and validation -- Error recovery strategies -- CI/CD pipeline integration - -## Quick Start Guide - -### For New Service Implementation -1. **Read**: [orchestration.md](orchestration.md) for overall workflow -2. **Start with**: [hello.md](hello.md) to create basic connectivity example -3. **Add wrapper**: [wrapper.md](wrapper.md) if mocking is needed for tests -4. **Build scenario**: [basics.md](basics.md) following service specification -5. **Write tests**: [tests.md](tests.md) for coverage -6. **Add metadata**: [metadata.md](metadata.md) for documentation -7. **Generate docs**: [readme_writeme.md](readme_writeme.md) for README - -### For Updating Existing Examples -1. **Identify component**: Determine which document applies -2. **Read guidance**: Review the specific document -3. **Make changes**: Follow the patterns and requirements -4. **Validate**: Use the validation commands in the document -5. **Update docs**: Regenerate README if needed - -## Common Patterns - -### File Structure -``` -rustv1/examples/{service}/ -├── Cargo.toml -├── README.md -├── src/ -│ ├── lib.rs -│ ├── {service}.rs # Optional wrapper -│ ├── bin/ -│ │ ├── hello.rs # MANDATORY -│ │ └── {scenario-name}.rs -│ └── {scenario_name}/ -│ ├── mod.rs -│ ├── scenario.rs -│ └── tests/ -│ └── mod.rs -``` - -### Snippet Tag Format -```rust -// snippet-start:[{service}.rust.{action_name}] -// Code here -// snippet-end:[{service}.rust.{action_name}] -``` - -### Error Handling -```rust -// Custom error type for scenarios -#[derive(Debug, PartialEq, Eq)] -pub struct ScenarioError { - message: String, - context: Option, -} -``` - -### Async Pattern -```rust -#[tokio::main] -async fn main() -> Result<(), Error> { - let sdk_config = aws_config::defaults(BehaviorVersion::latest()) - .load() - .await; - let client = Client::new(&sdk_config); - // Operations here -} -``` - -## Standards and Requirements - -### General Code Standards -All Rust examples must follow: -- ✅ **Rust 2021 edition** or later -- ✅ **Async/await** with tokio runtime -- ✅ **Pagination** for list operations when available -- ✅ **Error handling** with Result types -- ✅ **Snake_case** naming conventions -- ✅ **Module documentation** with //! comments -- ✅ **Snippet tags** for all code examples - -### Mandatory Components -Every service MUST include: -- ✅ **Hello example** in `src/bin/hello.rs` -- ✅ **Cargo.toml** with proper dependencies -- ✅ **README.md** (generated with writeme) -- ✅ **Metadata** in `.doc_gen/metadata/{service}_metadata.yaml` - -### Optional Components -Create only when needed: -- Service wrapper in `src/{service}.rs` (for mocking) -- Integration tests in scenario tests/ folders -- Multiple scenario binaries - -## Validation Commands - -### Build and Test -```bash -cargo build # Build all examples -cargo test # Run all tests -cargo fmt --check # Check formatting -cargo clippy -- -D warnings # Run linter -``` - -### Documentation -```bash -cd .tools/readmes -source .venv/bin/activate -python -m writeme --languages Rust:1 --services {service} -``` - -## Related Documents - -### Parent Document -- [rust-tech.md](../rust-tech.md) - Main Rust technology overview - -### General Standards -- [General-Code-Example-Standards.md](../General-Code-Example-Standards.md) -- [Rust-code-example-standards.md](../Rust-code-example-standards.md) - -## Getting Help - -### Common Issues -- **Compilation errors**: Check [wrapper.md](wrapper.md) for error handling patterns -- **Test failures**: See [tests.md](tests.md) for testing patterns -- **Metadata errors**: Review [metadata.md](metadata.md) for correct format -- **Documentation issues**: Check [readme_writeme.md](readme_writeme.md) - -### Best Practices -- Always consult knowledge bases before starting -- Follow service specification exactly -- Use existing examples (like EC2) as reference -- Validate frequently during development -- Test both happy and error paths - -## Contributing - -When adding or updating steering documents: -1. Follow the established structure and format -2. Include practical examples and code snippets -3. Add validation commands and quality gates -4. Update this README with any new documents -5. Cross-reference related documents From 81bd4e3b07196175e77ec6efb4edef134dc49193 Mon Sep 17 00:00:00 2001 From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com> Date: Wed, 10 Dec 2025 08:52:13 -0600 Subject: [PATCH 6/8] Delete rust-tech.md --- steering_docs/rust-tech.md | 177 ------------------------------------- 1 file changed, 177 deletions(-) delete mode 100644 steering_docs/rust-tech.md diff --git a/steering_docs/rust-tech.md b/steering_docs/rust-tech.md deleted file mode 100644 index 4ec2e598069..00000000000 --- a/steering_docs/rust-tech.md +++ /dev/null @@ -1,177 +0,0 @@ -# Rust Technology Stack & Build System - -## Overview -This document provides an overview of the Rust technology stack and build system for AWS SDK code examples. For detailed guidance on specific components, see the modular steering documents in the `rust-tech/` directory: - -- **[basics.md](rust-tech/basics.md)** - Interactive scenario generation -- **[hello.md](rust-tech/hello.md)** - Hello example generation -- **[wrapper.md](rust-tech/wrapper.md)** - Service wrapper generation -- **[tests.md](rust-tech/tests.md)** - Test generation and patterns -- **[metadata.md](rust-tech/metadata.md)** - Metadata generation -- **[orchestration.md](rust-tech/orchestration.md)** - Component orchestration -- **[readme_writeme.md](rust-tech/readme_writeme.md)** - README generation - -## Rust SDK v1 Development Environment - -### Build Tools & Dependencies -- **Build System**: Cargo -- **Testing Framework**: Built-in Rust testing -- **Package Manager**: Cargo with crates.io -- **SDK Version**: AWS SDK for Rust v1 -- **Rust Version**: Latest stable Rust - -### Common Build Commands - -```bash -# Development -cargo check # Check compilation without building -cargo build # Build project -cargo build --release # Build optimized release version - -# Testing -cargo test # Run all tests -cargo test --test integration # Run integration tests -cargo test -- --nocapture # Run tests with output - -# Execution -cargo run --bin hello # Run hello scenario -cargo run --bin getting-started # Run getting started scenario -cargo run --bin {scenario-name} # Run specific scenario -``` - -### Rust-Specific Pattern Requirements - -**CRITICAL**: Rust examples follow a specific directory structure pattern. Always examine existing Rust examples (like EC2) before creating new ones. - -#### Correct Structure for Rust Scenarios -``` -rustv1/examples/{service}/ -├── Cargo.toml -├── README.md -├── src/ -│ ├── lib.rs -│ ├── {service}.rs # Service wrapper -│ ├── bin/ -│ │ ├── hello.rs # MANDATORY: Hello scenario -│ │ ├── {action-one}.rs # Individual action file -│ │ ├── {action-two}.rs # Individual action file, etc. -│ │ └── {scenario-name}.rs # Other scenario entry points -│ └── {scenario_name}/ -│ ├── mod.rs -│ ├── scenario.rs # Main scenario logic -│ └── tests/ -│ └── mod.rs # Integration tests -``` - -#### Key Structural Points -- **MANDATORY**: Every service must include `src/bin/hello.rs` as the simplest example -- **Follow the EC2 example structure exactly** - it's the canonical pattern -- **Service wrapper goes in `src/{service}.rs`** (e.g., `src/comprehend.rs`) -- **Tests go in `{scenario_name}/tests/mod.rs`** for integration testing -- **Hello scenario**: Should demonstrate the most basic service operation - -#### File Naming Conventions -- Use snake_case for all Rust files and directories -- Binary files: `{action}.rs` in `src/bin/` directory -- Service modules: `{service}.rs` in `src/` directory -- Scenario modules: `{scenario_name}/mod.rs` and `scenario.rs` - -#### Hello Scenario Structure -- **File location**: `src/bin/hello.rs` -- **Function structure**: `main()` function as entry point with `tokio::main` attribute -- **Documentation**: Include module-level documentation explaining the hello example - -#### Code Structure Standards -- **Modules**: Use `mod.rs` files for module organization -- **Functions**: Use snake_case for function names -- **Structs/Enums**: Use PascalCase for type names -- **Constants**: Use UPPER_SNAKE_CASE for constants -- **Async/Await**: Use `tokio` runtime for async operations - -#### Error Handling Patterns -```rust -use aws_sdk_s3::{Client, Error}; -use aws_config::meta::region::RegionProviderChain; - -#[tokio::main] -async fn main() -> Result<(), Error> { - let region_provider = RegionProviderChain::default_provider().or_else("us-east-1"); - let config = aws_config::from_env().region(region_provider).load().await; - let client = Client::new(&config); - - match client.list_buckets().send().await { - Ok(response) => { - // Handle successful response - println!("Buckets: {:?}", response.buckets()); - Ok(()) - } - Err(e) => { - // Handle error - eprintln!("Error: {}", e); - Err(e) - } - } -} -``` - -#### Testing Standards -- **Integration tests**: Place in `{scenario_name}/tests/mod.rs` -- **Unit tests**: Include `#[cfg(test)]` modules in source files -- **Async testing**: Use `#[tokio::test]` for async test functions -- **Test naming**: Use descriptive function names with snake_case - -#### Cargo.toml Configuration -```toml -[package] -name = "{service}-examples" -version = "0.1.0" -edition = "2021" - -[[bin]] -name = "hello" -path = "src/bin/hello.rs" - -[[bin]] -name = "{action-name}" -path = "src/bin/{action-name}.rs" - -[dependencies] -aws-config = "1.0" -aws-sdk-{service} = "1.0" -tokio = { version = "1.0", features = ["full"] } -tracing-subscriber = "0.3" -``` - -#### Documentation Requirements -- **Module documentation**: Use `//!` for module-level docs -- **Function documentation**: Use `///` for function documentation -- **Inline comments**: Explain complex AWS service interactions -- **README sections**: Include Cargo setup and execution instructions - -### Language-Specific Pattern Errors to Avoid -- ❌ **NEVER assume file naming patterns** without checking existing examples -- ❌ **NEVER skip the mandatory `src/bin/hello.rs` file** -- ❌ **NEVER use camelCase for Rust identifiers** -- ❌ **NEVER ignore proper error handling with Result types** -- ❌ **NEVER forget to use async/await for AWS operations** - -### Best Practices -- ✅ **ALWAYS examine `rustv1/examples/ec2/` structure first** -- ✅ **ALWAYS include the mandatory hello scenario** -- ✅ **ALWAYS use snake_case naming conventions** -- ✅ **ALWAYS handle errors with Result types and proper error propagation** -- ✅ **ALWAYS use tokio runtime for async AWS operations** -- ✅ **ALWAYS follow the established directory structure exactly** - -### Cargo Workspace Integration -- **Workspace member**: Each service example is a workspace member -- **Shared dependencies**: Common dependencies managed at workspace level -- **Build optimization**: Shared target directory for faster builds - -### Integration with Knowledge Base -Before creating Rust code examples: -1. Query `coding-standards-KB` for "Rust-code-example-standards" -2. Query `Rust-premium-KB` for "Rust implementation patterns" -3. **CRITICAL**: Always examine existing EC2 example structure as canonical pattern -4. Follow KB-documented patterns for Cargo project structure and module organization -5. Validate against existing Rust examples only after KB consultation \ No newline at end of file From 2e19b1fc7929f9d13d18dc688d009ee07d9df3e3 Mon Sep 17 00:00:00 2001 From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com> Date: Wed, 10 Dec 2025 09:21:51 -0600 Subject: [PATCH 7/8] Fix snippets Fix snippets --- steering_docs/rust-tech/hello.md | 8 ++++---- steering_docs/rust-tech/metadata.md | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/steering_docs/rust-tech/hello.md b/steering_docs/rust-tech/hello.md index 5dba6b80dae..a2c18131912 100644 --- a/steering_docs/rust-tech/hello.md +++ b/steering_docs/rust-tech/hello.md @@ -255,16 +255,16 @@ tracing-subscriber = { version = "0.3", features = ["env-filter"] } ```rust // ✅ CORRECT -// snippet-start:[{service}.rust.hello] +// snippet-start:[{service2}.rust.hello] #[tokio::main] async fn main() -> Result<(), aws_sdk_{service}::Error> { // Implementation } -// snippet-end:[{service}.rust.hello] +// snippet-end:[{service2}.rust.hello] // ❌ WRONG - Old format -// snippet-start:[rust.example_code.{service}.hello] -// snippet-end:[rust.example_code.{service}.hello] +// snippet-start:[rust.example_code.{service3}.hello] +// snippet-end:[rust.example_code.{service3}.hello] ``` **Format**: `[{service}.rust.hello]` diff --git a/steering_docs/rust-tech/metadata.md b/steering_docs/rust-tech/metadata.md index 4e5fce4fb3e..585adc3104f 100644 --- a/steering_docs/rust-tech/metadata.md +++ b/steering_docs/rust-tech/metadata.md @@ -150,15 +150,15 @@ pub async fn run_scenario(client: Client) -> Result<(), ScenarioError> { ### Examples ```rust // ✅ CORRECT -// snippet-start:[s3.rust.create_bucket] -// snippet-start:[dynamodb.rust.list_tables] -// snippet-start:[ec2.rust.describe_instances] -// snippet-start:[guardduty.rust.hello] +// s3.rust.create_bucket +// dynamodb.rust.list_tables +// ec2.rust.describe_instances +// guardduty.rust.hello // ❌ WRONG - Don't use these formats -// snippet-start:[rust.example_code.s3.create_bucket] -// snippet-start:[S3.Rust.CreateBucket] -// snippet-start:[s3-rust-create-bucket] +// rust.example_code.s3.create_bucket +// S3.Rust.CreateBucket +// s3-rust-create-bucket ``` ## Service Abbreviations From 9c958870348c35d8a56772e5127117493a872b2b Mon Sep 17 00:00:00 2001 From: Rachel Hagerman <110480692+rlhagerm@users.noreply.github.com> Date: Wed, 10 Dec 2025 09:26:53 -0600 Subject: [PATCH 8/8] Update hello.md --- steering_docs/rust-tech/hello.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/steering_docs/rust-tech/hello.md b/steering_docs/rust-tech/hello.md index a2c18131912..d706f492bce 100644 --- a/steering_docs/rust-tech/hello.md +++ b/steering_docs/rust-tech/hello.md @@ -109,7 +109,7 @@ For services where pagination is not available or not needed: use aws_config::BehaviorVersion; use aws_sdk_{service}::Client; -// snippet-start:[{service}.rust.hello] +// snippet-start:[{service4}.rust.hello] #[tokio::main] async fn main() -> Result<(), aws_sdk_{service}::Error> { tracing_subscriber::fmt::init(); @@ -133,7 +133,7 @@ async fn main() -> Result<(), aws_sdk_{service}::Error> { Ok(()) } -// snippet-end:[{service}.rust.hello] +// snippet-end:[{service4}.rust.hello] ``` ## Hello Examples by Service Type