Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

All notable changes to NetworkingKit are documented in this file.

## 2.4.0 - 2026-07-23

### Added

- Add `BackendReferencePlugin` and its generator for build-time HTML references of app backend servers, configuration values, feature-grouped endpoints, request parameters, and source locations.
- Add generated backend-reference HTML to the NetworkingKit Demo.

## 2.3.8 - 2026-07-19

### Changed
Expand Down
54 changes: 54 additions & 0 deletions Docs/BackendReferencePlugin.zh-Hans.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# 后端 API HTML 索引

`BackendReferencePlugin` 会在每次编译 App Target 时扫描 Swift 源码,生成后端服务器、Feature、端点、请求参数与网络配置的 HTML 索引。默认推荐使用 Build Tool Plugin:无需维护 `--package-path` 或额外脚本,且始终与本次构建使用相同的源码。

## 推荐:Build Tool Plugin

### Xcode 配置步骤

1. 使用 **File → Add Package Dependencies…** 将 `NetworkingKit` 添加到工程。
2. 选中 App Target,打开 **Build Phases**。
3. 在 **Run Build Tool Plug-ins** 区域点击 `+`,选择 `BackendReferencePlugin`。
4. 直接 Build App。构建日志会显示 **Generate backend API reference**。
5. 在 Xcode Report navigator 中展开该构建步骤;HTML 位于该 Target 对应的 Derived Data 插件输出目录,入口文件为 `BackendAPIReference/index.html`。在 Finder 中打开该目录即可浏览文档。

Build Tool Plugin 受 SwiftPM 沙盒保护,只能写入 Derived Data 的插件工作目录,不能写回 App 的源码根目录。这是它无需路径配置、可安全在本地与 CI 自动运行的原因。

## 备用:导出到 App 根目录

只有需要将 HTML 固定保存为 `$SRCROOT/BackendAPIReference/`、提交到制品库或交付给非 Xcode 用户时,才在 App Target 最后添加一个 **Run Script Build Phase**。该备用方式需要为 Package checkout 提供路径:

```sh
swift run --package-path "$NETWORKING_KIT_CHECKOUT" BackendReferenceGenerator \
--source-directory "$SRCROOT" \
--output-directory "$SRCROOT/BackendAPIReference" \
--stamp "$DERIVED_FILE_DIR/backend-reference-export.stamp"
```

配置步骤:

1. 在 Scheme 或 CI 中定义 `NETWORKING_KIT_CHECKOUT`,值为 `NetworkingKit` 本地 checkout 的绝对路径。
2. 在 App Target 的 **Build Phases** 点击 `+ → New Run Script Phase`,并将该步骤放在编译步骤之后。
3. 粘贴上面的脚本,Build 一次。
4. 打开 `$SRCROOT/BackendAPIReference/index.html`。该目录只包含开发文档,不应加入 App bundle;如不需要提交,可加入 `.gitignore`。

如果只需要在开发和 CI 中查看文档,请保持仅使用 Build Tool Plugin;Run Script 是输出位置必须为 App 根目录时的次要方案。

## 识别规则

- `NetworkClient` / `SharedNetworkClient` 的 `baseURL` 表示一个后端服务器。
- `protocol AppNetworkRequest: NetworkRequest where Client == AppNetworkClient` 将请求关联至服务器。
- `RestfulRequest` 和 `GraphQLRequest` 会被识别为端点。
- 请求声明上方最近的 `// MARK: - Feature 名称` 作为 Feature 分组。
- 请求的存储属性列为参数,客户端 `configuration` 会在服务器页展示。

```swift
// MARK: - Characters
struct GetCharacterRequest: AppNetworkRequest, RestfulRequest {
private let id: String
var path: String { "/api/character/\(id)" }
var method: HTTPMethod { .get }
}
```

