> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mirobody.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Garmin Vendor 示例

> Garmin 设备品牌客户端如何对应到 C++ vendor 契约 —— 以及为何其中大部分刻意保留为桩。

## 概览

Garmin 是一个真实的设备品牌客户端 —— [`src/health/vendor/device/garmin.cpp`](https://github.com/thetahealth/mirobody/blob/main/src/health/vendor/device/garmin.cpp)，注册为 `garmin`。它是 Mirobody"只实现可公开确认的部分，其余保留为诚实的桩"这一规则最具启发性的例子，因为 Garmin 的数据 API **需合作方授权且为推送式**。

<Info>
  这是一个 `vendor::Vendor`，实现共享的 `authorize / fetch / webhook` 契约；接口参见 [Vendor 系统](/zh/concepts/providers)。
</Info>

## Garmin 的 API 形态带来的约束

| 方面             | 实际情况                                                      | 对客户端的影响                                                                                  |
| -------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| **认证**         | OAuth 2.0 + **PKCE**（Garmin 已从 OAuth 1.0a 迁移）；流程公开        | `authorize_url` 是**桩** —— PKCE 需要一个有状态的 `code_verifier`，裸 URL 构建器无处存放它（那属于 EHR 式的有状态连接层） |
| **数据投递**       | **推送式**：Garmin 把汇总数据 POST 到已注册的 webhook；拉取需合作方授权且仅保留约 7 天 | `fetch` 是一个**桩**，它解释这一模型，而非伪造一次同步拉取                                                      |
| **Webhook 签名** | 推送**无任何有文档的签名**                                           | `handle_webhook` 是**桩** —— 无法从公开文档验证真伪                                                   |
| **断开连接**       | 账号断连 hook **是**公开有文档的                                     | `revoke` **已实现**                                                                         |

于是在六个契约操作中，Garmin 恰好发布了一个可用调用 —— `revoke` —— 以及三个刻意保留的桩（`fetch`、`authorize_url`、`handle_webhook`），而 `list_providers` 不适用（单一品牌）。这是预期状态，记录在该文件的头部。

## `revoke` —— 唯一接通的操作

`revoke` 调用 Garmin 必需的账号断连端点 `DELETE /wellness-api/rest/user/registration`，以用户的 OAuth 2.0 access token 作为 bearer 认证：

```cpp theme={null}
void revoke(const std::string& /*user_id*/, const std::string& /*provider*/) override {
    if (config().api_key.empty()) {
        throw VendorError(info_.id + ": revoke requires a Garmin OAuth 2.0 access token — set "
                          "MIROBODY_VENDOR_GARMIN_API_KEY (partner-gated; needs approved creds)");
    }
    client::HttpRequest req;
    req.url     = base_url() + "/wellness-api/rest/user/registration";
    req.headers = { "Authorization: Bearer " + config().api_key };
    client::HttpResponse res = client::HttpClient().request("DELETE", req);
    if (res.status < 200 || res.status >= 300) {
        throw VendorError(info_.id + ": revoke failed (HTTP " +
                          std::to_string(res.status) + "): " + res.body.substr(0, 300));
    }
}
```

数据主机默认为 `https://apis.garmin.com`（可经 `MIROBODY_VENDOR_GARMIN_BASE_URL` 覆盖）。

## `fetch` —— 一个会自我解释的桩

Garmin 的 `fetch` 不用通用的"not implemented"，而是抛出一条说明原因的消息，让调用方了解这一模型：

```cpp theme={null}
std::string fetch(const std::string&, DataDomain,
                  const std::string&, const std::string&) override {
    throw VendorError(info_.id + ": Garmin Health is push-based and partner-gated "
                      "(OAuth2.0). Data is pushed to a registered webhook, not pulled — "
                      "wire the Ping/Pull data endpoints (partner-gated spec) or consume the "
                      "push once approved partner credentials exist.");
}
```

## 元数据（`info()`）

即便大多数操作都是桩，该 vendor 的 `VendorInfo` 依然被完整填充，且无需凭据即可查询：

```cpp theme={null}
i.id                 = "garmin";
i.display_name       = "Garmin Health";
i.positioning        = "Premium wearable brand; partner-gated, push-based Health API";
i.integration_method = "OAuth2.0 + PKCE; push (Ping/Push webhook) delivery; backfill triggers async";
i.docs_url           = "https://developer.garmin.com/gc-developer-program/health-api/";
i.region             = Region::Global;
i.domains            = {DataDomain::Activity, DataDomain::HeartRate,
                        DataDomain::Sleep, DataDomain::BodyMetrics};
i.integrations       = {Integration::Rest, Integration::Webhook};
```

## 配置

```bash theme={null}
# Garmin partner-gated OAuth 2.0 access token (needed for revoke; data path is partner-gated)
MIROBODY_VENDOR_GARMIN_API_KEY=your_garmin_oauth2_access_token
# optional host override
MIROBODY_VENDOR_GARMIN_BASE_URL=https://apis.garmin.com
```

## 对你自己 vendor 的启示

* **实现可公开确认的部分；其余用带原因的桩留白。** 一个点明缺失契约的桩，胜过一个捏造的契约。
* **有状态的 OAuth 流程（PKCE）单靠 `authorize_url` 装不下** —— Mirobody 把这类流程保留在有状态的连接层（正如 SMART-on-FHIR EHR 连接服务所做的那样），而不是放进 URL 构建器的签名里。
* **`VendorInfo` 无论如何都应完整** —— 它无需网络调用即可支撑选型 / 比较。

若要看一个实现更完整的设备品牌，可将 Garmin 与 **Fitbit**（`src/health/vendor/device/fitbit.cpp`）对照，后者实现了 `fetch`、`authorize_url`、`revoke` 以及带签名的 `handle_webhook`。

## 下一步

<CardGroup cols={2}>
  <Card title="Vendor 系统" icon="plug" href="/zh/concepts/providers">
    Garmin 所实现的契约
  </Card>

  <Card title="Provider 集成" icon="code" href="/zh/development/provider-integration">
    构建你自己的 `vendor::Vendor`
  </Card>

  <Card title="OAuth 实现" icon="key" href="/zh/development/oauth-implementation">
    vendor 客户端中的 OAuth 2.0 / PKCE 模式
  </Card>

  <Card title="Provider 概览" icon="watch" href="/zh/providers/overview">
    所有已发布的 vendor
  </Card>
</CardGroup>
