Skip to content

refactor: refactor architecture#6

Merged
bitxwave merged 8 commits intomainfrom
refactor/architecture
May 1, 2026
Merged

refactor: refactor architecture#6
bitxwave merged 8 commits intomainfrom
refactor/architecture

Conversation

@bitxwave
Copy link
Copy Markdown
Owner

@bitxwave bitxwave commented May 1, 2026

No description provided.

bitxwave added 8 commits May 1, 2026 17:05
- Implement std FromStr and Display for Protocol; remove custom from_str
  and the thin SourceProtocol/OutputProtocol aliases
- Drop cli::OutputFormat: a no-arg enum that was only used to
  double-dispatch what Protocol::default_output_format already encodes
- Registry uses IndexMap so iteration order matches registration order
  (deterministic logging and format-detection fallback)
- Introduce FORMAT_SUBSCRIPTION/FORMAT_PLAIN constants; route detect.rs
  and loader.rs through them and Protocol::as_format_str to kill
  scattered "clash"/"singbox"/"v2ray" literals
- TemplateEngine auto-numbers unnamed sources (source_N) instead of
  collapsing them to a shared "default" key
Commands:
- Each subcommand becomes a dedicated Args struct (ConvertArgs,
  ValidateArgs, TemplateArgs) via clap's #[derive(Args)]. Handlers
  receive typed args directly instead of pattern-matching the whole
  Commands enum and returning "Expected X" errors that can't actually
  fire — main.rs has already dispatched.
- AppConfig::merge_cli_params collapses into merge_convert_args
  (only the Convert subcommand has overridable fields).

URL handling:
- Drop the hand-rolled append_flag_to_url / get_flag_param_value /
  update_flag_param trio. url::Url does it correctly including
  fragment preservation and proper percent-encoding.
- New unit tests cover add/replace/override/fragment cases.

Sing-box DNS normalization:
- Move the legacy-DNS fallback out of SourceLoader and into
  SingboxFormat::parse_config where it belongs. The loader no longer
  knows about singbox internals, removing a backwards-pointing
  cross-module call.
Move the parsed-source model (Source, Config enum, ProxyServer
extraction) from utils/source/parser.rs to protocols/source.rs.

The Config variants wrap protocol-specific configs (clash::Config,
singbox::Config, v2ray::Config) and the extraction code reaches deep
into protocol types — this is protocol-layer logic, not a generic
util. Keeping it in utils forced `utils -> protocols` edges
throughout the codebase (utils becomes protocol-aware, inverting the
dependency arrow).

Now utils stays genuinely generic (parse_helpers only in the source
subtree; the loader remains as IO/orchestration).

No behavior change: type paths update from
`utils::source::parser::{Source,Config}` to `protocols::source::...`
(re-exported at `protocols::{Source,Config}`), and the file moves
via `git mv` preserving history. All 114 tests pass.
ProtocolFormat gains a processor() method returning a 'static
ProtocolProcessor reference. Each format (Clash/Singbox/V2Ray) holds
its processor as a module-level static so the trait method can hand
it out without heap allocation.

ProtocolRegistry drops its second map. One registration call per
protocol now covers descriptor metadata + parse_config + validate +
default template + template processor. Forgetting to register the
processor half is no longer possible.
The flat `parameters: HashMap<String, Value>` field was a
serde-exposed bag of protocol-specific JSON that duplicated the
typed `params: ProxyParams` enum. Two sources of truth, always at
risk of drifting apart.

Collapse into one: each ProxyParams variant carries its own
`extras: HashMap<String, Value>` for raw leftover fields specific to
that protocol. ProxyServer::extras() forwards to the variant.

- ProxyServer loses the flat parameters field
- Each ProxyParams variant (Shadowsocks/Vmess/Trojan/Vless/
  Hysteria2/Generic) gains `extras`
- Every construction site (source.rs, subscription.rs, tests) now
  populates extras inside the variant instead of the flat map
- Every read site in the three template processors reads through
  `node.extras()`; pattern-matching downstream on ProxyParams adds
  `..` to tolerate the new field
Previously SourceMeta.source held the raw user input (e.g.
"https://x/y?type=clash&token=..&flag=..") and downstream code
re-parsed it twice: loader used starts_with("http") to branch,
then sliced at '?' for file paths; URL flag manipulation parsed the
same string again.

Now SourceMeta carries a typed SourceLocation { Url(url::Url) |
File(PathBuf) }, computed once in parse_source_string. The parser
also splits synthetic keys (type/name/flag) from the user's own
URL query params — our keys never reach the remote server, and
with_flag_param operates on url::Url directly without re-parsing.

The raw `source` string is kept purely for log/display output
(tests still assert on it).
NetworkError grows from a single String bag into a structured variant
{ kind: NetworkErrorKind, url: Option<String>, detail: String }:

- Timeout, Connect, Status(u16), Tls, Other
- is_retryable() marks transient failures (timeout, connect, 5xx, 429)
- format_error hints are kind-specific: a 403/401 now points at
  User-Agent filtering (the exact failure mode we saw on the
  subscription panel) instead of the generic "check your network"

SourceLoader::load_from_url now honors config.retry_count (previously
declared-but-unused) with exponential backoff (250ms, 500ms, 1s,
capped at 4s). 4xx/DNS errors skip the retry loop.

Add wiremock integration tests (tests/url_fetch_test.rs):
- happy-path HTTP fetch + parse
- 403 surfaces as Status(403) variant (regression guard for the
  UA-filtering scenario)
- transient 503 is retried and succeeds on the second attempt
- 404 is NOT retried (expect(1) asserts the hit count)
GitHub deprecated Node 20 actions — runners will force Node 24 from
June 2nd 2026 and remove Node 20 entirely on September 16th 2026.
Move every flagged action to its Node-24 release line:

- actions/checkout        v4     -> v6
- Swatinem/rust-cache     v2.8.2 -> v2.9.1
- actions/upload-artifact v4     -> v7
- actions/download-artifact v4   -> v7

dtolnay/rust-toolchain@v1 and softprops/action-gh-release@v2 were not
flagged and stay as-is.

Note: upload/download-artifact v5+ require Actions Runner 2.327.1 or
newer. GitHub-hosted runners already satisfy this; self-hosted ones
need to be up-to-date.
@bitxwave bitxwave merged commit 77a0207 into main May 1, 2026
7 checks passed
@bitxwave bitxwave deleted the refactor/architecture branch May 1, 2026 09:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant