doc: document unified Config design, override flow, and lifecycle#433
doc: document unified Config design, override flow, and lifecycle#433kp-antonio-yang wants to merge 3 commits into
Conversation
|
Code coverage summary for 72b8395: ✅ Region coverage 69% passes |
6b1598a to
10db114
Compare
e3bbe81 to
4368088
Compare
d92647c to
8cdd62f
Compare
kp-samuel-tam
left a comment
There was a problem hiding this comment.
It's a great write-up overall, the flow chart captures the layering really well. I would like to raise a few blockers.
| 6 ==> clientFn | ||
| ``` | ||
|
|
||
| As shown, `Config` is the single source of truth for all clients — all user inputs, whether from a UI or a file, |
There was a problem hiding this comment.
From this line onwards there's a clear disjoint between the convention you're documenting and how Config actually ships today. Looking at the merged PR #411:
wintun_file,enable_dpapi→#[cfg(windows)](target gate on field)route_mode,dns_config_mode,enable_pmtud,enable_tun_iouring,enable_inside_pkt_encoding→#[cfg(desktop)](custom cfg on field)enable_batch_receive→#[cfg(batch_receive)](custom cfg on field)
The doc says "feature gates belong on fields" but there's 8+ fields in the prior PR being documented use target/custom cfg on fields.
Even if we're aligned on the direction, the code needs to follow the rule before we declare it a developer practice in the docs.
There was a problem hiding this comment.
Yeh, not all the code following the design and only mobile does.
The reason it when we use json schema on windows/macos client and we will refactor it to follow this design. For now, we do the least change on the code when mobile introduce. And it will make really sense when developer working on that target and test it at the same time, so we do not touch these target now.
There was a problem hiding this comment.
The lingering concerns are a signal that the documentation itself isn't clear enough. Ideally, after reading your doc, people should walk away feeling that what you're proposing makes sense.
The current document covers the what in depth, but doesn't really address the why. This is especially important in your case because you're deviating from the standard, which naturally raises concerns. Addressing those concerns head-on is exactly what this change needs to do, and it's also why I suggested writing this up in the first place.
In a technical context, explaining the why well means laying out your motivation, the technical constraints you're working within, and how this design directly addresses those challenges. I'd suggest following this exact structure in your doc for clarity.
There was a problem hiding this comment.
Yes, the why is the important and making focus.
A introduction is added, and also the using scenario put more details about why the scope is boarder than before. This issue should not be a problem.
| fn main () { | ||
| let config = Config::load(); | ||
| client(config) | ||
| } | ||
|
|
||
| #[cfg(window)] | ||
| fn client(config: Config) { | ||
| let Config { | ||
| win_only_field, | ||
| .. | ||
| } = config; | ||
|
|
||
| if win_only_filed > 256 { | ||
| // ... | ||
| } | ||
| } | ||
|
|
||
| #[cfg(android)] | ||
| fn client(config: Config) { | ||
| let Config { | ||
| android_only_field, | ||
| .. | ||
| } = config; | ||
| let tun = Tun::new(android_only_field); | ||
| } |
There was a problem hiding this comment.
Looks like this pseudocode was copy-pasted from #411 (comment) with typos - #[cfg(window)] is missing the s, and win_only_filed should be win_only_field.
Also, this code actively encourages the paired use of #[cfg(windows)] and #[cfg(feature="windows")]. This is unsound because we can't ever guarantee they are in sync or supported, adding to the drift Thomas was trying to talk about.
There was a problem hiding this comment.
It will always need to enable the corresponding feature gate for targets with this design, like current mobile feature.
Also, we can make sure it always sync and supported via cargo make, and also that is the reason we have script or make file in the project.
There was a problem hiding this comment.
How about we make this less controversial by only applying this rule on mobile for now? i.e.:
- Mobile -> use
#[cfg(feature="mobile")]to allow schema generation on desktop - Desktop -> follow Rust's default convention
As you noted in the doc, desktop won't adopt this convention until the larger refactor lands. Any new desktop config added before then would deviate from the proposal anyway, which somewhat undercuts its value if we roll it out broadly now.
There was a problem hiding this comment.
The intent was never to lay down strict rules — it started as a brief three-line comment to guide developers when they need to revisit the JSON schema. As it evolved into a document, it made more sense to capture not just the current state but also the details we will likely encounter going forward, especially since the discussion is already open. Current document put introduction and easier for reader to have the focus.
There was a problem hiding this comment.
The intent was never to lay down strict rules
No, by writing down what people should do, you are setting rules, no matter what you call it.
If you want to keep your vision for the desktop config, you may put it in a section labeled "Future Plans", but it must be distinctive that it's not meant to be followed.
57667a2 to
5ce523f
Compare
|
Hi @kp-samuel-tam |
5ce523f to
7d85daa
Compare
| ## Use cases by developer persona | ||
|
|
||
| The general config centralizes all settings into a single `Config` struct and aligns the config generation flow across clients. However, only the mobile feature has been implemented with this design so far — existing clients may not adopt it until a larger refactor happens. | ||
| There are two possible directions going forward: |
There was a problem hiding this comment.
Could you clarify when to use each direction?
It's not clear what the differences are or which scenarios call for which approach. If they're effectively the same, let's pick one as the standard going forward.
There was a problem hiding this comment.
If we can reach a decision on Option 1, I can drop the redundant documentation and keep things lean. Once that lands, a follow-up PR can enforce the same design across the desktop clients.
Personally, I lean towards Option 1 with feature gates. Since we are already using Makefile.toml, passing --features is not an extra burden — it is a common pattern in our build flow for each target. However, since there seems to be resistance towards --feature=target, so I am leaving this as an open question for now.
| @@ -0,0 +1,144 @@ | |||
| # General Config | |||
There was a problem hiding this comment.
Suggesting a more concise title:
| # General Config | |
| # Client Config Design Rationale |
Can you also link this doc in lightway-client/src/config.rs, such that people are aware that there is a relevant doc here?
There was a problem hiding this comment.
It is General Config.
I should doc the server part, although it is really similar with cli part of the client.
There was a problem hiding this comment.
Then I would suggest Client/Server Config Design Rationale. The word "General" doesn't really mean anything.
I see you've extended the diagram to include server as well, but that has become a bit messy. Can you separate it into its own flow chart?
| 6 ==> clientFn | ||
| ``` | ||
|
|
||
| As shown, `Config` is the single source of truth for all clients — all user inputs, whether from a UI or a file, |
There was a problem hiding this comment.
The lingering concerns are a signal that the documentation itself isn't clear enough. Ideally, after reading your doc, people should walk away feeling that what you're proposing makes sense.
The current document covers the what in depth, but doesn't really address the why. This is especially important in your case because you're deviating from the standard, which naturally raises concerns. Addressing those concerns head-on is exactly what this change needs to do, and it's also why I suggested writing this up in the first place.
In a technical context, explaining the why well means laying out your motivation, the technical constraints you're working within, and how this design directly addresses those challenges. I'd suggest following this exact structure in your doc for clarity.
| fn main () { | ||
| let config = Config::load(); | ||
| client(config) | ||
| } | ||
|
|
||
| #[cfg(window)] | ||
| fn client(config: Config) { | ||
| let Config { | ||
| win_only_field, | ||
| .. | ||
| } = config; | ||
|
|
||
| if win_only_filed > 256 { | ||
| // ... | ||
| } | ||
| } | ||
|
|
||
| #[cfg(android)] | ||
| fn client(config: Config) { | ||
| let Config { | ||
| android_only_field, | ||
| .. | ||
| } = config; | ||
| let tun = Tun::new(android_only_field); | ||
| } |
There was a problem hiding this comment.
How about we make this less controversial by only applying this rule on mobile for now? i.e.:
- Mobile -> use
#[cfg(feature="mobile")]to allow schema generation on desktop - Desktop -> follow Rust's default convention
As you noted in the doc, desktop won't adopt this convention until the larger refactor lands. Any new desktop config added before then would deviate from the proposal anyway, which somewhat undercuts its value if we roll it out broadly now.
6d460c2 to
dddfeec
Compare
dddfeec to
3248fab
Compare
|
Please take a look at the updated doc when you get a chance. 🙏
On the options section — if we would rather not leave it open-ended in the doc, I am happy to implement either Option 1 or Option 2 in a follow-up PR. But we need to reach consensus here first before I proceed. |
7f719d8 to
72f5f31
Compare
72f5f31 to
861861d
Compare
|
This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 7 days if no further activity occurs. |
7350c2b to
400e4f5
Compare
Previously, `config.validate()` was called inside the constructor before CLI and environment patches were applied, meaning validation ran on a partially-configured state and could reject a valid final config or pass on one that becomes invalid after patching. Move the validation call to after all patches are applied in `main.rs` and `mobile.rs`, so the check reflects the actual configuration that will be used.
5599572 to
c0da437
Compare
- documents with flow about config changes in cli, mobile - add mermaid chart for the flow - add how to apply the flow when introducing a new client
Add a "Config Lifecycle Methods" subsection covering the three methods that bridge a fully determined Config (step 6) into the client runtime, in call-site order: - validate(): checks the resolved Config for conflicts and invalid values — warns on sndbuf/rcvbuf/enable_pmtud set on TCP connections (non-macOS), and errors on invalid device_guid UUID or out-of-range wintun_ring_capacity (Windows). Called standalone in main.rs as the first step after config is determined, before logging or any other setup. - take_servers(): normalizes the server list before ClientConfig is constructed — promotes top-level single-server fields into Vec<ConnectionConfig>, resolves ca_cert (per-server → top-level → file path on desktop, error if unresolvable), and propagates shared auth (user/password/token) to servers that lack their own. - try_from_reload_sig_and_config(): the canonical ClientConfig constructor — builds TunConfig (name, WinTun settings, MTU, addresses), and maps all remaining Config fields including the optional config_reload_signal for runtime hot-reload. A call-site example showing the required ordering is placed at the top of the section before the per-method entries. The flow diagram is also updated to show validate() as the first step after step 6.
2eaa83e to
0211c90
Compare
Description
try_from_reload_sig_and_config)Configstruct design rationale for client and serverConfiglifecycle methods:take_servers(),validate(),try_from_reload_sig_and_config()lightway-client/src/config.rsandlightway-server/src/config.rsMotivation and Context
The client
Configstruct serves multiple targets (desktop, mobile, Windows, Linux, macOS) and several distinct user personas (native developers, cross-platform developers, frontend/designers). Without documentation, the design are not easy to know.This PR documents the work from #386, #388, #411, #420, and #451.
Easy to read on GitHub.
How Has This Been Tested?
Most documentation only and follow current test
Types of Changes
Checklist
main