You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As an OpenLark SDK user, I want existing Client::builder() calls to keep compiling, so that the refactor does not require source migration.
As an OpenLark SDK user, I want ClientBuilder to retain its 30-second default timeout, so that existing request timing behavior remains stable.
As a core configuration user, I want core ConfigBuilder to retain its no-timeout default, so that independent core callers are not silently constrained.
As a core configuration user, I want ConfigBuilder::build() to remain non-validating, so that the existing permissive construction contract is preserved.
As a security-conscious SDK user, I want non-Feishu/Lark base URLs rejected by default, so that credentials and tokens are not accidentally sent to an untrusted host.
As an SDK user with an intentional proxy or mock server, I want allow_custom_base_url(true) to be preserved through Client construction, so that explicitly authorized custom hosts continue to work.
As an SDK user supplying a core Config, I want Client::with_core_config to enforce the same hostname rules as ClientBuilder, so that constructor choice does not change the security contract.
As an SDK user, I want an unset core timeout accepted by Client::with_core_config, so that core's no-timeout configuration remains valid.
As an SDK user, I want a zero timeout rejected by Client construction, so that an unusable Client fails early with a clear validation error.
As an operator, I want environment variables applied at the point where from_env() or load_from_env() appears in the chain, so that override order is predictable.
As an operator, I want missing or empty environment variables to preserve existing builder values, so that partial environment configuration does not erase code defaults.
As an operator, I want invalid numeric values and unknown application types ignored as before, so that this refactor does not introduce unrelated environment-loading failures.
As an SDK user, I want a setter called after environment loading to override the environment value, so that explicit code configuration can take precedence.
As an SDK user, I want environment loading called after a setter to override that setter when a valid environment value exists, so that chain order remains meaningful.
As an SDK user, I want to add one header without replacing the existing header map, so that incremental request configuration remains convenient.
As an SDK user, I want duplicate header keys resolved by last write, so that header behavior is deterministic.
As an SDK user, I want whole-map header replacement and incremental header insertion to follow chain order, so that mixed usage is unambiguous.
As a maintainer, I want each configuration field stored in one canonical state, so that new fields cannot drift between core and Client representations.
As a maintainer, I want environment variable parsing implemented once, so that adding an environment-backed field requires one change.
As a maintainer, I want general validation implemented once in core, so that error fields, whitelist rules, and retry limits stay aligned.
As a maintainer, I want the Client-specific zero-timeout rule localized to Client construction, so that core callers are not subject to an unintended stricter policy.
As a maintainer, I want both public Client construction entries to use one private seam, so that initialization ordering and context cannot diverge.
As a maintainer, I want private field-mirroring tests removed, so that internal restructuring does not require rewriting behaviorally irrelevant tests.
As a test author, I want to verify configuration through the built Config or Client, so that tests exercise the same interface as callers.
As a test author, I want environment tests serialized and isolated, so that process-global state does not make the suite flaky.
As a support engineer, I want validation errors to retain an accurate category and field, so that recovery guidance remains actionable.
As a support engineer, I want errors to identify whether ClientBuilder::build or Client::with_core_config failed, so that reports can be traced to the correct caller path.
As a security reviewer, I want builder debug output to omit secrets, token provider details, and header contents, so that diagnostic logs cannot disclose sensitive configuration.
As a release maintainer, I want the custom-host propagation and whitelist enforcement fixes recorded in the changelog, so that the tightened behavior is visible to adopters.
As a future contributor, I want the shallow Client configuration mirror deleted rather than patched, so that the same class of drift cannot recur.
Implementation Decisions
The core configuration module is the canonical module for configuration state, defaults, environment interpretation, header merging, and general validation.
ConfigBuilder will directly hold the canonical private configuration state instead of a parallel collection of optional fields.
Existing ConfigBuilder setters and the behavior of build() remain unchanged from a caller perspective.
ConfigBuilder gains load_from_env() for ordered environment overlay and add_header() for incremental header insertion.
Static environment construction, mutation of an existing Config, and builder overlay must share one private environment interpretation implementation.
ClientBuilder will directly hold core ConfigBuilder; it will not mirror configuration fields.
Existing ClientBuilder setters remain available and delegate to core builder.
ClientBuilder::new() applies the Client-specific 30-second timeout over core defaults.
ClientBuilder::build and Client::with_core_config use one private construction seam.
The private construction seam owns validation and initialization ordering; callers are not required to validate first.
Config::validate() remains the only implementation for credentials, URL scheme, domain whitelist, and retry-count rules.
Client construction adds only the zero-timeout rule. A missing timeout remains valid for Client::with_core_config.
A custom host is accepted only when allow_custom_base_url(true) is explicitly present in the canonical configuration state.
Validation errors use CoreError, preserve the validation category and accurate field, and use core's canonical wording.
Error strings are not required to remain byte-for-byte compatible with the removed duplicate implementation.
Construction errors retain operation context for the public entry that was used. Registry initialization errors also retain the service_loading context.
Environment overlay follows call order. Missing, empty, or unparsable values preserve the current builder state.
Incremental headers use last-write-wins. Whole-map replacement and incremental insertion follow call order.
ClientBuilder retains Clone and a redacted Debug implementation. Debug output must not include secrets, token provider internals, or header values.
The parallel Client configuration state, its conversion, and its duplicate validation are deleted rather than deprecated because they are private implementation details.
No configuration policy interface, profile abstraction, port, or adapter is introduced. All dependencies are in-process, and no second varying implementation justifies another seam.
The earlier creation of the default HTTP client when a core builder is created is an accepted implementation trade-off for canonical state and higher locality.
Release notes will identify the security tightening, custom-host propagation fix, and normalized validation wording.
Testing Decisions
Good tests assert observable results through core ConfigBuilder, ClientBuilder::build, or Client::with_core_config. They do not inspect private builder fields or call removed validation helpers.
Core configuration tests cover the no-timeout default, ordered environment overlay, ignored invalid environment values, incremental header insertion, whole-map replacement order, custom-host flag retention, cloning, and redacted debug output.
Client construction tests cover the 30-second default, custom-host rejection and explicit allowance, equal whitelist behavior across both public constructors, valid missing timeout, rejected zero timeout, and preserved final configuration.
Error tests assert category, field, and operation context rather than legacy wording.
Environment tests use the repository's existing serialized environment-variable fixture pattern to avoid process-global races.
Existing core configuration tests are the prior art for builder defaults, environment loading, whitelist validation, and custom-host behavior.
Existing Client construction tests are the prior art for observing final configuration through Client::config() and for validating both construction entries.
Tests tied only to the removed private configuration mirror are deleted. Any behavior they uniquely protected is migrated to the highest surviving interface.
Verification includes focused core and Client tests, formatting, both all-feature and no-default-feature clippy modes, and workspace compilation checks.
Changelog assertions are reviewed alongside behavior tests so that the security tightening and compatibility contract remain documented.
Out of Scope
Making core ConfigBuilder::build() automatically validate.
Changing core's default timeout.
Removing or renaming existing public ClientBuilder setters.
Introducing public configuration policies, profiles, overlays, ports, or adapters beyond the two approved builder additions.
Adding strict errors for malformed optional environment values that are currently ignored.
Guaranteeing byte-for-byte compatibility of validation messages.
Redesigning the SDK's broader error hierarchy.
Changing the Feishu/Lark domain whitelist itself.
Further Notes
This work completes a follow-up explicitly left outside the earlier deprecated Config merge: the Client-side duplicate validation and construction state were retained temporarily and are now showing real drift.
The confirmed deletion test is central to the design: removing the private Client mirror makes complexity disappear into the canonical core module rather than pushing it into callers.
The repository currently has no domain glossary term that needs to be introduced for this implementation-level configuration work.
Problem Statement
OpenLark 用户目前可以通过
ClientBuilder或现成的 coreConfig构造 Client,但两条路径背后维护了平行的配置状态、环境变量解释和校验 implementation。这种重复已经造成可观察缺陷:ClientBuilder接受allow_custom_base_url(true),转换时却可能丢失该设置;Client::with_core_config又使用较弱的重复校验,使非白名单域名在没有显式放行时仍可能进入 Client。对 SDK 用户而言,这意味着看似相同的配置在不同构造入口可能产生不同结果,安全开关也不能可靠地兑现。对维护者而言,每增加一个配置字段,都必须同步修改多份状态和转换逻辑,遗漏后只能等到运行时或安全审查才能发现。
Solution
将 core
ConfigBuilder深化为配置状态、默认值、环境覆盖和 header 合并的唯一 implementation。ClientBuilder保留现有公开 interface,但只委托 core builder,并仅保留 Client 特有的 30 秒默认超时和严格构造策略。ClientBuilder::build与Client::with_core_config穿过同一个私有构造 seam。该 seam 统一执行 core 配置校验、Client 零超时校验、错误 context、registry 初始化、token provider 注入和 Client 组装。删除平行的
ClientBuildConfig、逐字段转换以及重复的 Client 校验。core 的宽松构建契约保持不变;只有 Client 构造入口继续严格校验。非白名单域名必须显式设置allow_custom_base_url(true),从而恢复 issue #150 已确定的安全契约。User Stories
Client::builder()calls to keep compiling, so that the refactor does not require source migration.ClientBuilderto retain its 30-second default timeout, so that existing request timing behavior remains stable.ConfigBuilderto retain its no-timeout default, so that independent core callers are not silently constrained.ConfigBuilder::build()to remain non-validating, so that the existing permissive construction contract is preserved.allow_custom_base_url(true)to be preserved through Client construction, so that explicitly authorized custom hosts continue to work.Config, I wantClient::with_core_configto enforce the same hostname rules asClientBuilder, so that constructor choice does not change the security contract.Client::with_core_config, so that core's no-timeout configuration remains valid.from_env()orload_from_env()appears in the chain, so that override order is predictable.ConfigorClient, so that tests exercise the same interface as callers.ClientBuilder::buildorClient::with_core_configfailed, so that reports can be traced to the correct caller path.Implementation Decisions
ConfigBuilderwill directly hold the canonical private configuration state instead of a parallel collection of optional fields.ConfigBuildersetters and the behavior ofbuild()remain unchanged from a caller perspective.ConfigBuildergainsload_from_env()for ordered environment overlay andadd_header()for incremental header insertion.Config, and builder overlay must share one private environment interpretation implementation.ClientBuilderwill directly hold coreConfigBuilder; it will not mirror configuration fields.ClientBuildersetters remain available and delegate to core builder.ClientBuilder::new()applies the Client-specific 30-second timeout over core defaults.ClientBuilder::buildandClient::with_core_configuse one private construction seam.Config::validate()remains the only implementation for credentials, URL scheme, domain whitelist, and retry-count rules.Client::with_core_config.allow_custom_base_url(true)is explicitly present in the canonical configuration state.CoreError, preserve the validation category and accurate field, and use core's canonical wording.service_loadingcontext.ClientBuilderretainsCloneand a redactedDebugimplementation. Debug output must not include secrets, token provider internals, or header values.Testing Decisions
ConfigBuilder,ClientBuilder::build, orClient::with_core_config. They do not inspect private builder fields or call removed validation helpers.Client::config()and for validating both construction entries.Out of Scope
ConfigBuilder::build()automatically validate.ClientBuildersetters.Further Notes