Skip to content

Commit 449bce7

Browse files
authored
Add copilot instructions (#355)
Add some rudimentary copilot instructions.
1 parent a3eb918 commit 449bce7

File tree

3 files changed

+348
-0
lines changed

3 files changed

+348
-0
lines changed

.github/copilot-instructions.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
This document provides explicit instructions for Copilot Agent to follow when working with this
2+
repository. It outlines the coding standards, project structure, and best practices to ensure
3+
high-quality contributions that align with the repository's goals and maintainability standards.
4+
5+
## Repository Overview
6+
7+
This is the Bitwarden SDK - a cross-platform library written in Rust that implements core business
8+
logic for the Bitwarden applications.
9+
10+
The repository is organized as a **monorepo** containing multiple libraries (`crates`) located under
11+
`bitwarden_license` and `crates`. Some notable crates include:
12+
13+
- **`bitwarden-core`** - Contains the underlying functionality of the SDK. This includes a Client
14+
struct. Other crates in the SDK depend on bitwarden-core and provide extensions to the Client
15+
struct to implement specific domains. **Avoid adding functionality to this crate and prefer to
16+
implement it in feature crates.**
17+
- **`bitwarden-crypto`** - Cryptographic primitives and key management
18+
- **`bitwarden-api-*`** - Auto-generated API bindings (DO NOT edit manually)
19+
- **`bitwarden-uniffi`** - Mobile bindings wrapper
20+
- **`bitwarden-wasm-internal`** - WebAssembly bindings
21+
22+
## Feature Flags
23+
24+
The SDK uses the following rust feature flags for language bindings.
25+
26+
- `uniffi` - Mobile bindings (Swift/Kotlin) via UniFFI
27+
- `wasm` - WebAssembly bindings with wasm-bindgen
28+
29+
### Core Architecture
30+
31+
The Client struct is the main entry point for the SDK and represents a single account instance. Any
32+
action that needs to be performed on the account is generally done through the Client struct. This
33+
allows the internals to be hidden from the consumer and provides a clear API.
34+
35+
We can extend the Client struct using extension traits in feature crates. This allow the underlying
36+
implementation to be internal to the crate with only the public API exposed through the Client
37+
struct. Below is an example of a generator extension for the Client struct.
38+
39+
Crates usually contains one or multiple `<domain>_client.rs` files which contains a Client struct
40+
that implements the core business logic for the domain.
41+
42+
```rust
43+
/// Generator extension for the Client struct
44+
#[cfg_attr(feature = "wasm", wasm_bindgen)]
45+
pub struct GeneratorClient {
46+
client: Client,
47+
}
48+
49+
#[cfg_attr(feature = "wasm", wasm_bindgen)]
50+
impl GeneratorClient {
51+
fn new(client: &'a Client) -> Self {
52+
Self { client }
53+
}
54+
55+
/// Generates a password based on the provided request.
56+
pub fn password(&self, input: PasswordGeneratorRequest) -> Result<String, PasswordError> {
57+
password(input)
58+
}
59+
60+
}
61+
62+
// Extension which exposes `generator` method on the `Client` struct.
63+
pub trait GeneratorClientExt {
64+
fn generator(self) -> GeneratorClient;
65+
}
66+
67+
impl GeneratorClientExt for Client {
68+
fn generator(self) -> GeneratorClient {
69+
GeneratorClient::new(self)
70+
}
71+
}
72+
```
73+
74+
### Cross-Platform Bindings
75+
76+
#### UniFFI
77+
78+
UniFFI is used for mobile bindings (Swift/Kotlin). Crates must include the following in `lib.rs` to
79+
enable UniFFI support.
80+
81+
```rust
82+
#[cfg(feature = "uniffi")]
83+
uniffi::setup_scaffolding!();
84+
```
85+
86+
Structs and enums that are exposed through a domain client must be derive:
87+
88+
- Structs: `#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]`.
89+
- Enums: `#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]`.
90+
91+
#### WASM
92+
93+
WASM is used for web bindings. Structs and enums that are exposed through a domain client should
94+
derive tsify.
95+
96+
```rust
97+
// Add import to top of file
98+
#[cfg(feature = "wasm")]
99+
use {tsify::Tsify, wasm_bindgen::prelude::*};
100+
101+
#[derive(Serialize, Deserialize)]
102+
#[cfg_attr(feature = "wasm", derive(Tsify), tsify(into_wasm_abi, from_wasm_abi))]
103+
pub struct ExampleStruct {
104+
// Fields here
105+
}
106+
```
107+
108+
## Development Workflow
109+
110+
### Building & Testing
111+
112+
Use the following commands to build and test the SDK:
113+
114+
- `cargo check --all-features --all-targets` to quickly verify code is valid.
115+
- `cargo test --workspace --all-features` to run tests
116+
117+
### Formatting & Linting
118+
119+
Before committing code, ensure it is formatted and linted:
120+
121+
- `cargo +nightly fmt --workspace` for formatting
122+
- `cargo clippy --workspace` for linting
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
---
2+
applyTo: "**/Cargo.toml"
3+
---
4+
5+
# Project coding standards for Cargo.toml
6+
7+
## Workspace Dependency Management
8+
9+
### External Dependencies
10+
11+
When adding a new dependency to a `Cargo.toml`, you **MUST** scan all existing `Cargo.toml` files in
12+
the repository to ensure it is not already defined in another crate. If it is, you should add it to
13+
the workspace dependencies and remove it from the individual crate.
14+
15+
```toml
16+
# ✅ DO: Define in root workspace
17+
[workspace.dependencies]
18+
serde = { version = ">=1.0, <2.0", features = ["derive"] }
19+
tokio = { version = "1.36.0", features = ["macros"] }
20+
```
21+
22+
```toml
23+
# ❌ DON'T: Define different versions in individual crates
24+
[dependencies]
25+
serde = "1.0.150" # Different version than workspace
26+
```
27+
28+
### Internal Dependencies
29+
30+
All internal crates must be listed in workspace dependencies with exact version matching:
31+
32+
```toml
33+
[workspace.dependencies]
34+
bitwarden-core = { path = "crates/bitwarden-core", version = "=1.0.0" }
35+
bitwarden-crypto = { path = "crates/bitwarden-crypto", version = "=1.0.0" }
36+
```
37+
38+
### Using Workspace Dependencies
39+
40+
In individual crate `Cargo.toml` files, always reference workspace dependencies:
41+
42+
```toml
43+
[dependencies]
44+
# ✅ DO: Use workspace dependencies
45+
serde = { workspace = true }
46+
bitwarden-crypto = { workspace = true }
47+
48+
# ✅ OK: Add features to workspace dependency
49+
serde = { workspace = true, features = ["derive"] }
50+
51+
# ❌ DON'T: Override workspace version
52+
serde = "1.0.200"
53+
```
54+
55+
## Package Metadata
56+
57+
### Required Workspace Inheritance
58+
59+
All crates must inherit common metadata from workspace:
60+
61+
```toml
62+
[package]
63+
name = "bitwarden-example"
64+
description = "Brief description of the crate's purpose"
65+
66+
# Required workspace inheritance
67+
version.workspace = true
68+
authors.workspace = true
69+
edition.workspace = true
70+
rust-version.workspace = true
71+
readme.workspace = true
72+
homepage.workspace = true
73+
repository.workspace = true
74+
license-file.workspace = true
75+
keywords.workspace = true
76+
```
77+
78+
## Feature Flags
79+
80+
### Standard Feature Patterns
81+
82+
Follow established feature flag patterns:
83+
84+
- `uniffi` - Mobile bindings via UniFFI
85+
- `wasm` - WebAssembly support
86+
87+
### Feature Flag Dependencies
88+
89+
Use conditional dependencies with feature flags:
90+
91+
```toml
92+
[features]
93+
uniffi = ["bitwarden-crypto/uniffi", "dep:uniffi"]
94+
wasm = ["dep:wasm-bindgen", "dep:tsify"]
95+
96+
[dependencies]
97+
# Conditional dependencies
98+
uniffi = { workspace = true, optional = true }
99+
wasm-bindgen = { workspace = true, optional = true }
100+
```
101+
102+
## Version Constraints
103+
104+
Use compatible version ranges for external dependencies:
105+
106+
```toml
107+
# ✅ DO: Allow patch updates
108+
serde = ">=1.0, <2.0"
109+
chrono = ">=0.4.26, <0.5"
110+
111+
# ❌ DON'T: Pin to exact external versions unless necessary
112+
serde = "=1.0.150"
113+
```
114+
115+
## Special Considerations
116+
117+
### Patches
118+
119+
Document any `[patch.crates-io]` entries with reasoning:
120+
121+
```toml
122+
# Temporary fix for WASM compatibility issue
123+
# TODO: Remove when upstream releases fix
124+
[patch.crates-io]
125+
pkcs5 = { git = "https://github.com/bitwarden/rustcrypto-formats.git", rev = "abc123" }
126+
```
127+
128+
### Profile Optimizations
129+
130+
Development profiles are configured at workspace level - do not override in individual crates unless
131+
absolutely necessary.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
applyTo: "**/*.rs"
3+
---
4+
5+
# Project coding standards for Rust
6+
7+
## Formatting and Linting
8+
9+
- Use `cargo +nightly fmt` to format code
10+
- Use `cargo clippy` to lint code and catch common mistakes
11+
- All code must be formatted and linted before being committed
12+
13+
## Error Handling
14+
15+
### Avoid Panics
16+
17+
- **Never use `unwrap()`** - `clippy` will forbid this
18+
- **Use `expect()` sparingly** - always provide a helpful message
19+
- **Prefer `?` operator** for error propagation
20+
21+
```rust
22+
// ❌ DON'T: Use unwrap or expect without context
23+
let value = result.unwrap();
24+
25+
// ✅ DO: Use ? for error propagation
26+
let value = result?;
27+
28+
// ✅ DO: Use expect with a helpful message
29+
let value = result.expect("Config is validated at startup, this should never fail");
30+
```
31+
32+
### Result and Option Handling
33+
34+
Avoid using `match` for simple cases. Use the below methods if it makes the code cleaner.
35+
36+
```rust
37+
// Use ? for error propagation
38+
do_something().map_err(|e| "Another error")?
39+
40+
// Use if let for single arm matches
41+
if let Ok(value) = result {
42+
outer.append(value);
43+
}
44+
45+
// Use Option methods
46+
some_option.map(|x| x + 1)
47+
some_option.and_then(|x| func(x + 1))
48+
some_option.unwrap_or(1)
49+
some_option.ok_or("error")
50+
51+
// Use Result methods
52+
result.map_err(|_| "Another error")
53+
```
54+
55+
## Naming Conventions
56+
57+
- Use `snake_case` for functions, variables, and modules
58+
- Use `PascalCase` for types, traits, and enum variants
59+
- Use `SCREAMING_SNAKE_CASE` for constants
60+
- Use descriptive names and avoid abbreviations
61+
62+
## Documentation
63+
64+
- All public APIs must have doc comments using `///`
65+
- Include examples for complex functions
66+
- Use `//` for internal comments that explain "why", not "what"
67+
68+
## Memory Management
69+
70+
- Use references (`&`) instead of owned values when possible
71+
- Use `&str` for string parameters when you don't need ownership
72+
- Use `String` for owned strings and return values
73+
- Pre-allocate collections with known sizes: `Vec::with_capacity()`
74+
75+
## Error Types
76+
77+
- Define domain-specific error types
78+
- Use `thiserror` crate for error derivation
79+
80+
## Testing
81+
82+
- Place unit tests in the same file using `#[cfg(test)]`
83+
- Use descriptive test names that describe the scenario
84+
- Follow the Arrange-Act-Assert pattern
85+
86+
## Security
87+
88+
- Always validate input parameters
89+
- Use constant-time operations for cryptographic comparisons
90+
91+
## Additional Guidelines
92+
93+
- Keep functions focused and single-purpose
94+
- Group imports: std library, external crates, local modules
95+
- Mark functions as `async` only when they perform async operations

0 commit comments

Comments
 (0)