agentleFS
Microsoft AzureKnown publisherAzure/azure-sdk-for-rust/sdk/cosmos/AGENTS.md

azure-sdk-for-rust / cosmos

AGENTS.md887 starsChanged 29 days agoMITView on GitHub

This file contains coding guidelines and architectural patterns specific to the Azure Cosmos DB SDK for Rust (sdk/cosmos). - Start with docs/README.md to inventory Cosmos project documentation. - Read docs/Project.md and docs/Architecture.md for project and architecture context, then use the indexes in docs/README.md to find relevant specs, ADRs, and reports. - Do not use relative links in docs/. Keep useful target paths as plain code instead, for example, specs/0017-patch-handler.md. Use absolute links from crate READMEs. - Do not link from…

The file

Show its rules and text

We split this AGENTS.md into the rules it gives an agent

43 rules. About 0 are common, 22 are distinctive, and 21 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈22

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 21

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Start with docs/README.md to inventory Cosmos project documentation.

    line 13
  2. Very specific:

    Do not use relative links in docs/.

    line 19
  3. Distinctive:

    Do not link from Markdown to Rust source files.

    line 22
  4. Distinctive:

    Do not load the entire documentation set when only a few indexed documents are relevant.

    line 24
  5. Distinctive:

    Follow the Azure SDK Design Guidelines for Rust as the primary reference for API design, error handling, async patterns, and module …

    line 29
  6. Distinctive:

    Follow the Data-Oriented Programming in Rust principles from https://analogrelay.github.io/dop-in-rust:

    line 33
  7. Distinctive:

    Separate data from behavior: Use plain structs for data and separate modules/traits for operations

    line 35
  8. Distinctive:

    Model data with enums and structs: Prefer algebraic data types to represent domain concepts

    line 36
  9. Distinctive:

    Treat data as immutable: Use owned types and transformations rather than mutation where practical

    line 37
  10. Distinctive:

    Always implement standard Rust traits instead of creating custom public methods when equivalent functionality exists:

    line 42
  11. Distinctive:

    If you need a non-fallible parse internally, create a private helper method and have the trait implementation call it.

    line 71
  12. Very specific:

    Use usize only for memory-related numbers (addresses, array indices, memory/buffer sizes) — including at FFI boundaries when the number …

    line 79
  13. Distinctive:

    Use a specific-size integer when it's necessary for a specific, well-understood memory optimization (e.g., packing a struct), or to match …

    line 80
  14. Very specific:

    Use u16 or u32 for domain numbers when they will clearly fit — not just "u16/u32 should be enough", but "more than 65,535/4 billion of …

    line 81
  15. Very specific:

    Use u64 for domain numbers when the expected range isn't clear, or is known to potentially exceed 32 bits.

    line 82
  16. Distinctive:

    Use the builder pattern for complex requests (queries, operations)

    line 88
  17. Distinctive:

    Prefer method chaining with clear, descriptive names

    line 90
  18. Very specific:

    Use azure_data_cosmos::Result<T> (SDK) or azure_data_cosmos_driver::error::Result<T> (driver) for all fallible operations — both alias …

    line 94
  19. Distinctive:

    All I/O operations must be async

    line 104
  20. Distinctive:

    Use type state pattern for operations that require specific resource context

    line 114
  21. Very specific:

    Critical Requirement: All types in models/ must be serializable and used for wire format.

    line 268
  22. Distinctive:

    Use builders for complex client construction:

    line 385
  23. Very specific:

    Use #[recorded::test] for integration tests that interact with Cosmos DB

    line 421
  24. Distinctive:

    AVOID writing tests for trivial or obvious functionality, like derived trait behavior, constructor behavior that just assigns fields, or …

    line 426
  25. Very specific:

    Use assert! for boolean assertions instead of assert_eq!(value, true/false)

    line 433
  26. Very specific:

    Prefer the strongest assertion that matches the contract: Use assert_eq!(vec, expected_vec) instead of assert!(vec.contains(x)) — the …

    line 434
  27. Very specific:

    This is the most common CI failure: - always run cargo fmt after making ANY code changes, including:

    line 450
  28. Very specific:

    When to suppress warnings: Only use #[allow(clippy::...)] when:

    line 477
  29. Distinctive:

    Always add a comment explaining why the warning is allowed.

    line 483
  30. Very specific:

    Always run cargo fmt on generated or modified Rust code before considering the task complete.

    line 487
  31. Very specific:

    When editing existing files, ensure the changes conform to rustfmt standards.

    line 488
  32. Very specific:

    When editing non-Rust files (JSON, YAML, TOML, etc.), respect the root .editorconfig settings.

    line 490
  33. Very specific:

    Run cspell to check for spelling errors in changed files (requires Node.js):

    line 494
  34. Very specific:

    Cosmos-specific words not in the dictionary: add them to ignoreWords in sdk/cosmos/.cspell.json.

    line 504
  35. Distinctive:

    Before considering any task complete, run the following checks in order on all modified crates:

    line 509
  36. Distinctive:

    All documentation warnings must be resolved before completing the task

    line 517
  37. Distinctive:

    When documenting factory methods or APIs, ensure the linked method names match the actual implementation

    line 540
  38. Very specific:

    Use the exact method name (e.g., method_by_name not just method) in doc links like [StructName::method_by_name]

    line 541
  39. Very specific:

    Use Cow<'_, str> for fields that might be borrowed or owned

    line 572
  40. Distinctive:

    Every public API should document:

    line 578
  41. Very specific:

    Put detailed rationale in docs/.

    line 599
  42. Distinctive:

    Keep comments short and dense.

    line 604
  43. Very specific:

    When a rationale really needs paragraphs, it belongs in docs/, not in the source.

    line 616

≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
AGENTS.md

Cosmos SDK Instructions

This file contains coding guidelines and architectural patterns specific to the Azure Cosmos DB SDK for Rust (sdk/cosmos).

Core Principles

Documentation navigation

  • Start with docs/README.md to inventory Cosmos project documentation.
  • Read docs/Project.md and docs/Architecture.md for project and architecture context, then use the indexes in docs/README.md to find relevant specs, ADRs, and reports.
  • Do not use relative links in docs/. Keep useful target paths as plain code instead, for example, specs/0017-patch-handler.md. Use absolute links from crate READMEs.
  • Do not link from Markdown to Rust source files. Those implementation links atrophy as code moves.
  • Do not load the entire documentation set when only a few indexed documents are relevant.

Design guidelines

  • Follow the Azure SDK Design Guidelines for Rust as the primary reference for API design, error handling, async patterns, and module organization unless they conflict with Cosmos-specific requirements outlined below or in other cosmos.*.instructions.md files.

Data-Oriented Programming (DOP)

Follow the Data-Oriented Programming in Rust principles from https://analogrelay.github.io/dop-in-rust:

  • Separate data from behavior: Use plain structs for data and separate modules/traits for operations
  • Model data with enums and structs: Prefer algebraic data types to represent domain concepts
  • Treat data as immutable: Use owned types and transformations rather than mutation where practical
  • Validate at boundaries: Parse/validate data when it enters the system, trust it internally

Prefer Standard Traits Over Custom Methods

Always implement standard Rust traits instead of creating custom public methods when equivalent functionality exists:

  • Parsing strings: Implement std::str::FromStr instead of pub fn from_str(s: &str)
  • Converting types: Implement From<T>/Into<T> instead of pub fn to_x() or pub fn from_x()
  • Default values: Implement Default instead of pub fn new_default()
  • Iteration: Implement Iterator/IntoIterator instead of pub fn get_items()
  • Display formatting: Implement Display/Debug instead of pub fn to_string()
  • Cloning: Implement Clone instead of pub fn copy()
  • Comparison: Implement PartialEq/Eq/PartialOrd/Ord instead of custom comparison methods

Rationale: Standard traits enable generic programming, work with Rust's type system, and follow Rust idioms.

Example:

// ❌ BAD: Custom public method duplicates trait functionality
impl MyType {
    pub fn from_str(s: &str) -> Option<Self> { /* ... */ }
}

// ✅ GOOD: Implement the standard trait
impl std::str::FromStr for MyType {
    type Err = azure_data_cosmos::Error;
    fn from_str(s: &str) -> Result<Self, Self::Err> { /* ... */ }
}

// Users can then use: "value".parse::<MyType>()

If you need a non-fallible parse internally, create a private helper method and have the trait implementation call it.

Encode domain semantics in trait impls: When a type has a domain-specific comparison rule, ordering, or equality definition, implement it directly in the type's Ord/PartialOrd/Eq traits rather than creating standalone helper functions that callers must remember to use. This prevents inconsistency — every comparison automatically gets the right semantics, and you can't accidentally use the wrong one.

