UNPKG

@seamapi/blueprint

Version:

Build tools for the Seam API using this blueprint.

380 lines (315 loc) 10 kB
import { pascalCase } from 'change-case' import type { Json, NonNullJson } from 'lib/json.js' import type { CodeSampleContext, CodeSampleDefinition } from './code-sample.js' const defaultGoPackageName = 'api' const goPackageBasePath = 'github.com/seamapi/go' export const createGoRequest = ( { request }: CodeSampleDefinition, _context: CodeSampleContext, ): string => { const isReqWithParams = Object.keys(request.parameters).length !== 0 const goPackageName = getGoPackageName(request.path) const goSdkImports = generateImports({ goPackageName, isReqWithParams, }) const requestStructName = getRequestStructName(request.path) const formattedArgs = formatGoRequestArgs(request.parameters, { goPackageName, requestStructName, }) const goSdkRequestArgs = `context.Background()${isReqWithParams ? `,\n${goPackageName}.${requestStructName}{\n${formattedArgs},\n}` : ''}` const pathParts = request.path.split('/') return `package main ${goSdkImports} func main() { client${pathParts.map((p) => pascalCase(p)).join('.')}(${isReqWithParams ? '\n' : ''}${goSdkRequestArgs},\n) } `.trim() } const getGoPackageName = (path: string): string => { if (!isPathNested(path)) { return defaultGoPackageName } const firstPathPart = path.split('/').slice(1)[1] if (firstPathPart == null) { throw new Error(`Invalid path: missing second part in "${path}"`) } return firstPathPart.replace(/_/g, '') } const isPathNested = (path: string): boolean => path.split('/').slice(1).length > 2 const generateImports = ({ goPackageName, isReqWithParams, }: { goPackageName: string isReqWithParams: boolean }): string => { const imports: string[] = [] if (isReqWithParams) { const defaultPackageImport = `import ${defaultGoPackageName} "${goPackageBasePath}"` imports.push(defaultPackageImport) } if (goPackageName !== defaultGoPackageName && isReqWithParams) { const nestedPackageImport = `import ${goPackageName} "${goPackageBasePath}/${goPackageName}"` imports.push(nestedPackageImport) } return imports.join('\n') } const getRequestStructName = (path: string): string => { const requestStructNameSuffix = 'Request' return isPathNested(path) ? `${pascalCase(removeUntilSecondSlash(path))}${requestStructNameSuffix}` : `${pascalCase(path)}${requestStructNameSuffix}` } const removeUntilSecondSlash = (str: string): string => str.replace(/^\/[^/]*/, '') interface GoContext { goPackageName: string requestStructName: string } const formatGoRequestArgs = ( jsonParams: NonNullJson, context: GoContext, ): string => Object.entries(jsonParams as Record<string, Json>) .map(([paramKey, paramValue]) => { const formattedValue = formatGoRequestArgValue( paramKey, paramValue, context, ) return `${pascalCase(paramKey)}: ${formattedValue}` }) .join(',\n') const formatGoRequestArgValue = ( key: string, value: Json, context: GoContext, ): string => { if (value == null) return 'nil' if (typeof value === 'string') { return `${defaultGoPackageName}.String("${value}")` } if (typeof value === 'boolean') { return `${defaultGoPackageName}.Bool(${value})` } if (typeof value === 'number') { return `${defaultGoPackageName}.Float64(${value})` } if (Array.isArray(value)) { return formatGoRequestArrayValue(key, value, context) } if (typeof value === 'object') { return formatGoRequestObjectValue(key, value, context) } throw new Error(`Unsupported type: ${typeof value}`) } const formatGoRequestArrayValue = ( key: string, value: Json[], context: GoContext, ): string => { if (value.length === 0) { // in Go there's no way define an empty array without specifying type // and code samples definitions don't include the type annotations return 'nil' } 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}'`) } const { goPackageName, requestStructName } = context const arrayType = isPrimitiveValue(item) ? getPrimitiveTypeName(item) : `${goPackageName}.${pascalCase(`${requestStructName} ${key}`)}Item` return `[${value.length}]${arrayType}{${formattedItems.join(', ')}}` } const isPrimitiveValue = (value: Json): value is string | number | boolean => typeof value !== 'object' && value !== null const getPrimitiveTypeName = (value: string | number | boolean): string => { switch (typeof value) { case 'string': return 'string' case 'number': return 'float64' case 'boolean': return 'bool' } } const formatGoRequestObjectValue = ( key: string, value: Record<string, Json>, context: GoContext, ): string => { if (Object.keys(value).length === 0) { return 'struct{}{}' } const formattedEntries = Object.entries(value) .map( ([objKey, val]) => `${pascalCase(objKey)}: ${formatGoRequestArgValue(objKey, val, context)}`, ) .join(', ') const { goPackageName, requestStructName } = context return `${goPackageName}.${pascalCase(`${requestStructName} ${key}`)}{${formattedEntries}}` } export const createGoResponse = ( { response, title }: CodeSampleDefinition, context: CodeSampleContext, ): 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 rawItem = value[0] if (rawItem == null) { throw new Error(`Null value in response array for '${key}'`) } const updatedPropertyChain = [...propertyChain, key] const formattedItems = value.map((v) => formatGoResponseParamValue( { key, value: v, propertyChain: updatedPropertyChain }, responseResourceGoStructName, ), ) if (isPrimitiveValue(rawItem)) { const arrayType = getPrimitiveTypeName(rawItem) return `[]${arrayType}{${formattedItems.join(', ')}}` } else { 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('_')) }