Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 7
refactor: flatten client structure and add Zod validation to RPC client#168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
f40725b1834251928562aae6009b6b58654bf9336b0afc02720b5c7908fcbebbcfdcd92a1f57fb3e3a9c2b7bfcbc3b30e0034b90532bf1ffFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Uh oh!
There was an error while loading. Please reload this page.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -32,16 +32,12 @@ catalogs: | ||
| prod: | ||
| '@modelcontextprotocol/sdk': ^1.19.1 | ||
| '@orama/orama': ^3.1.11 | ||
| '@stackone/stackone-client-ts': 4.32.2 | ||
| json-schema: ^0.4.0 | ||
Comment on lines
32
to
35
CopilotAI | ||
| enablePrePostScripts: true | ||
| minimumReleaseAge: 1440 | ||
| minimumReleaseAgeExclude: | ||
| - '@stackone/stackone-client-ts' | ||
| onlyBuiltDependencies: | ||
| - '@biomejs/biome' | ||
| - esbuild | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,104 @@ | ||||||
| import { RpcClient } from './rpc-client'; | ||||||
| import { StackOneAPIError } from './utils/errors'; | ||||||
| test('should successfully execute an RPC action', async () => { | ||||||
| const client = new RpcClient({ | ||||||
| security: { username: 'test-api-key' }, | ||||||
| }); | ||||||
| const response = await client.actions.rpcAction({ | ||||||
| action: 'hris_get_employee', | ||||||
| body: { fields: 'name,email' }, | ||||||
| path: { id: 'emp-123' }, | ||||||
| }); | ||||||
| // Response matches server's ActionsRpcResponseApiModel structure | ||||||
| expect(response).toHaveProperty('data'); | ||||||
| expect(response.data).toMatchObject({ | ||||||
| id: 'emp-123', | ||||||
| name: 'Test Employee', | ||||||
| }); | ||||||
| }); | ||||||
| test('should send correct payload structure', async () => { | ||||||
| const client = new RpcClient({ | ||||||
| security: { username: 'test-api-key' }, | ||||||
| }); | ||||||
| const response = await client.actions.rpcAction({ | ||||||
| action: 'custom_action', | ||||||
| body: { key: 'value' }, | ||||||
| headers: { 'x-custom': 'header' }, | ||||||
| path: { id: '123' }, | ||||||
| query: { filter: 'active' }, | ||||||
| }); | ||||||
| // Response matches server's ActionsRpcResponseApiModel structure | ||||||
| expect(response.data).toMatchObject({ | ||||||
| action: 'custom_action', | ||||||
| received: { | ||||||
| body: { key: 'value' }, | ||||||
| headers: { 'x-custom': 'header' }, | ||||||
| path: { id: '123' }, | ||||||
| query: { filter: 'active' }, | ||||||
| }, | ||||||
| }); | ||||||
| }); | ||||||
| test('should handle list actions with array data', async () => { | ||||||
| const client = new RpcClient({ | ||||||
| security: { username: 'test-api-key' }, | ||||||
| }); | ||||||
| const response = await client.actions.rpcAction({ | ||||||
| action: 'hris_list_employees', | ||||||
| }); | ||||||
| // Response data can be an array (matches RpcActionResponseData union type) | ||||||
| expect(Array.isArray(response.data)).toBe(true); | ||||||
| expect(response.data).toMatchObject([ | ||||||
| { id: expect.any(String), name: expect.any(String) }, | ||||||
| { id: expect.any(String), name: expect.any(String) }, | ||||||
| ]); | ||||||
| }); | ||||||
| test('should throw StackOneAPIError on server error', async () => { | ||||||
| const client = new RpcClient({ | ||||||
| security: { username: 'test-api-key' }, | ||||||
| }); | ||||||
| await expect( | ||||||
| client.actions.rpcAction({ | ||||||
| action: 'test_error_action', | ||||||
| }) | ||||||
| ).rejects.toThrow(StackOneAPIError); | ||||||
| }); | ||||||
| test('should include request body in error for debugging', async () => { | ||||||
| const client = new RpcClient({ | ||||||
| security: { username: 'test-api-key' }, | ||||||
| }); | ||||||
| await expect( | ||||||
| client.actions.rpcAction({ | ||||||
| action: 'test_error_action', | ||||||
| body: { debug: 'data' }, | ||||||
| }) | ||||||
| ).rejects.toMatchObject({ | ||||||
| statusCode: 500, | ||||||
| requestBody: { action: 'test_error_action' }, | ||||||
| ||||||
| requestBody: {action: 'test_error_action'}, | |
| requestBody: {action: 'test_error_action',body: {debug: 'data'}}, |
| Original file line number | Diff line number | Diff line change | ||
|---|---|---|---|---|
| @@ -0,0 +1,149 @@ | ||||
| import { z } from 'zod'; | ||||
| import { StackOneAPIError } from './utils/errors'; | ||||
Comment on lines
+2
to
+3
CopilotAI | ||||
| import { StackOneAPIError } from './utils/errors'; |
cubic-dev-aiBotDec 9, 2025 •
edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: Rule violated: Flag Security Vulnerabilities
The serverURL config parameter accepts any string without validating HTTPS. Since authentication credentials (Basic auth header with username/password) are sent to this URL, a misconfigured HTTP URL would transmit credentials in plaintext. Add HTTPS validation to the schema to prevent credential exposure.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/rpc-client.ts, line 37:
<comment>The `serverURL` config parameter accepts any string without validating HTTPS. Since authentication credentials (Basic auth header with username/password) are sent to this URL, a misconfigured HTTP URL would transmit credentials in plaintext. Add HTTPS validation to the schema to prevent credential exposure.</comment>
<file context>
@@ -0,0 +1,128 @@
+ * Zod schema for RPC client configuration validation
+ */
+const rpcClientConfigSchema = z.object({
+ serverURL: z.string().optional(),
+ security: z.object({
+ username: z.string(),
</file context>
CopilotAIDec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handling for response validation uses response.status which would be 200 (OK) when validation fails. This is misleading since the issue is with the response format, not an HTTP error. Consider using a different status code (e.g., 500) or creating a separate error type for validation failures.
Additionally, response.json() on line 99 can throw if the response body is not valid JSON, but this is not caught. Consider wrapping it in a try-catch to handle malformed responses gracefully.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
CopilotAIDec 9, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
rpc-client.tsfile imports and uses Zod for runtime validation, butzodis listed indevDependenciesinstead ofdependencies. This will cause runtime errors when the package is installed in production environments.Move
zodfromdevDependenciestodependenciesto ensure it's available at runtime.