Integer Type Selection

Choose integer types deliberately; don't default to usize or i32 out of habit:

  • Use usize only for memory-related numbers (addresses, array indices, memory/buffer sizes) — including at FFI boundaries when the number refers to memory.
  • Use a specific-size integer when it's necessary for a specific, well-understood memory optimization (e.g., packing a struct), or to match an FFI/ABI requirement.
  • Use u16 or u32 for domain numbers when they will clearly fit — not just "u16/u32 should be enough", but "more than 65,535/4 billion of these would be ridiculous" (e.g., retry counts, byte offsets within a single document).
  • Use u64 for domain numbers when the expected range isn't clear, or is known to potentially exceed 32 bits.

Cosmos-Specific Patterns

Request Building

  • Use the builder pattern for complex requests (queries, operations)
  • Builders should be type-safe, guiding users through required parameters
  • Prefer method chaining with clear, descriptive names

Error Handling

  • Use azure_data_cosmos::Result<T> (SDK) or azure_data_cosmos_driver::error::Result<T> (driver) for all fallible operations — both alias Result<T, crate::Error> over the typed Cosmos error.
  • Prefer returning Result::Err over panicking in public methods whose inputs could originate from user-constructed types (even indirectly). Callers can then decide whether to propagate, log, or handle — rather than crashing their application. Use assert!/panic! only for true invariant violations that indicate programmer error in internal code.
  • Cosmos-specific errors should provide:
    • HTTP status code
    • Request charge (RU/s) when available
    • Activity ID for tracing
    • Sub-status code for detailed error classification

Async/Await

  • All I/O operations must be async
  • Async runtime
    • For sdk/cosmos/azure_data_cosmos and sdk/cosmos/azure_data_cosmos_driver keep using the same async runtime abstractions as azure_core does.
  • Streaming (use of futures::Stream)
    • There is no need to consider streaming for payloads of individual requests/responses because the Cosmos DB service enforces rather strict limits on request and response payload size (max. 4 MB - only via config overrides extendable to 16 MB per response payload)
    • There is a need for pagination for example for query or change feed results (could return multiple pages - each page created by one or multiple responses) - so futures::Stream might be used there to achieve pagination - but for transport, assuming buffered transport is sufficient.

Resource Management

  • Model Cosmos resources (databases, containers, items) as distinct types
  • Use type state pattern for operations that require specific resource context
  • Lifetime parameters should be avoided in public APIs unless absolutely necessary

Partitioning

  • Make partition key handling explicit and type-safe
  • Provide clear guidance on partition key selection in documentation
  • Support both single and hierarchical partition keys

Query API

  • Provide a fluent, type-safe query builder
  • Support both SQL API and parameterized queries
  • Make continuation tokens transparent but accessible for pagination control

Code Organization

sdk/cosmos/azure_data_cosmos/
├── src/
│   ├── clients/           # Client types (CosmosClient, Database, Container)
│   ├── models/            # Data models (separate from behavior)
│   ├── operations/        # Operation implementations
│   ├── options/           # Request/Operation option builders
│   ├── query/             # Query builder and execution
│   └── responses/         # Response types with metadata

Azure Cosmos DB Driver Architecture

Multi-Crate Strategy

The Cosmos DB implementation is split across three crates with distinct purposes and support models:

azure_data_cosmos_driver (Core Implementation Layer)

  • Purpose: Core Cosmos DB protocol implementation, transport, routing, and retry handling
  • API: Public API available for advanced scenarios and cross-language SDK implementation
  • Support: Community/GitHub support only (no 24x7 Microsoft Support)
  • Versioning: Strict semantic versioning; can move to new major versions more frequently
  • Consumers: azure_data_cosmos (Rust SDK), potentially other language SDKs

azure_data_cosmos (Primary Rust SDK)

  • Purpose: Idiomatic Rust API for Cosmos DB with type-safe serialization
  • API: Full public SDK following Azure SDK Design Guidelines
  • Support: Full 24x7 Microsoft Support
  • Versioning: Strict semantic versioning; must maintain backward compatibility for years per major version
  • Dependency: Uses azure_data_cosmos_driver internally

Versioning Strategy: No Model Sharing

Avoid sharing models between crates to prevent version lock-in:

  • ❌ Do NOT create azure_data_cosmos_common for shared models
  • ❌ Do NOT directly expose azure_data_cosmos_driver models in azure_data_cosmos public API
  • ✅ DO keep models separate in each crate
  • ✅ DO use adapter functions/traits for conversion between crate models
  • ✅ DO duplicate model definitions when needed to maintain independent versioning

