Skip to content

Commit 7093e55

Browse files
authored
[PM-33733] Reorganize SDK documentation (#793)
Refresh of the SDK architecture documentation. Reorganizing it into new sections and expands identified gaps.
1 parent 198dbd9 commit 7093e55

13 files changed

Lines changed: 828 additions & 309 deletions

custom-words.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ OTLP
5757
owasp
5858
passcode
5959
passwordless
60+
passthroughs
6061
pinentry
6162
PNSs
6263
POSIX
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
---
2+
sidebar_position: 1
3+
---
4+
5+
# Adding new functionality
6+
7+
This guide walks through the steps for adding new functionality to the SDK. For guidance on whether
8+
something belongs in the SDK at all, see
9+
[What belongs in the SDK](index.md#what-belongs-in-the-sdk).
10+
11+
The examples below use `FoldersClient` from `bitwarden-vault` as a reference. For guidance on how to
12+
organize the files within a client, see [Client patterns](client-patterns.md).
13+
14+
## 1. Readiness checklist
15+
16+
Before creating a new crate or adding to an existing one, work through these questions to identify
17+
any blockers or prerequisites.
18+
19+
- **Does the functionality depend on other code that is not yet in the SDK?** Moving it is not
20+
recommended until those dependencies are available. Consider asking the team that owns the
21+
upstream code to migrate it first.
22+
23+
- **Does the functionality require authenticated API requests?** The SDK supports authenticated
24+
requests through autogenerated bindings. See [`bitwarden-vault`][vault-crate] as an example of a
25+
crate that makes authenticated calls.
26+
27+
- **Does the functionality require persistent state?** Review the docs for
28+
[`bitwarden-state`][state-crate] and see [`bitwarden-vault`][vault-crate] for an example of how
29+
state is managed.
30+
31+
- **Does the functionality need the SDK to produce an observable or reactive value?** Migrate the
32+
business logic to the SDK and build reactivity on top of it in TypeScript.
33+
34+
## 2. Create the crate
35+
36+
When the functionality warrants its own crate — typically when it represents a distinct domain — add
37+
a new crate under the `crates/` directory in the
38+
[SDK repository](https://github.com/bitwarden/sdk-internal).
39+
40+
1. Create the crate with `cargo init` and add it to the workspace `Cargo.toml`.
41+
2. Add `bitwarden-core` as a dependency for the shared runtime.
42+
3. Configure `CODEOWNERS` to ensure the appropriate team is assigned to review changes to the crate.
43+
44+
## 3. Define the client struct
45+
46+
Each feature crate exposes one or more client structs that group related operations. Create a struct
47+
that holds the dependencies the client needs and use the `#[derive(FromClient)]` macro to
48+
automatically populate fields from the SDK `Client`. See
49+
[Client patterns — `FromClient` and dependency injection](client-patterns.md#fromclient-and-dependency-injection)
50+
for details on how this works and which dependency types are available.
51+
52+
```rust
53+
#[derive(FromClient)]
54+
pub struct FoldersClient {
55+
pub(crate) key_store: KeyStore<KeyIds>,
56+
pub(crate) api_configurations: Arc<ApiConfigurations>,
57+
pub(crate) repository: Option<Arc<dyn Repository<Folder>>>,
58+
}
59+
```
60+
61+
If the client will be exposed over WASM, annotate it with
62+
`#[cfg_attr(feature = "wasm", wasm_bindgen)]`.
63+
64+
## 4. Wire into the application interface
65+
66+
Connect the feature client to the SDK `Client` by defining an
67+
[extension trait](client-patterns.md#extension-traits). This makes the feature accessible without
68+
modifying `Client` itself.
69+
70+
```rust
71+
pub trait VaultClientExt {
72+
fn vault(&self) -> VaultClient;
73+
}
74+
75+
impl VaultClientExt for Client {
76+
fn vault(&self) -> VaultClient {
77+
VaultClient::new(self.clone())
78+
}
79+
}
80+
```
81+
82+
:::note
83+
84+
While there is a team called `vault`, `VaultClient` refers to the vault-domain, not the team. You
85+
should not create team-clients in the SDK. Instead, organize clients by domain or feature area, and
86+
assign ownership to the team that maintains that domain or feature.
87+
88+
:::
89+
90+
For larger domains, the application interface client delegates to sub-clients rather than
91+
implementing every method itself. For example, `VaultClient` exposes `FoldersClient`,
92+
`CiphersClient`, and others through accessor methods:
93+
94+
```rust
95+
#[cfg_attr(feature = "wasm", wasm_bindgen)]
96+
impl VaultClient {
97+
pub fn folders(&self) -> FoldersClient {
98+
FoldersClient::from_client(&self.client)
99+
}
100+
}
101+
```
102+
103+
Finally, expose the new client in the application interface entry point so consumers can reach it.
104+
For the Password Manager SDK, this means adding an accessor method to
105+
[`PasswordManagerClient`][pm-lib]:
106+
107+
```rust
108+
impl PasswordManagerClient {
109+
pub fn vault(&self) -> bitwarden_vault::VaultClient {
110+
self.0.vault()
111+
}
112+
}
113+
```
114+
115+
:::tip
116+
117+
Steps 2–4 (create crate, define client, wire into application interface) should be submitted as a
118+
single pull request. Keep it small and focused on scaffolding — the Platform team reviews additions
119+
to the application interface clients, so a narrow scope helps move that review along quickly.
120+
121+
:::
122+
123+
## 5. Implement methods
124+
125+
With the crate scaffolding merged, add methods in subsequent pull requests. Each method should own
126+
its logic directly on the client struct — avoid thin passthroughs to free functions. For larger
127+
clients, split methods into separate files or subdirectories as described in
128+
[Client patterns](client-patterns.md#per-method-files-or-subdirectories).
129+
130+
:::important
131+
132+
Every public method on a client is a contract with consumers and must have test coverage. Treat
133+
client methods as a public API boundary — changes to their behavior can break downstream consumers
134+
across multiple platforms. See [Client patterns — Testing](client-patterns.md#testing) for how to
135+
set up test doubles.
136+
137+
:::
138+
139+
```rust
140+
#[cfg_attr(feature = "wasm", wasm_bindgen)]
141+
impl FoldersClient {
142+
pub async fn get(&self, folder_id: FolderId) -> Result<FolderView, GetFolderError> {
143+
let folder = self
144+
.repository
145+
.require()?
146+
.get(folder_id)
147+
.await?
148+
.ok_or(ItemNotFoundError)?;
149+
150+
Ok(self.key_store.decrypt(&folder)?)
151+
}
152+
}
153+
```
154+
155+
Consumers access the feature through the application interface:
156+
157+
```rust
158+
let folders = client.vault().folders().list().await?;
159+
```
160+
161+
## 6. Add mobile bindings
162+
163+
If the new functionality needs to be available on mobile platforms (Android / iOS), add a
164+
[UniFFI](language-bindings.md#mobile-bindings) wrapper in the `bitwarden-uniffi` crate.
165+
166+
### Expose the client
167+
168+
Add an accessor method on the appropriate UniFFI client — typically in
169+
[`bitwarden-uniffi/src/lib.rs`][uniffi-lib] or a sub-client — that returns a new wrapper struct:
170+
171+
```rust
172+
impl Client {
173+
pub fn vault(&self) -> Arc<VaultClient> {
174+
Arc::new(VaultClient(self.0.clone()))
175+
}
176+
}
177+
```
178+
179+
### Create the wrapper
180+
181+
Create a wrapper struct that holds the SDK `Client` and delegates to the underlying Rust client. See
182+
[`bitwarden-uniffi/src/tool/sends.rs`][uniffi-sends] for a complete example.
183+
184+
```rust
185+
use crate::Result;
186+
187+
pub struct FoldersClient(pub(crate) SharedClient);
188+
189+
#[uniffi::export]
190+
impl FoldersClient {
191+
pub async fn get(&self, folder_id: FolderId) -> Result<FolderView> {
192+
Ok(self.0.vault().folders().get(folder_id).await?)
193+
}
194+
}
195+
```
196+
197+
The wrapper should convert errors into `BitwardenError`. When introducing a new error type, add a
198+
variant for it in [`bitwarden-uniffi/src/error.rs`][uniffi-error] and implement the `From`
199+
conversion.
200+
201+
## Ownership
202+
203+
Feature and domain crates are usually owned and maintained by individual teams. When creating a new
204+
crate, coordinate with the Platform team to establish ownership and review expectations.
205+
206+
[pm-lib]: https://github.com/bitwarden/sdk-internal/blob/main/crates/bitwarden-pm/src/lib.rs
207+
[state-crate]: https://github.com/bitwarden/sdk-internal/tree/main/crates/bitwarden-state
208+
[uniffi-error]:
209+
https://github.com/bitwarden/sdk-internal/blob/main/crates/bitwarden-uniffi/src/error.rs
210+
[uniffi-lib]: https://github.com/bitwarden/sdk-internal/blob/main/crates/bitwarden-uniffi/src/lib.rs
211+
[uniffi-sends]:
212+
https://github.com/bitwarden/sdk-internal/blob/main/crates/bitwarden-uniffi/src/tool/sends.rs
213+
[vault-crate]: https://github.com/bitwarden/sdk-internal/tree/main/crates/bitwarden-vault

0 commit comments

Comments
 (0)