静态扫描不会执行 Swift 代码。动态 URL、HTTP 方法及复杂表达式会保留源码形式或显示为 `<dynamic>`。
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>AppNetworkClient</title><style>body{font:16px -apple-system,BlinkMacSystemFont,sans-serif;max-width:1100px;margin:48px auto;padding:0 24px;color:#172033}a{color:#1264d6;text-decoration:none}h1{margin-bottom:8px}.servers{padding:0;display:grid;gap:12px}.servers li{list-style:none;border:1px solid #dce3ef;border-radius:10px}.servers a{display:grid;padding:18px;gap:6px}.servers span,small{color:#526078}dl{display:grid;grid-template-columns:140px 1fr;gap:10px}dt{font-weight:600}dd{margin:0}section{margin-top:38px}table{border-collapse:collapse;width:100%;text-align:left}th,td{padding:12px;border-bottom:1px solid #dce3ef;vertical-align:top}th{background:#f5f7fb}code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.9em}</style></head><body><p><a href="index.html">← All servers</a></p><h1>AppNetworkClient</h1><p><strong>Base URL:</strong> <code>https://rickandmortyapi.com</code></p><section><h2>Configuration</h2><table><thead><tr><th>Configuration</th><th>Value</th></tr></thead><tbody><tr><td>timeoutInterval</td><td><code>15</code></td></tr><tr><td>retryPolicy</td><td><code>RetryPolicy(maxAttempts: 2)</code></td></tr><tr><td>errorLocalizer</td><td><code>AppNetworkErrorLocalizer()</code></td></tr></tbody></table></section><section><h2>GraphQL</h2><table><thead><tr><th>Method</th><th>Endpoint</th><th>Kind</th><th>Parameters</th><th>Request</th><th>Source</th></tr></thead><tbody><tr><td><code>POST</code></td><td><code>/graphql</code></td><td>GraphQL</td><td>id: String</td><td><code>FetchCharacterProfileRequest</code></td><td><code>DemoViewModel.swift</code></td></tr></tbody></table></section>
<section><h2>REST</h2><table><thead><tr><th>Method</th><th>Endpoint</th><th>Kind</th><th>Parameters</th><th>Request</th><th>Source</th></tr></thead><tbody><tr><td><code>GET</code></td><td><code>&quot;/api/character/\(id)&quot;</code></td><td>REST</td><td>id: String</td><td><code>GetCharacterRequest</code></td><td><code>DemoViewModel.swift</code></td></tr></tbody></table></section></body></html>
1 change: 1 addition & 0 deletions Examples/NetworkingKitDemo/BackendAPIReference/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Backend API Reference</title><style>body{font:16px -apple-system,BlinkMacSystemFont,sans-serif;max-width:1100px;margin:48px auto;padding:0 24px;color:#172033}a{color:#1264d6;text-decoration:none}h1{margin-bottom:8px}.servers{padding:0;display:grid;gap:12px}.servers li{list-style:none;border:1px solid #dce3ef;border-radius:10px}.servers a{display:grid;padding:18px;gap:6px}.servers span,small{color:#526078}dl{display:grid;grid-template-columns:140px 1fr;gap:10px}dt{font-weight:600}dd{margin:0}section{margin-top:38px}table{border-collapse:collapse;width:100%;text-align:left}th,td{padding:12px;border-bottom:1px solid #dce3ef;vertical-align:top}th{background:#f5f7fb}code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:.9em}</style></head><body><h1>Backend API Reference</h1><p>Generated from the app source during build.</p><ul class="servers"><li><a href="AppNetworkClient.html"><strong>AppNetworkClient</strong><span>https://rickandmortyapi.com</span><small>2 endpoints</small></a></li></ul></body></html>
14 changes: 14 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,26 @@ let package = Package(
name: "NetworkingKit",
targets: ["NetworkingKit"]
),
.plugin(
name: "BackendReferencePlugin",
targets: ["BackendReferencePlugin"]
),
],
targets: [
.target(
name: "NetworkingKit",
path: "Sources/NetworkingKit"
),
.executableTarget(
name: "BackendReferenceGenerator",
path: "Sources/BackendReferenceGenerator"
),
.plugin(
name: "BackendReferencePlugin",
capability: .buildTool(),
dependencies: ["BackendReferenceGenerator"],
path: "Plugins/BackendReferencePlugin"
),
.testTarget(
name: "NetworkingKitTests",
dependencies: ["NetworkingKit"],
Expand Down
26 changes: 26 additions & 0 deletions Plugins/BackendReferencePlugin/plugin.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import PackagePlugin
import Foundation

@main
struct BackendReferencePlugin: BuildToolPlugin {
func createBuildCommands(context: PluginContext, target: Target) throws -> [Command] {
guard let sourceTarget = target as? SourceModuleTarget else { return [] }

let tool = try context.tool(named: "BackendReferenceGenerator")
let stamp = context.pluginWorkDirectoryURL.appendingPathComponent("backend-reference.stamp")
let outputDirectory = context.pluginWorkDirectoryURL.appendingPathComponent("BackendAPIReference", isDirectory: true)

return [
.buildCommand(
displayName: "Generate backend API reference",
executable: tool.url,
arguments: [
"--source-directory", sourceTarget.directory.string,
"--output-directory", outputDirectory.path,
"--stamp", stamp.path
],
outputFiles: [stamp]
)
]
}
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ Or add the package manifest dependency:

```swift
dependencies: [
.package(url: "https://github.com/relaxfinger/NetworkingKit.git", from: "2.3.8")
.package(url: "https://github.com/relaxfinger/NetworkingKit.git", from: "2.4.0")
]
```

Expand Down
3 changes: 2 additions & 1 deletion README.zh-Hans.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ https://github.com/relaxfinger/NetworkingKit.git

```swift
dependencies: [
.package(url: "https://github.com/relaxfinger/NetworkingKit.git", from: "2.3.8")
.package(url: "https://github.com/relaxfinger/NetworkingKit.git", from: "2.4.0")
]
```

Expand Down Expand Up @@ -139,6 +139,7 @@ README 只保留从安装到首个请求的最短路径。构建生产网络层
| 日志、追踪与指标 | [Observability](Docs/Observability.md) | [可观测性](Docs/Observability.zh-Hans.md) |
| 稳定错误与本地化 UI 文案 | [Errors](Docs/Errors.md) | [错误与本地化](Docs/Errors.zh-Hans.md) |
| 证书和公钥 pinning | [Security](Docs/Security.md) | [传输安全](Docs/Security.zh-Hans.md) |
| 后端服务器、端点与参数 HTML 索引 | — | [后端 API HTML 索引](Docs/BackendReferencePlugin.zh-Hans.md) |

## Demo

Expand Down
Loading
Loading