Rationale: azure_data_cosmos must maintain multi-year backward compatibility. If it exposes azure_data_cosmos_driver models directly, the driver cannot make breaking changes without forcing an SDK major version bump.

Exception: Truly stable types unlikely to change (e.g., fundamental constants) can be shared via workspace dependencies.

Schema-Agnostic Data Plane Principle

Critical Architectural Rule: azure_data_cosmos_driver is completely ignorant of document/item schemas.

Rationale:

  • Cosmos DB is a schemaless database - item structure is application-defined
  • Driver must support multiple language SDKs (Rust, Java, .NET, Python) each with native serialization patterns
  • Mapping items to application types is a core capability that must be handled natively in the consuming SDK

Where the wire encoding sits: schema-ignorance does not extend to the Cosmos binary wire encoding. That encoding is negotiated with the service by the driver, on a header the driver owns (x-ms-cosmos-supported-serialization-formats), and the query pipeline cannot do its job without understanding it — nodes such as the ordered merge and SkipTake split a feed envelope apart, re-order documents across pages, and reassemble a new envelope. That is impossible to do over opaque bytes. So the boundary is:

  • Driver owns the envelope and the wire encoding: negotiating it, parsing feed pages, and emitting pages in the negotiated encoding.
  • SDK owns the item: turning a document's bytes into an application type and back.

The driver never inspects or depends on the fields inside a document.

Driver Data Plane Contract:

// Driver APIs work with raw bytes (buffered, ≤16MB payload limit)
pub async fn create_item(
    &self,
    partition_key: &PartitionKey,
    item_body: &[u8],  // UTF-8 JSON or Cosmos binary encoding
    options: &CreateItemOptions,
) -> Result<ItemResponse> {
    // Driver handles transport, routing, retries
    // Driver is IGNORANT of item schema
}

pub struct ItemResponse {
    pub body: Vec<u8>,  // Raw response body (buffered)
    pub diagnostics: DiagnosticsContext,
    pub charge: f64,
    // ... metadata, no typed item
}

SDK Layer Responsibility (azure_data_cosmos):

// SDK provides type-safe serialization on top of driver
pub async fn create_item<T: Serialize>(
    &self,
    item: &T,
    options: &CreateItemOptions,
) -> azure_data_cosmos::Result<ItemResponse<T>>
where
    T: for<'de> Deserialize<'de>,
{
    let body = serde_json::to_vec(item)?;  // SDK handles serialization
    let raw_response = self.driver.create_item(pk, &body, options).await?;
    let typed_item: T = serde_json::from_slice(&raw_response.body)?;
    // SDK provides typed response
}

Content Encoding: a received body is recognized as UTF-8 JSON or Cosmos binary from its first byte, so decoding is transparent to the API. Do not extend that sniffing to decide what a pipeline node emits — the emitted encoding is a property of the operation (CosmosOperation::emits_binary_payload), not of the bytes that happened to arrive. Deriving emission from received bytes lets two nodes in the same pipeline disagree when the service answers in a format other than the one that was requested.

Note emits_binary_payload rather than negotiates_binary_response: the former is what the caller receives, the latter is what the wire carries. They diverge under BinaryEncodingOptions::request_text_response, where the wire stays binary but the driver hands back text — so a node keyed off the wire format would re-encode items that execute_plan immediately decodes again.

Implications:

  • Driver models/ contains zero item/document types
  • Driver APIs accept &[u8] for request bodies
  • Driver APIs return Vec<u8> for response bodies (buffered; Cosmos enforces 4MB default, 16MB absolute max)
  • Each consuming SDK implements its own serialization strategy

Module Organization Patterns

What Qualifies as a "Model"

Definition: A serializable data structure representing values transmitted over the wire to or from the Cosmos DB service (request/response body or header values).

Critical Requirement: All types in models/ must be serializable and used for wire format. Configuration types that control SDK behavior belong in options/.

Include in models/ module:

  • ✅ Management/metadata resource representations: Account properties, offers, database properties, container properties, partition key ranges (NOT data plane documents/items)
  • ✅ Supporting structures: Types that are properties of models (IndexingPolicy, PartitionKeyDefinition, VectorEmbeddingPolicy, consistency levels, indexing modes, connection modes)
  • ✅ Operation-specific envelopes: Structures created for operation support (TransactionalBatch, PatchInstructions, BulkOperations)
  • ✅ Header/wire values: Types serialized into request headers or body (ETag, SessionToken, PartitionKey, TriggerReference)

