Skip to content

Commit b5f4b3d

Browse files
fix(sdk-codegen): make run_inline_query response type generic (#1730) (#1759)
## What `run_inline_query` is hardcoded to return `SDKResponse<string, ...>`, but when `result_format` is `'json'` the transport layer actually deserializes the body into an object/array. Since `value` is typed as `string`, callers have no valid TypeScript path to the real data and are forced into `as unknown as T` casts, which defeats type safety entirely. Looker's OpenAPI spec declares a single `200: { schema: { type: string } }` response for this endpoint with no per-`result_format` breakdown, so the generator can't derive the real shape per format automatically. This PR makes `run_inline_query` generic instead: ```ts async run_inline_query<T = string>( request: IRequestRunInlineQuery, options?: Partial<ITransportSettings> ): Promise<SDKResponse<T, IError | IValidationError>> ``` Callers who know their query's shape (they always do — they specify model/view/fields) can now write: ```ts const response = await sdk.run_inline_query<StoreProduct[]>(req); if (response.ok) { response.value; // StoreProduct[] — no cast } ``` ## Backward compatibility — this is opt-in, no existing consumers break - `<T = string>` has a **default type argument**. Any existing call site that doesn't supply a generic (`sdk.run_inline_query(request)`) resolves `T` to `string`, exactly as before — no change in inferred type, no change in behavior. - Generic type parameters are compile-time only and erased by the TypeScript compiler; the emitted JavaScript for `this.post(...)` is identical regardless of `T`. No runtime behavior changes. - The method signature (arguments, arity, return shape) is otherwise untouched. - Verified: `yarn build` type-checks the full monorepo against the new signature with zero errors, and reverting only the implementation (leaving the generic interface) reliably produces a type error — confirming the generic threads correctly end-to-end and that plain, non-generic call sites are unaffected. ## Scope This is scoped to `run_inline_query` only, per the linked issue. The same `result_format`-driven typing problem exists on a few sibling methods (`run_query`, `run_look`, `run_url_encoded_query`, `run_sql_query`) that weren't touched here to keep this PR minimal and reviewable. Happy to circle back and apply the same generic-default pattern to those once this approach is confirmed to be the right one. ## Changes - `packages/sdk-codegen/src/typescript.gen.ts`: generator now emits `<T = string>` for methods special-cased as "format-dependent string" responses (currently just `run_inline_query`). - `packages/sdk/src/4.0/methods.ts`, `methodsInterface.ts`, `funcs.ts`: regenerated output for `run_inline_query` reflecting the generic. Fixes #1703 ## Test plan - [x] `yarn build` passes cleanly across all 21 packages - [x] Full `sdk` / `sdk-codegen` / `sdk-rtl` jest suites pass (only pre-existing, unrelated failures remain — 2 tests requiring a live `looker.ini`, confirmed to fail identically on `main`) - [x] Manually verified (via a temporary local test, not included in this PR) that a caller can do `sdk.run_inline_query<MyType[]>(request)` and get a fully typed `response.value` with no cast, and that omitting the generic still defaults to `string` - [x] Confirmed the fix is load-bearing by reverting just the `methods.ts` implementation and re-running `tsc` — it immediately fails type-checking against the generic interface, proving the change is meaningful Co-authored-by: Marc <marc@marcswilson.com>
1 parent d705e57 commit b5f4b3d

4 files changed

Lines changed: 53 additions & 14 deletions

File tree

‎packages/sdk-codegen/src/typescript.gen.ts‎

Lines changed: 45 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -452,9 +452,10 @@ let response = await sdk.ok(sdk.${method.name}(`;
452452
params.length > 0 ? `\n${params.join(this.paramDelimiter)}` : '';
453453
}
454454
const callback = `callback: (response: Response) => Promise<${mapped.name}>,`;
455+
const generics = streamer ? '' : this.methodGenericParams(method);
455456
const header =
456457
this.commentHeader(indent, headComment) +
457-
`${indent}async ${method.name}(` +
458+
`${indent}async ${method.name}${generics}(` +
458459
(streamer ? `\n${bump}${callback}` : '');
459460
const returns = streamer ? '' : `: ${this.returnType(indent, method)}`;
460461

@@ -495,15 +496,53 @@ let response = await sdk.ok(sdk.${method.name}(`;
495496
);
496497
}
497498

499+
/**
500+
* Methods whose declared response type is a generic `string` but whose
501+
* actual runtime shape depends on a `result_format` request parameter
502+
* (e.g. 'json' returns a parsed object/array, not a string). The Looker
503+
* API spec does not document per-format response shapes, so these are
504+
* special-cased here to expose a generic type parameter instead of the
505+
* inaccurate hardcoded `string` return type.
506+
*
507+
* See https://github.com/looker-open-source/sdk-codegen/issues/1703
508+
*/
509+
private static readonly formatDependentStringMethods = new Set([
510+
'run_inline_query',
511+
]);
512+
513+
/**
514+
* `true` if this method should expose a `<T = string>` generic instead of
515+
* a hardcoded `string` return type
516+
*/
517+
hasGenericResponse(method: IMethod): boolean {
518+
return TypescriptGen.formatDependentStringMethods.has(method.name);
519+
}
520+
521+
/**
522+
* The generic type parameter declaration to splice between a method's
523+
* name and its parameter list, or '' if the method has no generic response
524+
*/
525+
methodGenericParams(method: IMethod): string {
526+
return this.hasGenericResponse(method) ? '<T = string>' : '';
527+
}
528+
529+
/**
530+
* The name to use for the method's response type, honoring the generic
531+
* response override for format-dependent methods
532+
*/
533+
responseTypeName(method: IMethod): string {
534+
if (this.hasGenericResponse(method)) return 'T';
535+
return this.typeMap(method.type).name;
536+
}
537+
498538
/**
499539
* Return type declaration for the method
500540
* @param indent
501541
* @param method
502542
*/
503543
returnType(indent: string, method: IMethod): string {
504-
const mapped = this.typeMap(method.type);
505544
const errors = this.errorResponses(indent, method);
506-
return `Promise<SDKResponse<${mapped.name}, ${errors}>>`;
545+
return `Promise<SDKResponse<${this.responseTypeName(method)}, ${errors}>>`;
507546
}
508547

509548
customHeaderComment(term: string, method: IMethod, params: string[] = []) {
@@ -652,9 +691,10 @@ ${indent}})`;
652691
fragment =
653692
params.length > 0 ? `\n${params.join(this.paramDelimiter)}` : '';
654693
}
694+
const generics = this.methodGenericParams(method);
655695
const header =
656696
this.commentHeader(indent, headComment) +
657-
`${indent}export const ${method.name} = async (`;
697+
`${indent}export const ${method.name} = async ${generics}(`;
658698
const returns = this.returnType(indent, method);
659699

660700
return (
@@ -839,13 +879,12 @@ ${indent}})`;
839879
840880
httpCall(indent: string, method: IMethod) {
841881
const request = this.useRequest(method) ? 'request.' : '';
842-
const mapped = this.typeMap(method.type);
843882
const bump = this.bumper(indent);
844883
const args = this.httpArgs(bump, method);
845884
const errors = this.errorResponses(indent, method);
846885
return (
847886
`${indent}return ${this.it(method.httpMethod.toLowerCase())}` +
848-
`<${mapped.name}, ${errors}>(` +
887+
`<${this.responseTypeName(method)}, ${errors}>(` +
849888
this.httpPath(method.endpoint, request) +
850889
`${args ? ', ' + args : ''})`
851890
);

‎packages/sdk/src/4.0/funcs.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11268,13 +11268,13 @@ export const run_query = async (
1126811268
* @param options one-time API call overrides
1126911269
*
1127011270
*/
11271-
export const run_inline_query = async (
11271+
export const run_inline_query = async <T = string>(
1127211272
sdk: IAPIMethods,
1127311273
request: IRequestRunInlineQuery,
1127411274
options?: Partial<ITransportSettings>
11275-
): Promise<SDKResponse<string, IError | IValidationError>> => {
11275+
): Promise<SDKResponse<T, IError | IValidationError>> => {
1127611276
request.result_format = encodeParam(request.result_format);
11277-
return sdk.post<string, IError | IValidationError>(
11277+
return sdk.post<T, IError | IValidationError>(
1127811278
`/queries/run/${request.result_format}`,
1127911279
{
1128011280
limit: request.limit,

‎packages/sdk/src/4.0/methods.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10588,12 +10588,12 @@ export class Looker40SDK extends APIMethods implements ILooker40SDK {
1058810588
* @param options one-time API call overrides
1058910589
*
1059010590
*/
10591-
async run_inline_query(
10591+
async run_inline_query<T = string>(
1059210592
request: IRequestRunInlineQuery,
1059310593
options?: Partial<ITransportSettings>
10594-
): Promise<SDKResponse<string, IError | IValidationError>> {
10594+
): Promise<SDKResponse<T, IError | IValidationError>> {
1059510595
request.result_format = encodeParam(request.result_format);
10596-
return this.post<string, IError | IValidationError>(
10596+
return this.post<T, IError | IValidationError>(
1059710597
`/queries/run/${request.result_format}`,
1059810598
{
1059910599
limit: request.limit,

‎packages/sdk/src/4.0/methodsInterface.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7469,10 +7469,10 @@ export interface ILooker40SDK extends IAPIMethods {
74697469
* @param options one-time API call overrides
74707470
*
74717471
*/
7472-
run_inline_query(
7472+
run_inline_query<T = string>(
74737473
request: IRequestRunInlineQuery,
74747474
options?: Partial<ITransportSettings>
7475-
): Promise<SDKResponse<string, IError | IValidationError>>;
7475+
): Promise<SDKResponse<T, IError | IValidationError>>;
74767476

74777477
/**
74787478
* ### Run an URL encoded query.

0 commit comments

Comments
 (0)