Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: stack overflow for type tagging during codegen #845

Merged
merged 2 commits into from
Oct 5, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/sdk-codegen/src/sdkModels.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@

*/

import { readFileSync } from 'fs'
import type * as OAS from 'openapi3-ts'
import { TestConfig } from './testUtils'
import { rootFile, TestConfig } from './testUtils'
import type {
IEnumType,
IMethod,
Expand Down Expand Up @@ -68,6 +69,13 @@ describe('sdkModels', () => {
expect(actual).toEqual(expected)
}

it('API 4 models', () => {
const api4 = readFileSync(rootFile('spec/Looker.4.0.oas.json'), 'utf-8')
const api = ApiModel.fromString(api4)
const actual = Object.keys(api.typeTags)
expect(actual).toHaveLength(29)
})

describe('ordering', () => {
it('has types in sorted order', () => {
checkSorted(apiTestModel.types)
Expand Down
61 changes: 35 additions & 26 deletions packages/sdk-codegen/src/sdkModels.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ export type EnumValueType = string | number
/**
* Returns sorted string array for IKeylist type
* @param list Set of values
* @returns {string[]} sorted string array of keys
* @returns sorted string array of keys
*/
export const keyValues = (list: KeyList): string[] => {
if (!list) return []
Expand All @@ -241,8 +241,8 @@ export const keyValues = (list: KeyList): string[] => {
/**
* Optionally quote a string if quotes are required
* @param value to convert to string and optionally quote
* @param {string} quoteChar defaults to "'"
* @returns {string} the quoted or unquoted value
* @param quoteChar defaults to "'"
* @returns the quoted or unquoted value
*/
export const mayQuote = (value: any, quoteChar = `'`): string => {
const str = value.toString()
Expand All @@ -252,9 +252,9 @@ export const mayQuote = (value: any, quoteChar = `'`): string => {

/**
* Resolve a list of method keys into an IMethod[] in alphabetical order by name
* @param {IApiModel} api model to use
* @param {KeyList} refs references to models
* @returns {IMethod[]} Populated method list. Anything not matched is skipped
* @param api model to use
* @param refs references to models
* @returns Populated method list. Anything not matched is skipped
*/
export const methodRefs = (api: IApiModel, refs: KeyList): IMethod[] => {
const keys = keyValues(refs)
Expand All @@ -267,28 +267,11 @@ export const methodRefs = (api: IApiModel, refs: KeyList): IMethod[] => {
return result
}

/**
* Resolves first method ref it can find
* @param api parsed spec
* @param type tree to walk
*/
export const firstMethodRef = (api: ApiModel, type: IType): IMethod => {
let method = methodRefs(api, type.methodRefs)[0]
if (!method) {
const parents = typeRefs(api, type.parentTypes)
for (const parent of parents) {
method = firstMethodRef(api, parent)
if (method) break
}
}
return method
}

/**
* Resolve a list of method keys into an IType[] in alphabetical order by name
* @param {IApiModel} api model to use
* @param {KeyList} refs references to models
* @returns {IMethod[]} Populated method list. Anything not matched is skipped
* @param api model to use
* @param refs references to models
* @returns Populated method list. Anything not matched is skipped
*/
export const typeRefs = (api: IApiModel, refs: KeyList): IType[] => {
const keys = keyValues(refs)
Expand All @@ -302,6 +285,32 @@ export const typeRefs = (api: IApiModel, refs: KeyList): IType[] => {
return result
}

/**
* Resolves first method ref it can find
* @param api parsed spec
* @param type tree to walk
* @param stack call stack to prevent infinite recursion
*/
export const firstMethodRef = (
api: ApiModel,
type: IType,
stack: KeyList = new Set<string>()
): IMethod => {
stack.add(type.name)

let method = methodRefs(api, type.methodRefs)[0]
if (!method) {
const parents = typeRefs(api, type.parentTypes)
for (const parent of parents) {
if (!stack.has(parent.name)) {
method = firstMethodRef(api, parent, stack)
}
if (method) break
}
}
return method
}

/**
* Returns the first method (if any) that uses the reference type for updating
* @param api parsed spec
Expand Down
99 changes: 47 additions & 52 deletions packages/sdk-codegen/src/typescript.gen.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ describe('typescript generator', () => {
const inputs = { look_id: 17, fields }
const method = apiTestModel.methods.look
const actual = gen.makeTheCall(method, inputs)
const expected = `let response = await sdk.ok(sdk.look(17, '${fields}'))`
const expected = `let response = await sdk.ok(sdk.look(
17, '${fields}'))`
expect(actual).toEqual(expected)
})

Expand All @@ -207,34 +208,28 @@ describe('typescript generator', () => {
const inputs = { look_id: 17, body, fields }
const method = apiTestModel.methods.update_look
const actual = gen.makeTheCall(method, inputs)
const expected = `let response = await sdk.ok(
sdk.update_look(
17,
{
title: 'test title',
description: 'gen test',
query: {
model: 'the_look',
view: 'users',
total: true,
},
},
'id,user_id,title,description'
)
)`
const expected = `let response = await sdk.ok(sdk.update_look(
17, {
title: 'test title',
description: 'gen test',
query: {
model: 'the_look',
view: 'users',
total: true
}
}, 'id,user_id,title,description'))`
expect(actual).toEqual(expected)
})

it('assigns request params', () => {
const inputs = { look_id: 17, result_format: 'png' }
const method = apiTestModel.methods.run_look
const actual = gen.makeTheCall(method, inputs)
const expected = `let response = await sdk.ok(
sdk.run_look({
const expected = `let response = await sdk.ok(sdk.run_look(
{
look_id: 17,
result_format: 'png',
})
)`
result_format: 'png'
}))`
expect(actual).toEqual(expected)
})

Expand All @@ -247,14 +242,13 @@ describe('typescript generator', () => {
}
const method = apiTestModel.methods.create_query_task
const actual = gen.makeTheCall(method, inputs)
const expected = `let response = await sdk.ok(
sdk.create_query_task({
const expected = `let response = await sdk.ok(sdk.create_query_task(
{
body: {
query_id: 1,
result_format: ResultFormat.csv,
},
})
)`
result_format: ResultFormat.csv
}
}))`
expect(actual).toEqual(expected)
})

Expand All @@ -264,11 +258,10 @@ describe('typescript generator', () => {
}
const method = apiTestModel.methods.all_users
const actual = gen.makeTheCall(method, inputs)
const expected = `let response = await sdk.ok(
sdk.all_users({
ids: new DelimArray<number>([1, 2, 3]),
})
)`
const expected = `let response = await sdk.ok(sdk.all_users(
{
ids: new DelimArray<number>([1,2,3])
}))`
expect(actual).toEqual(expected)
})

Expand Down Expand Up @@ -302,37 +295,40 @@ describe('typescript generator', () => {
const inputs = { body, fields }
const method = apiTestModel.methods.create_merge_query
const actual = gen.makeTheCall(method, inputs)
const expected = `let response = await sdk.ok(
sdk.create_merge_query({
const expected = `let response = await sdk.ok(sdk.create_merge_query(
{
body: {
pivots: ['one', 'two', 'three'],
pivots: [
'one',
'two',
'three'
],
sorts: ['a'],
source_queries: [
{
merge_fields: [
{
field_name: 'merge_1',
source_field_name: 'source_1',
},
source_field_name: 'source_1'
}
],
name: 'first query',
query_id: 1,
query_id: 1
},
{
merge_fields: [
{
field_name: 'merge_2',
source_field_name: 'source_2',
},
source_field_name: 'source_2'
}
],
name: 'second query',
query_id: 2,
},
],
query_id: 2
}
]
},
fields: 'id,user_id,title,description',
})
)`
fields: 'id,user_id,title,description'
}))`
expect(actual).toEqual(expected)
})

Expand All @@ -344,16 +340,15 @@ describe('typescript generator', () => {
}
const inputs = { body: query }
const method = apiTestModel.methods.create_sql_query
const expected = `let response = await sdk.ok(
sdk.create_sql_query({
const expected = `let response = await sdk.ok(sdk.create_sql_query(
{
connection_name: 'looker',
model_name: 'the_look',
vis_config: {
first: 1,
second: 'two',
},
})
)`
second: 'two'
}
}))`
const actual = gen.makeTheCall(method, inputs)
expect(actual).toEqual(expected)
})
Expand Down