Skip to content

Commit

Permalink
fix: Add test and export types
Browse files Browse the repository at this point in the history
  • Loading branch information
eliassjogreen committed Dec 18, 2024
1 parent 74c382f commit 77f2b25
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 32 deletions.
10 changes: 9 additions & 1 deletion main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ if (args.help) {
` --include-server-urls Include server URLs from the schema in the generated paths (default: ${parseOptions.default["include-server-urls"]})\n` +
` --include-absolute-url Include absolute URLs in the generated paths (default: ${parseOptions.default["include-absolute-url"]})\n` +
` --include-relative-url Include relative URLs in the generated paths (default: ${parseOptions.default["include-relative-url"]})\n` +
` --experimental-urlsearchparams Enable the experimental fully typed URLSearchParams type (default: ${parseOptions.default["experimental-urlsearchparams"]})\n`,
` --experimental-urlsearchparams Enable the experimental fully typed URLSearchParamsString type (default: ${parseOptions.default["experimental-urlsearchparams"]})\n`,
);
Deno.exit(0);
}
Expand Down Expand Up @@ -141,6 +141,14 @@ if (options.experimentalURLSearchParams) {
}`,
namedImports: ["URLSearchParamsString"],
});
} else {
source.addImportDeclaration({
isTypeOnly: true,
moduleSpecifier: `${args["import"]}/types/url_search_params${
URL.canParse(args["import"]) ? ".ts" : ""
}`,
namedImports: ["URLSearchParamsString"],
});
}

source.insertText(0, (writer) => {
Expand Down
88 changes: 63 additions & 25 deletions mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,19 +61,28 @@ export function toSchemaType(
schema?:
| OpenAPI.ReferenceObject
| OpenAPI.SchemaObject,
coerceToString?: boolean,
): string | undefined {
if (schema === undefined) return undefined;
if ("$ref" in schema) return pascalCase(schema.$ref.split("/").pop()!);

if ("nullable" in schema && schema.nullable !== undefined) {
const type = toSchemaType(document, { ...schema, nullable: undefined });
const type = toSchemaType(
document,
{ ...schema, nullable: undefined },
coerceToString,
);
if (type !== undefined) return `${type}|null`;
return "null";
}

if (schema.not !== undefined) {
const type = toSchemaType(document, { ...schema, not: undefined });
const exclude = toSchemaType(document, schema.not);
const type = toSchemaType(
document,
{ ...schema, not: undefined },
coerceToString,
);
const exclude = toSchemaType(document, schema.not, coerceToString);
if (type !== undefined && exclude !== undefined) {
return `Exclude<${type}, ${exclude}>`;
}
Expand All @@ -85,12 +94,13 @@ export function toSchemaType(
const type = toSchemaType(document, {
...schema,
additionalProperties: undefined,
});
}, coerceToString);
let additionalProperties;
if (schema.additionalProperties !== true) {
additionalProperties = toSchemaType(
document,
schema.additionalProperties,
coerceToString,
);
}
if (type !== undefined) {
Expand All @@ -101,14 +111,14 @@ export function toSchemaType(

if (schema.allOf) {
return schema.allOf
.map((schema) => toSchemaType(document, schema))
.map((schema) => toSchemaType(document, schema, coerceToString))
.filter(Boolean)
.join("&");
}

if (schema.oneOf) {
return schema.oneOf
.map((schema) => toSchemaType(document, schema))
.map((schema) => toSchemaType(document, schema, coerceToString))
.map((type, _, types) => toSafeUnionString(type, types))
.filter(Boolean)
.join("|");
Expand All @@ -129,7 +139,7 @@ export function toSchemaType(
}

return schema.anyOf
.map((schema) => toSchemaType(document, schema))
.map((schema) => toSchemaType(document, schema, coerceToString))
.map((type, _, types) => toSafeUnionString(type, types))
.filter(Boolean)
.join("|");
Expand All @@ -141,11 +151,13 @@ export function toSchemaType(

switch (schema.type) {
case "boolean":
if (coerceToString) return "`${boolean}`";
return "boolean";
case "string":
return "string";
case "number":
case "integer":
if (coerceToString) return "`${number}`";
return "number";
case "object": {
if ("properties" in schema && schema.properties !== undefined) {
Expand All @@ -157,19 +169,24 @@ export function toSchemaType(
.map(([property, type]) =>
`${escapeObjectKey(property)}${
schema.required?.includes(property) ? "" : "?"
}:${toSchemaType(document, type)}`
}:${toSchemaType(document, type, coerceToString)}`
)
.join(";")
}}`;
}

if (coerceToString) return "Record<string, string>";
return "Record<string, unknown>";
}
case "array": {
const items = toSchemaType(document, schema.items);
const items = toSchemaType(document, schema.items, coerceToString);
if (items !== undefined) return `(${items})[]`;

if (coerceToString) return "string[]";
return "unknown[]";
}
case "null":
if (coerceToString) return "`${null}`";
return "null";
}

Expand Down Expand Up @@ -300,25 +317,43 @@ export function createRequestBodyType(
document: OpenAPI.Document,
contentType: string,
schema?: OpenAPI.SchemaObject | OpenAPI.ReferenceObject,
options?: Options,
): string {
let type = "BodyInit";

switch (contentType) {
case "application/json":
case "application/json": {
type = `JSONString<${toSchemaType(document, schema) ?? "unknown"}>`;
break;
case "text/plain":
}
case "text/plain": {
type = "string";
break;
case "multipart/form-data":
}
case "multipart/form-data": {
type = "FormData";
break;
case "application/x-www-form-urlencoded":
type = "URLSearchParams";
}
case "application/x-www-form-urlencoded": {
const schemaType = toSchemaType(document, schema, true);
if (schemaType !== undefined) {
const types = [`URLSearchParamsString<${schemaType}>`];

// TODO: We don't yet support URLSearchParams with the --experimental-urlsearchparams flag
if (!options?.experimentalURLSearchParams) {
types.push(`URLSearchParams<${schemaType}>`);
}

return `(${types.join("|")})`;
} else {
type = `URLSearchParams`;
}
break;
case "application/octet-stream":
}
case "application/octet-stream": {
type = "ReadableStream | Blob | BufferSource";
break;
}
}

return type;
Expand Down Expand Up @@ -385,7 +420,6 @@ export function toTemplateString(
document: OpenAPI.Document,
pattern: string,
parameters: ParameterObjectMap,
options: Options,
): string {
let patternTemplateString = pattern;
let urlSearchParamsOptional = true;
Expand All @@ -397,7 +431,9 @@ export function toTemplateString(
urlSearchParamsOptional = false;
}

const types = [toSchemaType(document, parameter.schema) ?? "string"];
const types = [
toSchemaType(document, parameter.schema, true) ?? "string",
];
if (parameter.allowEmptyValue === true) types.push("true");
urlSearchParamsRecord.push(
`${escapeObjectKey(parameter.name)}${!parameter.required ? "?" : ""}: ${
Expand All @@ -414,15 +450,17 @@ export function toTemplateString(
);
}

const URLSearchParams = urlSearchParamsRecord.length > 0
? options.experimentalURLSearchParams
? `\${URLSearchParamsString<{${urlSearchParamsRecord.join(";")}}>}`
: urlSearchParamsOptional
? '${"" | `?${string}`}'
: "?${string}"
const urlSearchParamsType = urlSearchParamsRecord.length > 0
? `URLSearchParamsString<{${urlSearchParamsRecord.join(";")}}>`
: undefined;

const urlSearchParams = urlSearchParamsType
? urlSearchParamsOptional
? `\${\`?\${${urlSearchParamsType}}\` | ""}`
: `?\${${urlSearchParamsType}}`
: "";

return `${patternTemplateString}${URLSearchParams}`;
return `${patternTemplateString}${urlSearchParams}`;
}

export function toHeadersInitType(
Expand Down Expand Up @@ -569,7 +607,7 @@ export function addOperationObject(
doc.tags.push({ tagName: "summary", text: operation.summary.trim() });
}

const path = toTemplateString(document, pattern, parameters, options);
const path = toTemplateString(document, pattern, parameters);

const inputs = [];

Expand Down
5 changes: 5 additions & 0 deletions scripts/npm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ await build({
name: "./types/json",
path: "./types/json.ts",
},
{
kind: "export",
name: "./types/url_search_params",
path: "./types/url_search_params.ts",
},
{
kind: "export",
name: "./types/url_search_params_string",
Expand Down
23 changes: 23 additions & 0 deletions tests/petstore/test_url_search_params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { Equal, Expect, IsUnion, NotEqual } from "npm:type-testing";
import type { Error, Pets } from "./schemas/petstore.json.ts";

import { URLSearchParams } from "../../types/url_search_params.ts";

const urlSearchParams = new URLSearchParams<{ limit?: `${number}` }>({
limit: "10",
});
const response = await fetch(
`http://petstore.swagger.io/v1/pets?${urlSearchParams.toString()}`,
);

if (response.ok) {
const json = await response.json();
type test_IsUnion = Expect<IsUnion<typeof json>>;
type test_IsPetsOrError = Expect<Equal<typeof json, Pets | Error>>;
}

if (response.status === 200) {
const pets = await response.json();
type test_IsPets = Expect<Equal<typeof pets, Pets>>;
type test_IsNotError = Expect<NotEqual<typeof pets, Error>>;
}
4 changes: 2 additions & 2 deletions types/headers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type HeadersRecord = Record<string, string>;
// TODO: Add support for tuple format of headers
export type TypedHeadersInit<T extends HeadersRecord> = T | Headers<T>;

declare interface Headers<T extends HeadersRecord = HeadersRecord> {
export declare interface Headers<T extends HeadersRecord = HeadersRecord> {
/**
* Appends a new value onto an existing header inside a `Headers` object, or
* adds the header if it does not already exist.
Expand Down Expand Up @@ -70,7 +70,7 @@ declare interface Headers<T extends HeadersRecord = HeadersRecord> {
getSetCookie(): string[];
}

declare var Headers: {
export declare var Headers: {
readonly prototype: Headers;
new <T extends HeadersRecord>(init?: T): Headers<T>;
};
8 changes: 4 additions & 4 deletions types/url_search_params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export type URLSearchParamsInit<T extends URLSearchParamsRecord> =
| T
| URLSearchParamsString<T>;

declare interface URLSearchParams<
export declare interface URLSearchParams<
T extends URLSearchParamsRecord = URLSearchParamsRecord,
> {
/**
Expand Down Expand Up @@ -69,8 +69,8 @@ declare interface URLSearchParams<
* searchParams.getAll('name');
* ```
*/
get<K extends RequiredKeys<T>>(name: K): [T[K]];
get<K extends OptionalKeys<T>>(name: K): [] | [T[K]];
getAll<K extends RequiredKeys<T>>(name: K): [T[K]];
getAll<K extends OptionalKeys<T>>(name: K): [] | [NonNullable<T[K]>];

/**
* Returns the first value associated to the given search parameter.
Expand Down Expand Up @@ -194,7 +194,7 @@ declare interface URLSearchParams<
size: number;
}

declare var URLSearchParams: {
export declare var URLSearchParams: {
readonly prototype: URLSearchParams;
new <T extends URLSearchParamsRecord>(
init?: URLSearchParamsInit<T>,
Expand Down

0 comments on commit 77f2b25

Please sign in to comment.