Skip to content

Fix #230: API action adapter should pass schema params as positional args#232

Merged
ding113 merged 2 commits intodevfrom
fix/issue-230-api-statistics-body-parsing
Nov 28, 2025
Merged

Fix #230: API action adapter should pass schema params as positional args#232
ding113 merged 2 commits intodevfrom
fix/issue-230-api-statistics-body-parsing

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Nov 28, 2025

Close #230

Summary

  • Fixed the createActionRoute function in the OpenAPI action adapter to properly extract and pass request body parameters as positional arguments to Server Actions
  • Previously, the entire body object was passed as a single argument, causing actions like getUserStatistics(timeRange) to receive { timeRange: "hour" } instead of the expected "hour" string

Root Cause

The error Invalid time range: [object Object] occurred because:

  1. API request: POST /api/actions/statistics/getUserStatistics with body { "timeRange": "hour", "userId": 1 }
  2. The adapter called action(body) which passed the entire object as the first parameter
  3. The action function getUserStatistics(timeRange) expected a string but received the whole object

Changes

  • Modified src/lib/api/action-adapter-openapi.ts to extract parameters from the request body based on the schema keys and pass them as positional arguments
  • Single parameter actions now receive just the value, not the wrapper object
  • Multi-parameter actions receive values in schema key order

Test plan

  • Verify POST /api/actions/statistics/getUserStatistics with { "timeRange": "hour" } returns valid statistics
  • Verify other single-parameter actions (getKeys, removeUser, etc.) work correctly
  • Verify multi-parameter actions continue to work

🤖 Generated with Claude Code

…lose #230

The createActionRoute function was passing the entire request body object
as a single argument to Server Actions. This broke actions that expect
positional parameters (e.g., `getUserStatistics(timeRange)` receiving
`{ timeRange: "hour" }` instead of `"hour"`).

The fix extracts parameters from the request body based on the schema
keys and passes them as positional arguments to match the action
function signatures.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ding113, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical bug in the OpenAPI action adapter that caused Server Actions to receive malformed arguments from API requests. By implementing a more sophisticated parameter extraction mechanism based on the defined request schema, the adapter now correctly translates incoming JSON bodies into the expected positional arguments for Server Actions. This fix resolves type mismatch errors, such as passing an object when a string was expected, thereby enhancing the reliability and correctness of API integrations with Server Actions.

Highlights

  • Parameter Handling Fix: Corrects an issue in the OpenAPI action adapter where request body parameters were not passed as distinct positional arguments to Server Actions.
  • Type Mismatch Resolution: Resolves a bug where Server Actions expecting primitive types (e.g., a string) would incorrectly receive an entire object, leading to type errors like "Invalid time range: [object Object]". This specifically addresses issue Bug: API接口获取统计数据失败 #230.
  • Schema-Based Argument Extraction: The createActionRoute function now intelligently extracts arguments from the request body based on the requestSchema definition, ensuring parameters are passed in the correct order and format.
  • Support for Variadic Arguments: The adapter now correctly handles actions with single parameters (passing just the value) and multiple parameters (passing values in schema-defined order), improving compatibility with various Server Action signatures.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@ding113 ding113 added bug Something isn't working size/XS Extra Small PR (< 50 lines) labels Nov 28, 2025
@ding113
Copy link
Owner Author

ding113 commented Nov 28, 2025

🔒 Security Scan Results

No security vulnerabilities detected

This PR has been scanned against OWASP Top 10, CWE Top 25, and common security anti-patterns. No security issues were identified in the code changes.

📋 OWASP Top 10 Coverage

  • A01: Injection - Clean (no SQL/NoSQL/command injection vectors introduced)
  • A02: Broken Authentication - Clean (auth logic unchanged)
  • A03: Sensitive Data Exposure - Clean (no new data exposure)
  • A04: XML External Entities - N/A (no XML parsing)
  • A05: Broken Access Control - Clean (authorization unchanged)
  • A06: Security Misconfiguration - Clean (no config changes)
  • A07: XSS - N/A (API endpoint, no rendering)
  • A08: Insecure Deserialization - Clean (existing JSON parsing)
  • A09: Known Vulnerabilities - Clean (no new dependencies)
  • A10: Logging & Monitoring - Clean (logging unchanged)

Additional Security Checks

  • Input Validation: Schema-based extraction uses Object.keys(schemaShape) ensuring only schema-defined keys are extracted
  • Parameter Pollution: Only parameters defined in Zod schema are passed to actions
  • SSRF: No URL handling changes
  • Path Traversal: No file path handling

