Skip to content

Commit 4ecc0f1

Browse files
committed
feat: add extractor config loading
1 parent ce9a3fd commit 4ecc0f1

2 files changed

Lines changed: 143 additions & 0 deletions

File tree

src/config-schema.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { z } from "zod";
2+
import { HttpMethodMap } from "./types.js";
3+
4+
export const httpMethodSchema = z.nativeEnum(HttpMethodMap);
5+
6+
export const locationEnum = z.enum(["path", "query", "body"]);
7+
8+
export const routeOverrideParameterSchema = z.object({
9+
location: z.enum(["path", "query", "body"]),
10+
name: z.string().min(1),
11+
canonicalPath: z.string().min(1).optional(),
12+
});
13+
14+
export const routeOverrideSchema = z.object({
15+
method: httpMethodSchema.optional(),
16+
path: z.string().min(1),
17+
skip: z.boolean().optional(),
18+
removeParams: z.array(z.string().min(1)).optional(),
19+
addParams: z.array(routeOverrideParameterSchema).optional(),
20+
});
21+
22+
export const extractorConfigSchema = z
23+
.object({
24+
root_dir: z.string().default(""),
25+
route_file: z.string().min(1).default("src/main/typesense_server.cpp"),
26+
source_branch: z.string().min(1).default("v30"),
27+
output_path: z.string().min(1).default("./output/typesense_api_spec.json"),
28+
diagnostics_output_path: z
29+
.string()
30+
.min(1)
31+
.optional()
32+
.default("./output/typesense_api_diagnostics.json"),
33+
base_url: z.string().min(1).optional(),
34+
max_call_depth: z.number().finite().default(4),
35+
fail_on_diagnostics: z.boolean().default(false),
36+
fail_on_unresolved: z.boolean().default(false),
37+
include_helpers: z.array(z.string().min(1)).default([]),
38+
blacklist: z
39+
.object({
40+
paths: z.array(z.string().min(1)).default([]),
41+
params: z.array(z.string().min(1)).default([]),
42+
})
43+
.default({
44+
paths: [],
45+
params: [],
46+
}),
47+
overrides: z.array(routeOverrideSchema).default([]),
48+
debug: z
49+
.object({
50+
enabled: z.boolean().default(false),
51+
route_filter: z.string().min(1).optional(),
52+
})
53+
.default({
54+
enabled: false,
55+
}),
56+
})
57+
.strict();
58+
59+
export type RouteOverrideParameter = z.infer<
60+
typeof routeOverrideParameterSchema
61+
>;
62+
export type RouteOverride = z.infer<typeof routeOverrideSchema>;
63+
export type ParsedExtractorConfig = z.infer<typeof extractorConfigSchema>;
64+
export type ParameterLocation = z.infer<typeof locationEnum>;

src/config.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import path from "node:path";
2+
import { cosmiconfig } from "cosmiconfig";
3+
import { extractorConfigSchema } from "./config-schema.js";
4+
import type { ParsedExtractorConfig } from "./config-schema.js";
5+
import type {
6+
CliOptions,
7+
ExtractorConfig,
8+
} from "./types.js";
9+
10+
export async function loadConfig(
11+
configPath: string,
12+
cliOptions: CliOptions,
13+
): Promise<ExtractorConfig> {
14+
const absoluteConfigPath = path.resolve(configPath);
15+
const configDir = path.dirname(absoluteConfigPath);
16+
const explorer = cosmiconfig("typesense-api-extractor", {
17+
searchPlaces: [
18+
"package.json",
19+
".typesense-api-extractorrc",
20+
".typesense-api-extractorrc.json",
21+
".typesense-api-extractorrc.yaml",
22+
".typesense-api-extractorrc.yml",
23+
".typesense-api-extractorrc.js",
24+
"typesense-api-extractor.config.js",
25+
"config.json",
26+
],
27+
});
28+
29+
const result = await explorer.load(absoluteConfigPath);
30+
if (result === null) {
31+
throw new Error(`Could not load config file ${absoluteConfigPath}.`);
32+
}
33+
34+
const parsedConfig = parseConfig(result.config, absoluteConfigPath);
35+
return {
36+
rootDir: parsedConfig.root_dir,
37+
routeFile: parsedConfig.route_file,
38+
sourceBranch: parsedConfig.source_branch,
39+
outputPath:
40+
cliOptions.outputPath !== undefined
41+
? path.resolve(cliOptions.outputPath)
42+
: path.resolve(configDir, parsedConfig.output_path),
43+
diagnosticsOutputPath:
44+
parsedConfig.diagnostics_output_path === undefined
45+
? undefined
46+
: path.resolve(configDir, parsedConfig.diagnostics_output_path),
47+
baseUrl: cliOptions.baseUrl ?? parsedConfig.base_url,
48+
maxCallDepth: cliOptions.maxCallDepth ?? parsedConfig.max_call_depth,
49+
failOnDiagnostics:
50+
cliOptions.failOnDiagnostics ?? parsedConfig.fail_on_diagnostics,
51+
failOnUnresolved:
52+
cliOptions.failOnUnresolved ?? parsedConfig.fail_on_unresolved,
53+
blacklist: parsedConfig.blacklist,
54+
includeHelpers: parsedConfig.include_helpers,
55+
overrides: parsedConfig.overrides,
56+
debug: {
57+
enabled: cliOptions.verbose || parsedConfig.debug.enabled,
58+
routeFilter: cliOptions.debugRoute ?? parsedConfig.debug.route_filter,
59+
},
60+
};
61+
}
62+
63+
function parseConfig(
64+
config: unknown,
65+
configPath: string,
66+
): ParsedExtractorConfig {
67+
const parsedConfig = extractorConfigSchema.safeParse(config);
68+
if (parsedConfig.success) {
69+
return parsedConfig.data;
70+
}
71+
72+
const message = parsedConfig.error.errors
73+
.map(
74+
(error) =>
75+
`Error for config field '${error.path.join(".")}': ${error.message}`,
76+
)
77+
.join("\n");
78+
throw new Error(`Could not parse config file ${configPath}:\n${message}`);
79+
}

0 commit comments

Comments
 (0)