Exclude from models/ (use dedicated modules):

  • ❌ Configuration/options types → options/ module (e.g., Region for excluded regions, TriggerOptions for which triggers to use, ThroughputControlOptions)
  • ❌ Client types → clients/ module
  • ❌ Error types → error.rs or crate root
  • ❌ Diagnostics/telemetry types → diagnostics/ module
  • ❌ Pipeline/routing/internal types → separate modules with pub(crate) visibility
  • ❌ Data plane items/documents → NOT in driver (see Schema-Agnostic Data Plane above)
  • ❌ Builders → builders/ submodule or inline in models/builders/

Model Characteristics:

/// Models should be serializable and debuggable
#[derive(Clone, Default, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[non_exhaustive]  // For forward compatibility
pub struct ContainerProperties {
    pub id: Cow<'static, str>,
    pub partition_key: PartitionKeyDefinition,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub indexing_policy: Option<IndexingPolicy>,
}

impl ContainerProperties {
    // Models CAN have methods (builders, accessors, conversions)
    pub fn builder() -> ContainerPropertiesBuilder {
        ContainerPropertiesBuilder::default()
    }
}

Diagnostics Module Pattern

Cosmos DB provides rich diagnostic information beyond standard Azure SDK telemetry. This operational metadata lives in a dedicated diagnostics/ module:

Structure:

src/diagnostics/
  ├── mod.rs              # Public exports
  ├── context.rs          # DiagnosticsContext (Rust equivalent of CosmosDiagnosticsContext)
  ├── metrics.rs          # Request metrics, RU consumption, timings
  └── regions.rs          # Regional endpoint contact info

Rationale: Diagnostics types are NOT service resources - they are SDK operational metadata. Keeping them separate from models/ maintains clean separation between:

  • Models: What Cosmos DB service defines (resources, policies, envelopes)
  • Diagnostics: What the SDK tracks about operations (timings, retries, endpoints contacted)

Example:

/// Context containing diagnostic information about a Cosmos DB operation
pub struct DiagnosticsContext {
    pub request_charge: f64,
    pub regions_contacted: Vec<RegionContactInfo>,
    pub retry_attempts: Vec<RetryInfo>,
    pub request_timeline: RequestTimeline,
}

// Attached to operation results, not stored in models
pub struct ItemResponse {
    pub body: Vec<u8>,  // Raw bytes, schema-agnostic
    pub diagnostics: DiagnosticsContext,
}

Options Module Pattern

Configuration types for individual operations/requests follow Azure SDK conventions in dedicated options/ module:

Structure:

src/options/
  ├── mod.rs          # Public exports
  ├── client.rs       # {Crate}Options (client-level config)
  ├── connection.rs   # ConnectionPoolOptions, environment config
  └── operations.rs   # {Operation}Options (per-request config)

Naming Convention:

  • Client-level: DriverOptions, CosmosClientOptions
  • Operation-level: {Operation}{Action}Options (e.g., ExecuteQueryOptions, CreateContainerOptions)
  • Domain-specific: Descriptive names (ConnectionPoolOptions, RetryOptions)

Hierarchy (Environment → Client → Operation):

// Client options nest azure_core types
pub struct DriverOptions {
    pub client_options: ClientOptions,  // From azure_core
    pub connection_pool: ConnectionPoolOptions,
    pub default_consistency: Option<ConsistencyLevel>,
}

// Operation options can override client defaults
pub struct ExecuteQueryOptions<'a> {
    pub method_options: ClientMethodOptions<'a>,  // For tracing context
    pub consistency: Option<ConsistencyLevel>,    // Override client default
    pub max_item_count: Option<i32>,
}

Builder Pattern

Use builders for complex client construction:

pub mod builders {
    pub struct DriverBuilder {
        connection_pool: Option<ConnectionPoolOptions>,
        retry: Option<RetryOptions>,
    }

    impl DriverBuilder {
        pub fn new() -> Self { Self::default() }

        pub fn with_connection_pool(mut self, opts: ConnectionPoolOptions) -> Self {
            self.connection_pool = Some(opts);
            self
        }

