Skip to content

Swagger 生成时验证路径参数与路由占位符的匹配性 (Closes #5428)#5520

Draft
ljluestc wants to merge 6 commits into
zeromicro:masterfrom
ljluestc:fix/base64-newline-handling
Draft

Swagger 生成时验证路径参数与路由占位符的匹配性 (Closes #5428)#5520
ljluestc wants to merge 6 commits into
zeromicro:masterfrom
ljluestc:fix/base64-newline-handling

Conversation

@ljluestc
Copy link
Copy Markdown

当请求结构体中声明了 path:"id" 标记的字段,但路由路径中不包含对应的占位符(如 /:id{id})时,goctl 目前仍会生成 in: path 的参数,导致 Swagger 文档语义不一致(OpenAPI 规范要求路径参数必须出现在 URL 模板中)。

问题示例

type Req {
  ID string `path:"id"`  // 声明了路径参数
}

@handler H
get /foo (Req) returns ()  // 路径 /foo 中没有 {/:id} 占位符

生成的 Swagger 结果(错误):

{
  "type": "string",
  "name": "id",
  "in": "path",    // 参数位置为 path
  "required": true
}

但实际路径是 /foo,不包含 {id},这是无效的 Swagger 文档。

解决方案

在生成路径参数前,解析路由路径中的占位符(支持 :id{id} 两种格式),仅当路径中存在匹配的占位符时才生成对应的路径参数。对于未匹配的路径参数,静默跳过以避免生成无效的 Swagger 文档。

核心改动

1. 新增 extractPathPlaceholders() 函数 (tools/goctl/api/swagger/vars.go)

  • 支持解析 :id 风格的 go-zero 占位符
  • 支持解析 {id} 风格的 OpenAPI 占位符
  • 返回路由路径中所有有效的路径参数名称集合

2. 修改 parametersFromType() 函数 (tools/goctl/api/swagger/parameter.go)

  • 新增 routePath string 参数,接收原始路由路径
  • 在生成路径参数前调用 extractPathPlaceholders() 获取允许的占位符集
  • 仅当 pathParameterTag.Name 在占位符集合中时才生成参数
  • 未匹配的路径参数被静默过滤

3. 更新 spec2Path() 函数 (tools/goctl/api/swagger/path.go)

  • 调用 parametersFromType() 时传入 route.Path

4. 新增单元测试

  • vars_test.go: 测试占位符提取逻辑(14 个测试用例)

    • 覆盖空路径、无占位符
    • 单/多占位符
    • 混合格式 (:id{id})
    • 边缘情况(空占位符名称、尾随斜杠等)
  • parameter_path_filter_test.go: 测试路径参数验证逻辑(6 个测试用例)

    • 覆盖匹配/不匹配场景
    • 多个路径参数部分匹配的场景
    • 查询参数不受影响的验证
  • 修复 parameter_test.go: 兼容新的参数签名

测试结果

=== RUN   TestExtractPathPlaceholders
    --- PASS: empty_path
    --- PASS: no_placeholders
    --- PASS: single_:id_style_placeholder
    --- PASS: single_{id}_style_placeholder
    --- PASS: multiple_:id_style_placeholders
    --- PASS: multiple_{id}_style_placeholders
    --- PASS: mixed_style_placeholders
    --- PASS: placeholder_at_root
    --- PASS: placeholder_after_static_segments
    --- PASS: empty_placeholder_name_with_colon
    --- PASS: empty_placeholder_name_with_braces
    --- PASS: trailing_slash_with_placeholder
    --- PASS: complex_path_with_multiple_placeholders
--- PASS

=== RUN   TestExtractPathPlaceholders_Duplicates
--- PASS

=== RUN   TestExtractPathPlaceholders_CaseSensitive
--- PASS

=== RUN   TestParametersFromType_PathParameterValidation
    --- PASS: path parameter matches route placeholder
    --- PASS: path parameter matches {id} style placeholder
    --- PASS: path parameter does NOT match route placeholder - should be filtered out
    --- PASS: multiple path parameters, only some match route placeholders
    --- PASS: no path tag - no path params should be generated
    --- PASS: mixed route path - colon and brace styles
--- PASS

=== RUN   TestParametersFromType_PathParameterFilteringWithQueryString
--- PASS

PASS
ok   github.com/zeromicro/go-zero/tools/goctl/api/swagger 0.003s

ljluestc and others added 6 commits March 22, 2026 23:33
…ger generation (zeromicro#5428)

Fix swagger generation to only include path parameters when the route path contains matching placeholders (:id or {id}).

- Add extractPathPlaceholders() to parse both :id and {id} style placeholders
- Make parametersFromType() accept routePath and filter path parameters
- Unmatched path parameters are silently skipped to avoid invalid swagger output
- Add comprehensive tests for placeholder extraction and validation logic
Fixes zeromicro#4710

Problem:
Android's Base64.encodeToString() method adds line breaks by default,
causing IllegalHeaderValueException when used with Ktor HTTP clients.
HTTP headers cannot contain line breaks or other special characters.

Root Cause:
The DecryptBase64() function used base64.StdEncoding.DecodeString() which
doesn't handle strings with newlines or whitespace. Clients using
Base64.encodeToString() without NO_WRAP flag would send corrupted Base64
strings in X-Content-Security headers, causing decryption failures.

Solution:
Modified DecryptBase64() to strip newlines (\n), carriage returns (\r),
and whitespace from Base64 strings before decoding. This makes the server
compatible with:
- Android Base64.encodeToString() (with default line wrapping)
- Standard Base64 without wrapping
- Strings with extra whitespace or carriage returns

This change is backward compatible - it doesn't affect clients already
sending clean Base64 strings, while enabling compatibility with clients
that add line breaks.

Changes:
- Modified core/codec/rsa.go: DecryptBase64() method
- Added test TestBase64WithNewlines() in core/codec/rsa_test.go
Strip newline characters from the inner Base64-encoded key after RSA
decryption in ParseContentSecurity. This fixes compatibility with
Android clients where Base64.encodeToString() adds line breaks by
default (MIME-style), which causes base64.StdEncoding.DecodeString
to fail on the server side.

Closes zeromicro#4710
去除 ParseContentSecurity 中 signature 字段的 Base64 编码字符串中的换行符
(\n 和 \r),以支持使用 Base64.encodeToString() 默认添加换行符的平台
(如 Android)。同时添加新的测试用例验证签名带换行符时的正确性

Closes zeromicro#4710
Co-Authored-By: Oz <oz-agent@warp.dev>
@kevwan kevwan added enhancement New feature or request area/goctl Categorizes issue or PR as related to goctl. kind/in-progress Issues and PRs that in progress labels Apr 30, 2026
@kevwan
Copy link
Copy Markdown
Contributor

kevwan commented Apr 30, 2026

Translation / English summary:

This draft PR adds validation in goctl swagger generation to filter out path parameters (fields with path:"id" tag) when the corresponding placeholder (:id or {id}) does not exist in the route URL. This prevents generating invalid Swagger documents that violate the OpenAPI spec requirement that path parameters must appear in the URL template (fixes #5428).

Original (原文): 当请求结构体中声明了 path:"id" 标记的字段,但路由路径中不包含对应的占位符时,goctl 仍会生成 in: path 参数,导致 Swagger 文档不符合 OpenAPI 规范。


Review

Concept: Correct and necessary — the OpenAPI spec requires all in: path parameters to have matching URL template parameters. Silently generating invalid swagger is a real problem.

Changes:

  • extractPathPlaceholders() in vars.go — parses :id and {id} style placeholders from the route path
  • parametersFromType() gets a new routePath parameter and filters out unmatched path params
  • spec2Path() passes route.Path to parametersFromType()

Concerns:

  1. Signature change to parametersFromType() — This is a breaking change to an internal function. Are there any other callers that need to be updated? Please check all call sites.

  2. Silent filtering vs. error/warning — Currently, unmatched path params are silently skipped. This could cause confusion if a user has a typo (e.g., path:"idd" but route has :id) — the parameter just vanishes. Consider emitting a logx.Warn or returning an error, at minimum in verbose mode.

  3. Test coverage — The 14-case test for extractPathPlaceholders is thorough. The 6-case path parameter filtering test looks good. ✅

Good work overall. Please mark ready for review once you've addressed the signature change concern and considered the silent-drop behavior. Also, please include an English description in your PR body to help all contributors review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/goctl Categorizes issue or PR as related to goctl. enhancement New feature or request kind/in-progress Issues and PRs that in progress

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants