Skip to content

Commit

Permalink
make queryParams optional when no parameter is required
Browse files Browse the repository at this point in the history
  • Loading branch information
inkognitro committed Oct 7, 2024
1 parent 2fd8a8f commit cba70b5
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 135 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -351,7 +351,7 @@ The decision of having a `RequestHandler` interface with granular implementation
in mind: `server` vs `client` | `prod` vs `test` | custom "middleware" behaviour.
You can write your own implementations or just combine some of the existing ones below, according to your needs.
The `RequestHandler` interface for custom implementations can be found
[here](https://github.com/inkognitro/oas-tszod-gen/blob/main/src/templates/core/core.ts#L263).
[here](https://github.com/inkognitro/oas-tszod-gen/blob/main/src/templates/core/core.ts#L343).

### AxiosRequestHandler
This implementation is responsible for executing your requests through the http(s) protocol.
Expand Down
13 changes: 10 additions & 3 deletions src/oas3/codegen/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,20 @@ function findPayloadParamCode(
path: OutputPath,
applyRequestResult: ApplyRequestTypeDefinitionResult
): null | string {
const fields = applyRequestResult.payloadFields;
if (!fields.length) {
const requiredFields = applyRequestResult.requiredPayloadFields;
const optionalFields = applyRequestResult.optionalPayloadFields;
if (!requiredFields.length && !optionalFields.length) {
return null;
}
const requiredFieldsCode = requiredFields.length
? `'${requiredFields.join("' | '")}'`
: 'never';
const optionalFieldsCode = optionalFields.length
? `'${optionalFields.join("' | '")}'`
: 'never';
const requestPayloadTypeName = templateRequestPayloadType.createName(path);
const requestTypeName = applyRequestResult.typeDefinition.createName(path);
return `payload: ${requestPayloadTypeName}<${requestTypeName}, '${fields.join("' | '")}'>`;
return `payload: ${requestPayloadTypeName}<${requestTypeName}, ${requiredFieldsCode}, ${optionalFieldsCode}>`;
}

export function applyEndpointCallerFunction(
Expand Down
58 changes: 38 additions & 20 deletions src/oas3/codegen/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,18 @@ function applyNullableRequestBodyByContentTypeMap(
};
}

function applyNullableRequestLocationParameters(
type ApplyRequestLocationParametersResult = {
hasAtLeastOneRequiredParameter: boolean;
codeOutput: CodeGenerationOutput;
};

function applyNullableRequestLocationParametersResult(
codeGenerator: CodeGenerator,
requestParameters: Parameter[],
path: OutputPath,
ctx: Context,
parameterLocation: ConcreteParameterLocation
): null | CodeGenerationOutput {
): null | ApplyRequestLocationParametersResult {
const objectSchema = findObjectSchemaFromLocationParameters(
codeGenerator,
requestParameters,
Expand All @@ -145,7 +150,11 @@ function applyNullableRequestLocationParameters(
if (!objectSchema) {
return null;
}
return applyObjectSchema(codeGenerator, objectSchema, path, ctx);
const codeOutput = applyObjectSchema(codeGenerator, objectSchema, path, ctx);
return {
codeOutput,
hasAtLeastOneRequiredParameter: !!objectSchema.required?.length,
};
}

export type RequestPayloadField =
Expand All @@ -157,7 +166,8 @@ export type RequestPayloadField =
| 'body';

export type ApplyRequestTypeDefinitionResult = {
payloadFields: RequestPayloadField[];
requiredPayloadFields: RequestPayloadField[];
optionalPayloadFields: RequestPayloadField[];
typeDefinition: DefinitionOutput;
};

Expand All @@ -167,34 +177,35 @@ export function applyRequestTypeDefinition(
path: OutputPath,
ctx: Context
): ApplyRequestTypeDefinitionResult {
const pathParamsCodeOutput = applyNullableRequestLocationParameters(
const pathParamsCodeOutput = applyNullableRequestLocationParametersResult(
codeGenerator,
schema.parameters ?? [],
[...path, 'pathParams'],
ctx,
'path'
);
const queryParamsCodeOutput = applyNullableRequestLocationParameters(
)?.codeOutput;
const queryParamsResult = applyNullableRequestLocationParametersResult(
codeGenerator,
schema.parameters ?? [],
[...path, 'queryParams'],
ctx,
'query'
);
const headersCodeOutput = applyNullableRequestLocationParameters(
const queryParamsCodeOutput = queryParamsResult?.codeOutput;
const headersCodeOutput = applyNullableRequestLocationParametersResult(
codeGenerator,
schema.parameters ?? [],
[...path, 'headers'],
ctx,
'header'
);
const cookiesCodeOutput = applyNullableRequestLocationParameters(
)?.codeOutput;
const cookiesCodeOutput = applyNullableRequestLocationParametersResult(
codeGenerator,
schema.parameters ?? [],
[...path, 'cookies'],
ctx,
'cookie'
);
)?.codeOutput;
let bodyCodeOutput: CodeGenerationOutput | null = null;
if (schema.requestBody?.content) {
bodyCodeOutput = applyNullableRequestBodyByContentTypeMap(
Expand Down Expand Up @@ -280,25 +291,32 @@ export function applyRequestTypeDefinition(
},
};
codeGenerator.addOutput(typeDefinition, ctx);
const includedFields: RequestPayloadField[] = [];
const requiredFields: RequestPayloadField[] = [];
const optionalFields: RequestPayloadField[] = [];
if (pathParamsCodeOutput) {
includedFields.push('pathParams');
requiredFields.push('pathParams');
}
if (queryParamsCodeOutput) {
includedFields.push('queryParams');
if (
queryParamsCodeOutput &&
queryParamsResult?.hasAtLeastOneRequiredParameter
) {
requiredFields.push('queryParams');
} else if (queryParamsCodeOutput) {
optionalFields.push('queryParams');
}
if (headersCodeOutput) {
includedFields.push('headers');
optionalFields.push('headers');
}
if (cookiesCodeOutput) {
includedFields.push('cookies');
optionalFields.push('cookies');
}
if (bodyCodeOutput) {
includedFields.push('contentType');
includedFields.push('body');
requiredFields.push('contentType');
requiredFields.push('body');
}
return {
typeDefinition,
payloadFields: includedFields,
requiredPayloadFields: requiredFields,
optionalPayloadFields: optionalFields,
};
}
37 changes: 31 additions & 6 deletions src/oas3/codegen/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,27 @@ const templateCreateRequestUrlFunction: TemplateDefinitionOutput = {
getRequiredOutputPaths: () => [templatePathParamsType.path],
};

const templateRequiredAndPartialType: TemplateDefinitionOutput = {
type: OutputType.TEMPLATE_DEFINITION,
definitionType: 'type',
path: ['core', 'core', 'requiredAndPartial'],
createName: () => {
return 'RequiredAndPartial';
},
createGenericsDeclarationCode: () => {
const codeParts = [
'T extends object = any,',
'RFields extends keyof T = any,',
'OFields extends keyof T = any,',
];
return codeParts.join('\n');
},
createCode: () => {
return 'Required<Pick<T, RFields>> & Partial<Pick<T, OFields>>';
},
getRequiredOutputPaths: () => [],
};

const templateRequestPayloadTypePath = ['core', 'core', 'requestPayload'];
export const templateRequestPayloadType: TemplateDefinitionOutput = {
type: OutputType.TEMPLATE_DEFINITION,
Expand All @@ -57,24 +78,27 @@ export const templateRequestPayloadType: TemplateDefinitionOutput = {
`TRequest extends ${templateRequestType.createName(
templateRequestPayloadTypePath
)} = any,`,
'TFields extends "cookies" | "headers" | "pathParams" | "queryParams" | "contentType" | "body" = any,',
"RFields extends 'pathParams' | 'queryParams' | 'contentType' | 'body' = any,",
"OFields extends 'cookies' | 'headers' | 'queryParams' = any,",
];
return codeParts.join('\n');
},
createCode: () => {
const fieldCodeParts = [
'requestId?: string; // always optional',
'headers?: TRequest["headers"]; // always optional',
'cookies?: TRequest["cookies"]; // always optional',
'requestId: string;',
'headers: TRequest["headers"];',
'cookies: TRequest["cookies"];',
'pathParams: TRequest["pathParams"];',
'queryParams: TRequest["queryParams"];',
'contentType: TRequest["contentType"];',
'body: TRequest["body"];',
];
return `Pick<{${fieldCodeParts.join('\n')}}, "requestId" | TFields>`;
return `${templateRequiredAndPartialType.createName(
templateRequestPayloadTypePath
)}<{${fieldCodeParts.join('\n')}}, RFields, "requestId" | OFields>`;
},
getRequiredOutputPaths: () => {
return [templateRequestType.path];
return [templateRequestType.path, templateRequiredAndPartialType.path];
},
};

Expand Down Expand Up @@ -931,6 +955,7 @@ export const templateDefinitionOutputs: TemplateDefinitionOutput[] = [
templateRequestBodyDataType,
templateRequestType,
templateRequestUnionType,
templateRequiredAndPartialType,
templateRequestPayloadType,
templateRequestFromPayloadType,
templateCreateRequestIdFunction,
Expand Down
Loading

0 comments on commit cba70b5

Please sign in to comment.