        pub async fn build(
            self,
            endpoint: impl Into<String>,
            credential: impl TokenCredential,
            options: DriverOptions,
        ) -> azure_data_cosmos_driver::error::Result<Driver> {
            // ... construction logic
        }
    }
}

// Usage:
let driver = Driver::builder()
    .with_connection_pool(pool_opts)
    .build(endpoint, credential, opts).await?;

Testing

  • Use #[recorded::test] for integration tests that interact with Cosmos DB
  • Unit tests should focus on:
    • Request building logic
    • Response parsing
    • Error handling paths
    • AVOID writing tests for trivial or obvious functionality, like derived trait behavior, constructor behavior that just assigns fields, or simple getters/setters.
  • Integration tests should cover:
    • CRUD operations
    • Query scenarios
    • Partition key handling
    • Continuation token pagination
  • Tests should use standard trait implementations (e.g., .parse::<T>() instead of calling T::from_str() directly)
  • Use assert! for boolean assertions instead of assert_eq!(value, true/false)
  • Prefer the strongest assertion that matches the contract: Use assert_eq!(vec, expected_vec) instead of assert!(vec.contains(x)) — the former catches both missing and extra elements. In general, assert exact values over partial properties.

Code Quality and Validation

Automatic Formatting (CRITICAL)

ALWAYS run cargo fmt FIRST before any other validation steps. The CI pipeline will fail if code is not properly formatted:

# Format the modified crate
cargo fmt -p <crate-name>

# Check if formatting is correct without modifying files
cargo fmt -p <crate-name> -- --check

This is the most common CI failure - always run cargo fmt after making ANY code changes, including:

  • Adding new code
  • Modifying existing code
  • Moving code between files
  • Refactoring

Automatic Clippy Validation

ALWAYS run cargo clippy after making code changes to catch common issues and ensure code quality:

# Run clippy for the modified crate
cargo clippy -p <crate-name> --all-features --all-targets

# Run clippy for the entire workspace (use sparingly)
cargo clippy --workspace --all-features --all-targets --keep-going --no-deps

Fix all clippy warnings before considering code complete. Common patterns to watch for:

  • ✅ Implement standard traits instead of custom methods (clippy::should_implement_trait)
  • ✅ Use assert!(value) instead of assert_eq!(value, true)
  • ✅ Use assert!(!value) instead of assert_eq!(value, false)
  • ✅ Avoid unnecessary clones, allocations, or copies
  • ✅ Use idiomatic patterns (e.g., if let, pattern matching)

When to suppress warnings: Only use #[allow(clippy::...)] when:

  • The warning is a false positive
  • Following the suggestion would make code less clear
  • The pattern is required for FFI or external constraints

Always add a comment explaining why the warning is allowed.

Code Formatting

  • Always run cargo fmt on generated or modified Rust code before considering the task complete.
  • When editing existing files, ensure the changes conform to rustfmt standards.
  • The CI pipeline will reject any code that is not properly formatted.
  • When editing non-Rust files (JSON, YAML, TOML, etc.), respect the root .editorconfig settings. For example, JSON files use 2-space indentation. Do not reformat entire files when making small changes — preserve the existing formatting.

Spell Checking

Run cspell to check for spelling errors in changed files (requires Node.js):

# Check spelling in files changed compared to upstream/main
pwsh eng/common/scripts/check-spelling-in-changed-files.ps1 -TargetCommittish "upstream/main"

To fix spelling errors, either correct typos in source code or add legitimate terms to the appropriate spelling configuration:

  • Crate names: eng/dict/crates.txt
  • Cosmos-specific words not in the dictionary: add them to ignoreWords in sdk/cosmos/.cspell.json. Do not add Cosmos-specific words to the global .vscode/cspell.json; changing the global file triggers reviews from the central SDK reviewers.
  • Terms shared across SDKs: .vscode/cspell.json

Pre-Completion Validation Checklist

Before considering any task complete, run the following checks in order on all modified crates:

  1. Format check (MUST BE FIRST): cargo fmt -p <crate-name>
    • This is the #1 cause of CI failures - always run this first!
  2. Build check: cargo build -p <crate-name>
  3. Clippy lint check: cargo clippy -p <crate-name> --all-features --all-targets
  4. Documentation check: cargo doc -p <crate-name> --no-deps --all-features
    • This catches broken intra-doc links (e.g., referencing non-existent methods in [backtick links])
    • All documentation warnings must be resolved before completing the task
  5. Test check (if tests exist): cargo test -p <crate-name> --all-features
    • Tests gated by test_category (e.g., emulator tests) are always compiled but are ignored at runtime unless the corresponding cfg is set via RUSTFLAGS.
    • To actually run emulator tests: RUSTFLAGS='--cfg test_category="emulator"' cargo test -p <crate-name> --tests
    • To run against the new vnext (Linux) emulator instead, point at a vnext container and use the emulator_vnext cfg:

      # Start the vnext emulator in Docker (HTTP mode):
      docker run -d --name cosmosdb-emulator-vnext \
          -p 8081:8081 -p 8080:8080 \
          -e ENABLE_EXPLORER=false \
          mcr.microsoft.com/cosmosdb/linux/azure-cosmos-emulator:vnext-preview
      
      # Point the test framework at it and select the vnext-compatible test set:
      export AZURE_COSMOS_CONNECTION_STRING='AccountEndpoint=http://localhost:8081;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==;'
      RUSTFLAGS='--cfg test_category="emulator_vnext"' \
          cargo test -p azure_data_cosmos_driver --test emulator -- --test-threads=1

      A subset of emulator tests (backup-endpoint fallback, partition failover) are intentionally not gated on "emulator_vnext" because they rely on multi-endpoint topology the vnext gateway does not model. See the per-file module headers for the exclusion rationale.

Common documentation link errors to avoid:

  • When documenting factory methods or APIs, ensure the linked method names match the actual implementation
  • Use the exact method name (e.g., method_by_name not just method) in doc links like [StructName::method_by_name]

Copyright Header

All Rust source files in the sdk/cosmos directory must begin with the following copyright header:

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

This header must be:

  • The first two lines of every .rs file
  • Followed by a blank line before any other content (imports, module docs, etc.)
  • Exactly as shown above (no variations in formatting or text)

Example:

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

//! Module documentation starts here.

use std::time::Duration;

Performance Considerations

  • Minimize allocations in hot paths
  • Use Cow<'_, str> for fields that might be borrowed or owned
  • Stream large responses rather than buffering entirely
  • Provide options for controlling request throughput (RU/s limits)

Documentation

Every public API should document:

  • Purpose: What the API does
  • Example: Runnable code showing typical usage
  • Errors: What errors can be returned and why
  • Performance: RU/s implications, if relevant
  • Partition Key: Whether the operation is partition-scoped

Source comment references

  • Rust source comments and rendered Rustdoc must not contain direct paths or links to Markdown files. Avoid stale pseudo-links such as repository-relative paths written as text or Markdown links.
  • include_str! may embed Markdown in a doc attribute, including crate READMEs and shared Rustdoc fragments. The compiler validates these paths, and the path is not rendered in the generated documentation.
  • Public doc comments must not reference Cosmos specs or ADRs.
  • Internal doc comments on private or pub(crate) items may reference a spec or ADR only by its number and title, for example, "Spec 0017: Patch handler" or "ADR 0005: Flat native ABI data model."
  • Non-doc comments may use the same number-and-title form.
  • Put detailed rationale in docs/; use docs/README.md to locate it rather than embedding a file path in Rust source.

Comment Brevity (IMPORTANT)

Keep comments short and dense. Long, chatty comment blocks are noise: they age badly, bury the signal, and read as filler.

  • Inline comments: 1–2 lines. 3 is the hard ceiling. Explain why, never what the code already says.
  • Doc comments: lead with a one-sentence summary. Add detail only when a caller genuinely needs it to use the API correctly.
  • Test comments: usually unnecessary — a good test name plus a clear assertion message says it. Comment only a non-obvious setup or a subtle invariant.
  • Never restate an argument three ways, narrate the diff ("this used to be X, now it's Y"), re-explain something already covered by a nearby doc comment, or write a mini design doc inline. Refer to an applicable spec by number and title only when the source comment rules above allow it.
  • When a rationale really needs paragraphs, it belongs in docs/, not in the source.
// ❌ BAD: five lines to say one thing
// This is a client-side re-encode of a payload the driver itself produced,
// not a malformed service envelope, so it is a serialization fault (not the
// 500 `SERVICE_ORDER_BY_ENVELOPE_INVALID`). Include the item ordinal to
// correlate on a large page.

// ✅ GOOD
// Client-side re-encode failure, not a malformed service envelope.

Additional Resources

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

Reviews can't be read right now.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.