sdk/cosmos/.github/skills/cosmos-design-struct/SKILL.md
Enforce consistent struct design conventions across sdk/cosmos crates. Validates visibility modifiers, field privacy, #[non_exhaustive] usage, and construction API patterns (`Default`/`new` with `with_*` setters, or optional separate builders with `builder()`/`build()`), and construction correctness on public structs. Can auto-fix violations or report them as errors.
npx skillsauth add azure/azure-sdk-for-rust cosmos-design-structInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use this skill when:
sdk/cosmos/**Follow these steps strictly:
scope argument is specified and is not equal (case-insensitive) to all or *, set the target path to sdk/cosmos/<scope> (for example, if scope is azure_data_cosmos, use sdk/cosmos/azure_data_cosmos as the target path).sdk/cosmos.tests/ directories in the validation scope (e.g., sdk/cosmos/azure_data_cosmos/tests/).changed-only is true (the default), restrict scanning to .rs files that differ between the current local branch and main. Use git diff --name-only main -- <target path> (and include per-crate tests/ directories) to obtain the list. Only .rs files in the result set are scanned; all other files are skipped.changed-only is false, scan all .rs files under the target path(s).generated/ subdirectories — these are produced by external tools and must never be modified.struct declarations in the .rs files identified in Step 2.Classify every struct into exactly one of these categories:
pub and all ancestor modules up to the crate root are also pub (the struct is reachable from outside the crate). These structs get the full set of rules: getter coverage for externally-readable fields, explicit field-visibility decisions based on invariants/validation needs, and a construction pattern that matches the required/optional field mix:Default when there are no logically required fields.new(required...) when the struct has both required and optional fields.Default nor new(required...) is required by this skill.with_* methods only for optional fields so callers can conveniently adjust a small subset of options.with_* methods.with_* methods.builder()/build() + with_*) is optional, but if not used, the target struct itself must provide fluent with_* methods for optional fields when they exist.Effectively scoped — The struct has a pub visibility modifier but lives inside a module that restricts visibility (e.g., pub(crate) mod, pub(super) mod). The struct is not reachable from outside the crate. These structs should:
pub(crate) struct Foo not pub struct Foo inside a pub(crate) mod).#[non_exhaustive] — it is unnecessary since external code cannot reach the struct.pub without further restriction — the struct-level visibility already limits access, and repeating pub(crate) or pub(super) on every field adds noise without benefit.Internal — The struct is used only within its declaring module or submodule. These structs should:
pub(super) for parent module access, pub(crate) for crate-wide access).#[non_exhaustive].For all structs regardless of category:
| Usage scope | Struct visibility |
|---|---|
| Only within the declaring module | No visibility modifier (private) |
| Within the parent module | pub(super) |
| Within the crate | pub(crate) |
| Outside the crate | pub |
Additional rules:
pub but lives inside a non-public module (e.g., pub struct Foo inside pub(crate) mod internal), change the struct to use the effective visibility: pub(crate) struct Foo. This makes the actual visibility obvious at the struct declaration site without requiring the reader to trace module ancestry.pub — the struct-level visibility already constrains access. This is an intentional choice to reduce repetitive pub(crate) or pub(super) annotations on fields while still making the effective visibility clear and easy to review from the struct declaration alone.These rules apply only to structs classified as truly public in Step 3:
#[non_exhaustive] required only for all-public-field truly public structsFor truly public structs, require #[non_exhaustive] when all named fields are public. This prevents external code from constructing the struct with literal syntax, ensuring forward compatibility when fields are added in future versions.
If one or more fields are non-public, #[non_exhaustive] is optional and typically redundant for construction control.
Before making a field non-public solely to validate it in a with_* setter or constructor, evaluate whether the type system can enforce the invariant instead. Newtypes, enums, and other constrained wrapper types make invalid states unrepresentable at compile time, which is stronger and more ergonomic than runtime checks.
Decision order (prefer earlier options):
ConsistencyLevel, IndexingMode), use an enum. Invalid variants simply cannot be expressed.new() / From and guarantees the invariant internally (e.g., RegionName normalizes casing on construction; SubStatusCode wraps a raw number with parse validation).String as DatabaseId to avoid mixing up identifiers).with_*/constructor — Use this as a last resort, only when type-level enforcement is impractical (e.g., cross-field invariants that span multiple values, constraints that depend on external state, or cases where introducing a new type would create excessive API friction for negligible safety gain).When a newtype is introduced, the field holding it can often remain pub because the invariant is encoded in the type itself — external code cannot construct an invalid value regardless of field visibility. See subsection (g) for newtype struct conventions.
Rule of thumb: If a
with_*setter contains a.clamp(), range check, format validation, or normalization, that logic almost certainly belongs in a newtype's constructor instead.
Fields on truly public structs may be pub or non-public. Choose visibility by checking (1) whether validation/invariant enforcement is needed and (2) whether crate-internal code in other modules needs direct non-getter access:
| Scenario | Field visibility |
|---|---|
| Field has no validation/invariant constraints and direct access is acceptable for external consumers | pub is allowed. with_* setters and direct field modification may coexist. |
| Field requires validation/invariant enforcement (e.g., value constraints, normalization, coupled-field invariants) | Non-public (private by default; pub(crate) only when justified by crate-internal usage). Route external mutation through with_*/constructor APIs. |
| Field is non-public and crate-internal code in other modules only reads it | Prefer private plus getter usage at call sites. |
| Field is non-public and crate-internal code outside the defining module requires non-getter semantics (mutation, move/consume, mutable references, nested writes) | pub(crate) (or pub(super) when parent-only). |
Rationale: Public fields maximize ergonomics and avoid forced ownership extractors for simple data. Non-public fields keep API evolution and validation logic controllable where invariants matter.
How to determine the correct visibility:
pub is acceptable.pub(crate)/pub(super).pub is acceptable.Every field that external consumers need to read must have a getter method:
fn session_token(&self) -> &str)&T for non-Copy types, or T for Copy types (e.g., bool, u32, f64)For truly public structs, construction APIs must be ergonomic without over-engineering. The baseline constructor and optional setters must follow this contract:
Default.new(required...) and do not implement Default.new is optional; Default is not required).with_* fluent setters for optional fields when optional fields exist and the struct has more than one field.fn with_xxx(mut self, value: T) -> Self.with_* setters are not required.with_* setters are not required.This can be realized in either of the following styles:
Default + with_*.new(required...) + with_*.new(required...) (both acceptable).Type::builder() returning <Type>Builder.with_* setters on the builder.build(...) (required parameters on build() when applicable).Default on the target struct.For with_* setters in either style (on the target type or on a builder), use the same fluent signature: take mut self and return Self.
Separate builder types are still optional. Fluent with_* support is required only when optional fields are present and the struct has more than one field.
A required field is one that must be set for the struct to be semantically valid.
new(required...), and with_* setters are for optional overrides.build(required...), and with_* setters are for optional overrides.Default; use new(required...) (or build(required...)) plus optional with_*.new(required...); keep the simplest API that fits the crate.When inferring required fields on existing structs, use docs, service behavior, and call-site usage patterns.
Newtype structs are exempt from the named-field construction rules. Since a newtype wraps a single value, the full named-field struct rules do not apply. Instead, newtypes should:
new(), From impls, or associated constants.value()) or Into/AsRef impls.#[non_exhaustive].Builder types (*Builder structs), when present, are exempt from #[non_exhaustive] and getters.
Serde derive macros (Serialize, Deserialize) work on private fields — no pub(crate) is needed for serde compatibility.
If a separate builder type is used, follow these conventions:
<Type>Builder.with_* setters for optional fields.build(self, ...) -> <Type> (or azure_core::Result<Type> when fallible).build(...), not as optional builder state.<Type>::builder(... required args ...) -> <Type>Builder to initialize the builder type.These conventions apply only when a builder exists; they are not a requirement to introduce one.
auto-fix is true#[non_exhaustive] to truly public structs where all named fields are public and the attribute is missing.#[non_exhaustive] from non-public structs that have it unnecessarily.pub fields to non-public:pub is allowed.with_* APIs as needed.From/Into trait-based conversion; add targeted into_* methods only when a trait-based API is not a good fit.new(required...) or build(required...)) and remove/avoid Default in that case.
b. If no ergonomic construction API exists, add the simplest valid option:
- all-optional/simple types: Default + with_* on the target type.
new(required...) + with_*, and remove/avoid Default.new(required...) and no forced with_*.with_*.
cargo fmt -p <crate> after changes.cargo clippy -p <crate> --all-features --all-targets and fix any resulting warnings.cargo build -p <crate> to confirm changes compile.auto-fix is falseEmit a structured report listing every violation:
## Violations
### <crate_name>
#### <file_path>:<line_number> — `StructName`
- **Category**: Truly public | Effectively scoped | Internal
- **Rule violated**: <rule description>
- **Current**: <current code snippet>
- **Proposed**: <proposed fix>
Regardless of the auto-fix setting, always produce a final summary:
List all changes (applied or proposed) that affect truly public structs, grouped by crate and module:
#[non_exhaustive]pub to private<Type>Builder) (if applied)builder() factory method added (if applied)Default/new/with_* construction APIs added or adjustedHighlight any change that constitutes a semver breaking change with:
⚠️ BREAKING CHANGE: `StructName::field_name` was `pub` and is now private.
External code using `instance.field_name` must change to `instance.field_name()` (getter).
External code constructing via struct literal must change to non-literal construction APIs.
Breaking changes include:
pub field becoming private (consumers using direct field access will break)#[non_exhaustive] (consumers using struct literal construction will break)Default, new, with_*, builder, or build)builder() and a builder type is typically additive and non-breakinggenerated/ subdirectories.#[non_exhaustive] is only applicable to truly public structs; effectively-scoped and internal structs must omit it.#[non_exhaustive] when all named fields are public. If a struct has non-public fields, #[non_exhaustive] is optional and usually redundant for construction control.pub without additional restriction — the struct-level visibility already limits access, and repeating pub(crate) on every field is unnecessary noise.Serialize, Deserialize) work on private fields — no pub(crate) is needed for serde compatibility.with_* prefix per Azure SDK Rust guidelines (not bare field names).with_* support is required only when optional fields exist and the struct has more than one field. Required-only and single-property structs do not need with_* methods.new(...)/build(...) is required when required and optional fields coexist. For required-only structs, this skill does not require new(...).with_*, and place required params on build().From/Into traits first. Use targeted into_* methods as an exception when extracting a specific owned field without cloning is clearer than trait conversion.sdk/cosmos/AGENTS.md for canonical model, options, and builder patterns.Default + with_*; mixed required+optional: new + with_*; required-only: simplest API, optional builder). Also evaluate whether any constrained fields warrant a newtype or enum rather than a bare primitive type (see Step 6b).Option type with no semantically valid zero value.with_* setters or constructors. Setter-level validation (clamping, range checks, format normalization) is a last resort for invariants that cannot be practically expressed in the type system (see Step 6b).development
Run pre-commit checks for a specific set of crates. Use this when validating changes under sdk/cosmos before committing or during code review.
tools
Update the TypeSpec emitter for Rust and optionally regenerate all clients
tools
Check and fix formatting and other issues in markdown files using markdownlint-cli2.
development
Create a new Azure SDK crate from a TypeSpec specification.