Skip to content

Commit 26c34cc

Browse files
committed
refactor: unify string list parsing logic
1 parent a2bf3b5 commit 26c34cc

File tree

6 files changed

+61
-18
lines changed

6 files changed

+61
-18
lines changed

src/mcp/actors.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { ActorDefinition } from 'apify-client';
33
import { ApifyClient } from '../apify-client.js';
44
import { MCP_STREAMABLE_ENDPOINT } from '../const.js';
55
import type { ActorDefinitionPruned } from '../types.js';
6+
import { parseCommaSeparatedList } from '../utils/generic.js';
67

78
/**
89
* Returns the MCP server path for the given Actor ID.
@@ -13,7 +14,7 @@ export function getActorMCPServerPath(actorDefinition: ActorDefinition | ActorDe
1314
if ('webServerMcpPath' in actorDefinition && typeof actorDefinition.webServerMcpPath === 'string') {
1415
const webServerMcpPath = actorDefinition.webServerMcpPath.trim();
1516

16-
const paths = webServerMcpPath.split(',').map((path) => path.trim());
17+
const paths = parseCommaSeparatedList(webServerMcpPath);
1718
// If there is only one path, return it directly
1819
if (paths.length === 1) {
1920
return paths[0];

src/stdio.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import log from '@apify/log';
2525
import { processInput } from './input.js';
2626
import { ActorsMcpServer } from './mcp/server.js';
2727
import type { Input, ToolSelector } from './types.js';
28+
import { parseCommaSeparatedList } from './utils/generic.js';
2829
import { loadToolsFromInput } from './utils/tools-loader.js';
2930

3031
// Keeping this interface here and not types.ts since
@@ -86,13 +87,9 @@ For more details visit https://mcp.apify.com`,
8687
// Respect either the new flag or the deprecated one
8788
const enableAddingActors = Boolean(argv.enableAddingActors || argv.enableActorAutoLoading);
8889
// Split actors argument, trim whitespace, and filter out empty strings
89-
const actorList = argv.actors !== undefined
90-
? argv.actors.split(',').map((a: string) => a.trim()).filter((a: string) => a.length > 0)
91-
: undefined;
90+
const actorList = argv.actors !== undefined ? parseCommaSeparatedList(argv.actors) : undefined;
9291
// Split tools argument, trim whitespace, and filter out empty strings
93-
const toolCategoryKeys = argv.tools !== undefined
94-
? argv.tools.split(',').map((t: string) => t.trim()).filter((t: string) => t.length > 0)
95-
: undefined;
92+
const toolCategoryKeys = argv.tools !== undefined ? parseCommaSeparatedList(argv.tools) : undefined;
9693

9794
// Propagate log.error to console.error for easier debugging
9895
const originalError = log.error.bind(log);

src/tools/dataset.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ApifyClient } from '../apify-client.js';
55
import { HelperTools } from '../const.js';
66
import type { InternalTool, ToolEntry } from '../types.js';
77
import { ajv } from '../utils/ajv.js';
8+
import { parseCommaSeparatedList } from '../utils/generic.js';
89
import { generateSchemaFromItems } from '../utils/schema-generation.js';
910

1011
const getDatasetArgs = z.object({
@@ -91,9 +92,9 @@ export const getDatasetItems: ToolEntry = {
9192
const client = new ApifyClient({ token: apifyToken });
9293

9394
// Convert comma-separated strings to arrays
94-
const fields = parsed.fields?.split(',').map((f) => f.trim());
95-
const omit = parsed.omit?.split(',').map((f) => f.trim());
96-
const flatten = parsed.flatten?.split(',').map((f) => f.trim());
95+
const fields = parseCommaSeparatedList(parsed.fields);
96+
const omit = parseCommaSeparatedList(parsed.omit);
97+
const flatten = parseCommaSeparatedList(parsed.flatten);
9798

9899
const v = await client.dataset(parsed.datasetId).listItems({
99100
clean: parsed.clean,

src/tools/get-actor-output.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { ApifyClient } from '../apify-client.js';
55
import { HelperTools, TOOL_MAX_OUTPUT_CHARS } from '../const.js';
66
import type { InternalTool, ToolEntry } from '../types.js';
77
import { ajv } from '../utils/ajv.js';
8-
import { getValuesByDotKeys } from '../utils/generic.js';
8+
import { getValuesByDotKeys, parseCommaSeparatedList } from '../utils/generic.js';
99

1010
/**
1111
* Zod schema for get-actor-output tool arguments
@@ -78,12 +78,7 @@ You also can retrieve only specific fields from the output if needed. Use this t
7878
const client = new ApifyClient({ token: apifyToken });
7979

8080
// Parse fields into array
81-
const fieldsArray = parsed.fields
82-
? parsed.fields
83-
.split(',')
84-
.map((field) => field.trim())
85-
.filter((field) => field.length > 0)
86-
: [];
81+
const fieldsArray = parseCommaSeparatedList(parsed.fields);
8782

8883
// TODO: we can optimize the API level field filtering in future
8984
/**

src/utils/generic.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,23 @@
88
* const value = getValuesByDotKeys(obj, ['a.b.c', 'a.b.d', 'nested']);
99
* value; // { 'a.b.c': 42, 'a.b.d': undefined, 'nested': { d: 100 } }
1010
*/
11+
/**
12+
* Parses a comma-separated string into an array of trimmed strings.
13+
* Empty strings are filtered out after trimming.
14+
*
15+
* @param input - The comma-separated string to parse. If undefined, returns an empty array.
16+
* @returns An array of trimmed, non-empty strings.
17+
* @example
18+
* parseCommaSeparatedList("a, b, c"); // ["a", "b", "c"]
19+
* parseCommaSeparatedList("a, , b"); // ["a", "b"]
20+
*/
21+
export function parseCommaSeparatedList(input?: string): string[] {
22+
if (!input) {
23+
return [];
24+
}
25+
return input.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
26+
}
27+
1128
export function getValuesByDotKeys(obj: Record<string, unknown>, keys: string[]): Record<string, unknown> {
1229
const result: Record<string, unknown> = {};
1330
for (const key of keys) {

tests/unit/utils.generic.test.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { describe, expect, it } from 'vitest';
22

3-
import { getValuesByDotKeys } from '../../src/utils/generic.js';
3+
import { getValuesByDotKeys, parseCommaSeparatedList } from '../../src/utils/generic.js';
44

55
describe('getValuesByDotKeys', () => {
66
it('should get value for a key without dot', () => {
@@ -50,3 +50,35 @@ describe('getValuesByDotKeys', () => {
5050
expect(result).toEqual({ nested: { a: 1, b: 2 } });
5151
});
5252
});
53+
54+
describe('parseCommaSeparatedList', () => {
55+
it('should parse comma-separated list with trimming', () => {
56+
const result = parseCommaSeparatedList('field1, field2,field3 ');
57+
expect(result).toEqual(['field1', 'field2', 'field3']);
58+
});
59+
60+
it('should handle empty input', () => {
61+
const result = parseCommaSeparatedList();
62+
expect(result).toEqual([]);
63+
});
64+
65+
it('should handle empty string', () => {
66+
const result = parseCommaSeparatedList('');
67+
expect(result).toEqual([]);
68+
});
69+
70+
it('should filter empty strings', () => {
71+
const result = parseCommaSeparatedList(' field1, , field2,,field3 ');
72+
expect(result).toEqual(['field1', 'field2', 'field3']);
73+
});
74+
75+
it('should handle only commas and spaces', () => {
76+
const result = parseCommaSeparatedList(' , , ');
77+
expect(result).toEqual([]);
78+
});
79+
80+
it('should handle single item', () => {
81+
const result = parseCommaSeparatedList(' single ');
82+
expect(result).toEqual(['single']);
83+
});
84+
});

0 commit comments

Comments
 (0)