🛡️ Security Posture

Strong - The change modifies parameter passing logic but maintains existing security controls:

  • Authentication checks remain intact (lines 199-221)
  • Role-based authorization is preserved
  • Zod schema validation continues to enforce input constraints
  • The refactor properly scopes parameter extraction to schema-defined keys only

🤖 Automated security scan by Claude AI - OWASP Top 10 & CWE coverage

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request fixes an issue where API action parameters were passed as a single object instead of positional arguments. The change correctly extracts parameters from the request body based on the Zod schema and passes them as individual arguments to the server action.

My review includes two main points:

  1. A critical issue regarding how the request body is obtained. The current implementation bypasses the framework's validation, which can lead to runtime errors. I've recommended using c.req.valid('json') for safe and validated data access.
  2. A high-severity issue that identifies a regression for actions that expect the full request body but have no parameters defined in their schema. I've suggested a fix to maintain backward compatibility for these cases.

Comment on lines 233 to 249
if (requestSchema instanceof z.ZodObject) {
const schemaShape = requestSchema.shape;
const keys = Object.keys(schemaShape);
if (keys.length === 0) {
// 没有参数
args = [];
} else if (keys.length === 1) {
// 单个参数,直接传递值
args = [body[keys[0] as keyof typeof body]];
} else {
// 多个参数,按 schema key 顺序传递
args = keys.map((key) => body[key as keyof typeof body]);
}
} else {
// 非对象 schema,直接传递整个 body
args = [body];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

While this new logic correctly extracts positional arguments, it relies on the body variable, which is obtained unsafely on line 224. This makes the new code susceptible to runtime errors and bypasses the framework's validation features.

The Problem:
The current use of const body = await c.req.json().catch(() => ({})) on line 224 has two major flaws in a hono/zod-openapi application:

  1. Bypasses Schema Validation: The framework is designed to validate the request body against the requestSchema before this handler code is executed. The correct way to access this validated, parsed, and typed data is via c.req.valid('json'). By calling c.req.json() again, you are re-parsing the body and completely ignoring the validation that has already occurred.
  2. Hides Errors: If a client sends malformed JSON, the .catch(() => ({})) will cause body to become an empty object. Your new logic (e.g., on lines 241 and 244) will then attempt to access properties on this empty object, resulting in undefined arguments being passed to your action. This can lead to subtle bugs or crashes that are hard to debug.

Recommendation:
To ensure correctness and robustness, line 224 should be changed to:

const body = c.req.valid("json");

Although this line is outside the current diff, this change is critical for the stability of the logic you've introduced. It will guarantee that body is a type-safe, validated object that conforms to your Zod schema, making your new argument extraction code safe and reliable.

Comment on lines +237 to +238
// 没有参数
args = [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When the schema is an object with no defined keys (e.g., z.object({}).passthrough()), this logic calls the action with no arguments. This is a regression from the previous behavior, which passed the entire body object.

This will break actions that expect the whole request body as a single object but don't have properties formally defined in the schema (relying on passthrough for dynamic keys).

To maintain backward compatibility for this scenario, you should pass the body as a single argument.

Suggested change
// 没有参数
args = [];
// 没有参数,但可能是 passthrough,传递整个 body 对象以保持兼容
args = [body];

Copy link
Owner Author

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Code Review Summary

This PR aims to fix issue #230 where API actions were receiving the entire body object instead of extracted parameters. While the fix correctly handles single-parameter actions, it introduces a critical regression for multi-parameter actions.

🔍 Issues Found

  • Critical (🔴): 1 issue
  • High (🟠): 0 issues
  • Medium (🟡): 0 issues
  • Low (🟢): 0 issues

🎯 Priority Actions

  1. CRITICAL: The multi-parameter branch (lines 243-246) breaks actions like editUser(userId, data), editKey(keyId, data), and editProvider(providerId, data) which expect (id, dataObject) not (id, prop1, prop2, ...)

💡 General Observations

The root cause is that there are two distinct action signature patterns in the codebase:

  1. Single primitive parameter: getUserStatistics(timeRange), removeUser(userId) - ✅ Fixed by this PR
  2. Single object parameter: addKey(data: {...}), addUser(data: {...}) - ✅ Was already working, still works
  3. ID + data object: editUser(userId, data), editKey(keyId, data) - ❌ BROKEN by this PR

The schema cannot distinguish between these patterns. A safer approach would be to keep the multi-parameter case passing the full body object (reverting to original behavior for that case).


🤖 Automated review by Claude AI - focused on identifying issues for improvement

}
} else {
// 非对象 schema,直接传递整个 body
args = [body];
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical: This fix breaks multi-parameter actions like editUser(userId, data), editKey(keyId, data), and editProvider(providerId, data).

Why this is a problem: The new logic extracts all keys from the schema and passes them as individual positional arguments. However, actions like editUser expect (userId: number, data: {...}) - two arguments where the second is an object containing remaining properties.

For example, with schema { userId, name, note, rpm, ... }:

  • Current code produces: editUser(body.userId, body.name, body.note, body.rpm, ...)
  • Expected by action: editUser(body.userId, { name, note, rpm, ... })

This will cause data parameter in editUser to receive just body.name (a string) instead of the expected object, breaking all edit operations.

Suggested fix:

// 如果 requestSchema 是对象类型,提取 keys 作为参数顺序
let args: unknown[];
if (requestSchema instanceof z.ZodObject) {
  const schemaShape = requestSchema.shape;
  const keys = Object.keys(schemaShape);
  if (keys.length === 0) {
    // 没有参数
    args = [];
  } else if (keys.length === 1) {
    // 单个参数,直接传递值
    args = [body[keys[0] as keyof typeof body]];
  } else {
    // 多个参数场景 - 保持原有行为传递整个 body 对象
    // 因为存在两种 action 签名:
    // 1. action(arg1, arg2, ...) - 多个独立参数
    // 2. action(data: {...}) - 单个对象参数包含多个属性
    // 无法从 schema 区分这两种情况,传递整个 body 更安全
    args = [body];
  }
} else {
  // 非对象 schema,直接传递整个 body
  args = [body];
}

- Change multi-parameter case to pass entire body object
- Fixes #230 for single-param actions (e.g., getUserStatistics)
- Preserves compatibility for editUser/editKey/editProvider signatures
// 因为存在 editUser(userId, data) 这类签名,无法从 schema 区分
args = [body];
}
} else {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Critical: Multi-parameter actions like editKey, editUser, editProvider will break

Why this is a problem: The fallback for keys.length > 1 passes the entire body object as a single argument. However, actions like editKey(keyId, data), editUser(userId, data), and editProvider(providerId, data) expect two separate positional arguments: an ID and a data object.

For example, when calling editKey with body { keyId: 1, name: "test" }:

  • Expected: editKey(1, { name: "test" })
  • Actual (this PR): editKey({ keyId: 1, name: "test" }) ← First param receives the whole object, second param is undefined

This is a regression that will break all edit operations.

Suggested fix: The fix needs to happen at the route definition level. Each multi-param action route should use a wrapper:

// In route.ts for editKey:
const { route: editKeyRoute, handler: editKeyHandler } = createActionRoute(
  "keys",
  "editKey",
  // Wrapper to extract positional args from body
  async (body: { keyId: number; name: string; /* ... */ }) => {
    const { keyId, ...data } = body;
    return keyActions.editKey(keyId, data);
  },
  { requestSchema: z.object({ keyId: z.number().int().positive(), ... }) }
);

Alternatively, add explicit parameter extraction config to ActionRouteOptions to specify which keys should be extracted as separate positional args.

@ding113
Copy link
Owner Author

ding113 commented Nov 28, 2025

🔒 Security Scan Results

No NEW security vulnerabilities introduced by this PR

This PR has been scanned against OWASP Top 10, CWE Top 25, and common security anti-patterns. The code changes in this PR do not introduce new security vulnerabilities.

Analysis Summary

The PR modifies src/lib/api/action-adapter-openapi.ts to change how request body parameters are extracted and passed to Server Actions:

Change Analysis:

  • Before: action(body) - passed entire body object
  • After: For single-param schemas, extracts just the value: action(body[key])

This is a functional fix (addressing issue #230) that corrects parameter passing behavior. The change does not introduce new attack vectors.

Scanned Categories

  • ✅ Injection attacks (SQL, NoSQL, Command, LDAP, etc.) - No database queries in changed code
  • ✅ Authentication and session management - No changes to auth logic
  • ✅ Sensitive data exposure - No sensitive data handling changes
  • ✅ Access control and authorization - Authorization checks preserved
  • ✅ Security misconfiguration - No configuration changes
  • ✅ Cross-site scripting (XSS) - Server-side code only
  • ✅ Insecure deserialization - JSON parsing unchanged
  • ✅ SSRF and path traversal - No URL/path handling
  • ✅ Cryptographic weaknesses - No crypto operations

Pre-existing Observation (Not Introduced by This PR)

⚠️ Note: The codebase uses c.req.json() instead of c.req.valid('json') for request body parsing (line 224). The @hono/zod-openapi framework provides automatic validation via c.req.valid(), but the current implementation parses JSON without schema validation before passing to actions. This is a pre-existing pattern not introduced by this PR. The downstream Server Actions may have their own validation, but the API adapter layer could be strengthened by using the framework's built-in validation capabilities.


🤖 Automated security scan by Claude AI - OWASP Top 10 & CWE coverage

Copy link
Owner Author

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Code Review Summary

This PR fixes the body parsing issue for single-parameter Server Actions by extracting the parameter value from the body object instead of passing the entire object. The fix correctly addresses issue #230 for single-param actions like getUserStatistics(timeRange).

🔍 Issues Found

  • Critical (🔴): 0 issues (my previous comment was inaccurate - see clarification below)
  • High (🟠): 0 issues
  • Medium (🟡): 1 issue (documentation/comment accuracy)
  • Low (🟢): 0 issues

🎯 Priority Actions

  1. Clarification on my previous comment: After deeper analysis, I realized the multi-param issue I flagged is not a regression - the existing code (action(body)) already didn't work for editUser(userId, data) style signatures. The PR correctly maintains this existing behavior while fixing single-param cases. The code comment accurately documents this limitation.

  2. Minor suggestion: The comment on line 227 claims compatibility with action(arg1, arg2, ...) but the implementation only supports action(singleArg) and action({...}) patterns. Consider updating the comment to be more precise:

    // 这样可以兼容 action(singleValue) 和 action({ arg1, arg2, ... }) 两种签名
    // 注意:不支持 action(arg1, arg2, ...) 多个位置参数的签名

💡 General Observations

  • The fix is correctly scoped to address the specific issue reported (#230)
  • The single-param case extraction logic is sound
  • For multi-param positional-args signatures like editUser(userId, data), the current API routes already pass the whole body object, which means these routes were already broken before this PR. This is a pre-existing architectural limitation, not something introduced by this change.
  • Future consideration: If multi-param positional actions need API support, wrapper functions should be added at the route definition level in route.ts

🤖 Automated review by Claude AI - focused on identifying issues for improvement

Copy link
Owner Author

@ding113 ding113 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📋 Code Review Summary

This PR fixes the body parsing issue for single-parameter Server Actions by extracting the parameter value from the body object instead of passing the entire object. The fix correctly addresses issue #230 for single-param actions like getUserStatistics(timeRange).

🔍 Issues Found

  • Critical (🔴): 0 issues (my previous comment was inaccurate - see clarification below)
  • High (🟠): 0 issues
  • Medium (🟡): 1 issue (documentation/comment accuracy)
  • Low (🟢): 0 issues

🎯 Priority Actions

  1. Clarification on my previous comment: After deeper analysis, I realized the multi-param issue I flagged is not a regression - the existing code (action(body)) already did not work for editUser(userId, data) style signatures. The PR correctly maintains this existing behavior while fixing single-param cases. The code comment accurately documents this limitation.

  2. Minor suggestion (🟡 Medium): The comment on line 227 claims compatibility with action(arg1, arg2, ...) but the implementation only supports action(singleArg) and action({...}) patterns. Consider updating the comment to be more precise:

    // 这样可以兼容 action(singleValue) 和 action({ arg1, arg2, ... }) 两种签名
    // 注意:不支持 action(arg1, arg2, ...) 多个位置参数的签名

💡 General Observations

  • The fix is correctly scoped to address the specific issue reported (#230)
  • The single-param case extraction logic is sound
  • For multi-param positional-args signatures like editUser(userId, data), the current API routes already pass the whole body object, which means these routes were already not working before this PR. This is a pre-existing architectural limitation, not something introduced by this change.
  • Future consideration: If multi-param positional actions need API support, wrapper functions should be added at the route definition level in route.ts

🤖 Automated review by Claude AI - focused on identifying issues for improvement

@ding113 ding113 merged commit 59fb9da into dev Nov 28, 2025
10 of 11 checks passed
@ding113 ding113 deleted the fix/issue-230-api-statistics-body-parsing branch November 28, 2025 16:48
ding113 pushed a commit that referenced this pull request Nov 28, 2025
@ding113 ding113 mentioned this pull request Nov 28, 2025
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working size/XS Extra Small PR (< 50 lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments