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

Implement GO response samples #104

Merged
merged 9 commits into from
Oct 3, 2024
220 changes: 205 additions & 15 deletions src/lib/code-sample/go.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ import { pascalCase } from 'change-case'

import type { Json, NonNullJson } from 'lib/json.js'

import { createJsonResponse } from './json.js'
import type { CodeSampleDefinition, Context } from './schema.js'

const defaultGoPackageName = 'api'
const goPackageBasePath = 'github.com/seamapi/go'

export const createGoRequest = (
{ request }: CodeSampleDefinition,
context: Context,
_context: Context,
): string => {
const isReqWithParams = Object.keys(request.parameters).length !== 0
const goPackageName = getGoPackageName(request.path)
Expand All @@ -21,8 +20,7 @@ export const createGoRequest = (
})

const requestStructName = getRequestStructName(request.path)
const formattedArgs = formatGoArgs(request.parameters, {
...context,
const formattedArgs = formatGoRequestArgs(request.parameters, {
goPackageName,
requestStructName,
})
Expand Down Expand Up @@ -86,20 +84,27 @@ const getRequestStructName = (path: string): string => {
const removeUntilSecondSlash = (str: string): string =>
str.replace(/^\/[^/]*/, '')

interface GoContext extends Context {
interface GoContext {
goPackageName: string
requestStructName: string
}

const formatGoArgs = (jsonParams: NonNullJson, context: GoContext): string =>
const formatGoRequestArgs = (
jsonParams: NonNullJson,
context: GoContext,
): string =>
Object.entries(jsonParams as Record<string, Json>)
.map(([paramKey, paramValue]) => {
const formattedValue = formatGoValue(paramKey, paramValue, context)
const formattedValue = formatGoRequestArgValue(
paramKey,
paramValue,
context,
)
return `${pascalCase(paramKey)}: ${formattedValue}`
})
.join(', ')

const formatGoValue = (
const formatGoRequestArgValue = (
key: string,
value: Json,
context: GoContext,
Expand All @@ -113,17 +118,17 @@ const formatGoValue = (
return `${defaultGoPackageName}.Float64(${value})`

if (Array.isArray(value)) {
return formatGoArray(key, value, context)
return formatGoRequestArrayValue(key, value, context)
}

if (typeof value === 'object') {
return formatGoObject(key, value, context)
return formatGoRequestObjectValue(key, value, context)
}

throw new Error(`Unsupported type: ${typeof value}`)
}

const formatGoArray = (
const formatGoRequestArrayValue = (
key: string,
value: Json[],
context: GoContext,
Expand All @@ -134,7 +139,9 @@ const formatGoArray = (
return 'nil'
}

const formattedItems = value.map((v) => formatGoValue(key, v, context))
const formattedItems = value.map((v) =>
formatGoRequestArgValue(key, v, context),
)
const item = value[0]
if (item == null) {
throw new Error(`Null value in response array for '${key}'`)
Expand Down Expand Up @@ -163,7 +170,7 @@ const getPrimitiveTypeName = (value: string | number | boolean): string => {
}
}

const formatGoObject = (
const formatGoRequestObjectValue = (
key: string,
value: Record<string, Json>,
context: GoContext,
Expand All @@ -175,7 +182,7 @@ const formatGoObject = (
const formattedEntries = Object.entries(value)
.map(
([objKey, val]) =>
`${pascalCase(objKey)}: ${formatGoValue(objKey, val, context)}`,
`${pascalCase(objKey)}: ${formatGoRequestArgValue(objKey, val, context)}`,
)
.join(', ')

Expand All @@ -184,4 +191,187 @@ const formatGoObject = (
return `${goPackageName}.${pascalCase(`${requestStructName} ${key}`)}{${formattedEntries}}`
}

export const createGoResponse = createJsonResponse
export const createGoResponse = (
{ response, title }: CodeSampleDefinition,
context: Context,
): string => {
const { endpoint } = context

if (endpoint.response.responseType === 'void') return 'nil'

const { responseKey, resourceType } = endpoint.response
const responseValue = response?.body?.[responseKey]

if (responseValue == null) {
throw new Error(`Missing ${responseKey} for '${title}'`)
}

const responseResourceGoStructName = pascalCase(resourceType)

return Array.isArray(responseValue)
? formatGoArrayResponse(responseValue, responseResourceGoStructName, title)
: formatGoResponse(responseValue, responseResourceGoStructName)
}

const formatGoArrayResponse = (
responseArray: Json[],
responseResourceGoStructName: string,
title: string,
): string => {
const formattedItems = responseArray
.map((v) => {
if (v == null) {
throw new Error(`Null value in response array for '${title}'`)
}
return formatGoResponse(v, responseResourceGoStructName)
})
.join(', ')

return `[]${defaultGoPackageName}.${responseResourceGoStructName}{${formattedItems}}`
}

const formatGoResponse = (
responseParams: NonNullJson,
responseResourceGoStructName: string,
): string => {
const params = formatGoResponseParams(
responseParams,
responseResourceGoStructName,
)
return `${defaultGoPackageName}.${responseResourceGoStructName}{${params}}`
}

const formatGoResponseParams = (
jsonParams: NonNullJson,
responseResourceGoStructName: string,
): string =>
Object.entries(jsonParams as Record<string, Json>)
.map(([paramKey, paramValue]) => {
const formattedValue = formatGoResponseParamValue(
{
key: paramKey,
value: paramValue,
propertyChain: [],
},
responseResourceGoStructName,
)
return `${pascalCase(paramKey)}: ${formattedValue}`
})
.join(', ')

const formatGoResponseParamValue = (
{
key,
value,
propertyChain,
}: { key: string; value: Json; propertyChain: string[] },
responseResourceGoStructName: string,
): string => {
if (value === null) return 'nil'
if (typeof value === 'boolean') return value.toString()
if (typeof value === 'number') return value.toString()
if (typeof value === 'string') return `"${value}"`

if (Array.isArray(value)) {
return formatGoResponseArrayValue(
{ key, value, propertyChain },
responseResourceGoStructName,
)
}

if (typeof value === 'object') {
return formatGoResponseObjectValue(
{ key, value, propertyChain },
responseResourceGoStructName,
)
}

throw new Error(`Unsupported type: ${typeof value}`)
}

const formatGoResponseArrayValue = (
{
key,
value,
propertyChain,
}: { key: string; value: Json[]; propertyChain: string[] },
responseResourceGoStructName: string,
): string => {
if (value.length === 0) {
return 'nil'
}

const item = value[0]
if (item == null) {
throw new Error(`Null value in response array for '${key}'`)
}

const updatedPropertyChain = [...propertyChain, key]

if (isPrimitiveValue(item)) {
const arrayType = getPrimitiveTypeName(item)
const formattedItems = value.map((v) =>
formatGoResponseParamValue(
{ key, value: v, propertyChain: updatedPropertyChain },
responseResourceGoStructName,
),
)
return `[]${arrayType}{${formattedItems.join(', ')}}`
} else {
const formattedItems = value.map((v) =>
formatGoResponseParamValue(
{ key, value: v, propertyChain: updatedPropertyChain },
responseResourceGoStructName,
),
)
const structName = getStructName(
updatedPropertyChain,
responseResourceGoStructName,
)

return `[]${structName}{${formattedItems.join(', ')}}`
}
}

const formatGoResponseObjectValue = (
{
key,
value,
propertyChain,
}: { key: string; value: Record<string, Json>; propertyChain: string[] },
responseResourceGoStructName: string,
): string => {
if (Object.keys(value).length === 0) {
return 'struct{}{}'
}

const updatedPropertyChain = [...propertyChain, key]
const structName = getStructName(
updatedPropertyChain,
responseResourceGoStructName,
)

const formattedEntries = Object.entries(value)
.map(([objKey, val]) => {
const formattedValue = formatGoResponseParamValue(
{
key: objKey,
value: val,
propertyChain: updatedPropertyChain,
},
responseResourceGoStructName,
)
return `${pascalCase(objKey)}: ${formattedValue}`
})
.join(', ')

return `${defaultGoPackageName}.${structName}{${formattedEntries}}`
}

const getStructName = (
propertyChain: string[],
responseResourceGoStructName: string,
): string => {
const fullPropertyChain = [responseResourceGoStructName, ...propertyChain]
return pascalCase(fullPropertyChain.join('_'))
}
3 changes: 3 additions & 0 deletions test/fixtures/types/code-sample-definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ export default [
number_prop: 10,
object_prop: {
foo: 'bar',
nested_object_prop: {
foo: 'bar',
},
},
array_prop: ['foo', 'bar'],
},
Expand Down
Loading