跳转到主要内容

概览

Garmin 是一个真实的设备品牌客户端 —— src/health/vendor/device/garmin.cpp,注册为 garmin。它是 Mirobody”只实现可公开确认的部分,其余保留为诚实的桩”这一规则最具启发性的例子,因为 Garmin 的数据 API 需合作方授权且为推送式
这是一个 vendor::Vendor,实现共享的 authorize / fetch / webhook 契约;接口参见 Vendor 系统

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 —— 以及三个刻意保留的桩(fetchauthorize_urlhandle_webhook),而 list_providers 不适用(单一品牌)。这是预期状态,记录在该文件的头部。

revoke —— 唯一接通的操作

revoke 调用 Garmin 必需的账号断连端点 DELETE /wellness-api/rest/user/registration,以用户的 OAuth 2.0 access token 作为 bearer 认证:
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”,而是抛出一条说明原因的消息,让调用方了解这一模型:
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 依然被完整填充,且无需凭据即可查询:
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};

配置

# 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 与 Fitbitsrc/health/vendor/device/fitbit.cpp)对照,后者实现了 fetchauthorize_urlrevoke 以及带签名的 handle_webhook

下一步

Vendor 系统

Garmin 所实现的契约

Provider 集成

构建你自己的 vendor::Vendor

OAuth 实现

vendor 客户端中的 OAuth 2.0 / PKCE 模式

Provider 概览

所有已发布的 vendor