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

Add Go code samples #92

Merged
merged 32 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
06cb841
Add Go code samples
andrii-balitskyi Sep 24, 2024
6ecd8a3
ci: Generate code
seambot Sep 24, 2024
0c938b9
ci: Format code
seambot Sep 24, 2024
355878f
Fix go request and response string value samples
andrii-balitskyi Sep 24, 2024
b458db7
ci: Generate code
seambot Sep 24, 2024
dffe5e4
Make go responses to return json, correctly determine go package name
andrii-balitskyi Sep 26, 2024
19c0e49
Imporve readability and structure
andrii-balitskyi Sep 26, 2024
3dfbfba
ci: Generate code
seambot Sep 26, 2024
7f0e7d0
ci: Format code
seambot Sep 26, 2024
deec40c
Improve var name
andrii-balitskyi Sep 26, 2024
22f278d
Format
andrii-balitskyi Sep 26, 2024
197cb9f
Add go package import
andrii-balitskyi Sep 26, 2024
91ae178
ci: Generate code
seambot Sep 26, 2024
db4cd6f
Improve readability
andrii-balitskyi Sep 26, 2024
6025cb5
ci: Generate code
seambot Sep 26, 2024
0cddf41
Fix sdk imports
andrii-balitskyi Sep 26, 2024
7a2048d
ci: Generate code
seambot Sep 26, 2024
0d3cc1c
Imporve go package name determination and nested imports logic
andrii-balitskyi Sep 26, 2024
08d9c3f
ci: Format code
seambot Sep 26, 2024
b638638
Improve var names
andrii-balitskyi Sep 26, 2024
842baff
More renames
andrii-balitskyi Sep 26, 2024
9dc2687
Improve array property value handling
andrii-balitskyi Sep 26, 2024
b0fa535
Improve array item check
andrii-balitskyi Sep 26, 2024
829f5ac
Improve object property value handling
andrii-balitskyi Sep 26, 2024
4afc95b
Improve code readability and structure
andrii-balitskyi Sep 26, 2024
3384d75
Improve naming
andrii-balitskyi Sep 26, 2024
9a1ed46
Add comment
andrii-balitskyi Sep 26, 2024
52d95b8
Reorg function order
andrii-balitskyi Sep 26, 2024
00c6f70
Update src/lib/code-sample/go.ts
andrii-balitskyi Sep 27, 2024
382f701
Fix var names casing
andrii-balitskyi Sep 27, 2024
12ccd3a
Merge branch 'main' of github.com:seamapi/blueprint into go-code-samples
andrii-balitskyi Sep 27, 2024
63faa0f
ci: Format code
seambot Sep 27, 2024
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
167 changes: 167 additions & 0 deletions src/lib/code-sample/go.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
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,
): 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 = formatGoArgs(request.parameters)
const goSdkRequestArgs = `context.Background()${isReqWithParams ? `, ${goPackageName}.${requestStructName}(${formattedArgs})` : ''}`

const pathParts = request.path.split('/')

return `${goSdkImports}

client${pathParts.map((p) => pascalCase(p)).join('.')}(${goSdkRequestArgs})
`.trim()
}

const getGoPackageName = (path: string): string => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is an example where the import is not import api "github.com/seamapi/go?

Copy link
Contributor Author

@andrii-balitskyi andrii-balitskyi Sep 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For ex., import acs "github.com/seamapi/go/acs for acs.UsersListAccessibleEntrancesRequest or
import accesscodes "github.com/seamapi/go/accesscodes for accesscodes.UnmanagedConvertToManagedRequest or
import useridentities "github.com/seamapi/go/useridentities for useridentities.EnrollmentAutomationsDeleteRequest etc.

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(/^\/[^/]*/, '')

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

const formatGoValue = ({
value,
key,
}: {
value: Json
key: string
}): string => {
if (value == null) return 'nil'
if (typeof value === 'string') return `api.String("${value}")`
if (typeof value === 'boolean') return `api.Bool(${value})`
if (typeof value === 'number') return `api.Float64(${value})`

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

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

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

const formatGoArray = (value: Json[], key: string): 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) => formatGoValue({ value: v, key }))
const item = value[0]
if (item == null) {
throw new Error(`Null value in response array for '${key}'`)
}

const arrayType = isPrimitiveValue(item)
? getPrimitiveTypeName(item)
: `api.${pascalCase(key)}`

return `[${value.length}]${arrayType}{${formattedItems.join(', ')}}`
}

const isPrimitiveValue = (value: Json): boolean =>
value != null && typeof value !== 'object'

const getPrimitiveTypeName = (value: Json): string => {
switch (typeof value) {
case 'string':
return 'string'
case 'number':
return 'float64'
case 'boolean':
return 'bool'
default:
throw new Error(`Unsupported type: ${typeof value}`)
}
}

const formatGoObject = (value: Record<string, Json>, key: string): string => {
if (Object.keys(value).length === 0) {
return 'struct{}{}'
}

const formattedEntries = Object.entries(value)
.map(
([objKey, val]) =>
`${pascalCase(objKey)}: ${formatGoValue({ value: val, key: objKey })}`,
)
.join(', ')

return `api.${pascalCase(key)}{${formattedEntries}}`
}

export const createGoResponse = createJsonResponse
andrii-balitskyi marked this conversation as resolved.
Show resolved Hide resolved
11 changes: 10 additions & 1 deletion src/lib/code-sample/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
import { JsonSchema } from 'lib/json.js'

import { formatCodeRecords } from './format.js'
import { createGoRequest, createGoResponse } from './go.js'
import {
createJavascriptRequest,
createJavascriptResponse,
Expand Down Expand Up @@ -47,12 +48,13 @@ const CodeSampleSyntaxSchema = z.enum([
'php',
'ruby',
'bash',
'go',
])

export type CodeSampleSyntax = z.infer<typeof CodeSampleSyntaxSchema>

const CodeSchema = z.record(
z.enum(['javascript', 'python', 'php', 'ruby', 'seam_cli']),
z.enum(['javascript', 'python', 'php', 'ruby', 'seam_cli', 'go']),
z.object({
title: z.string().min(1),
request: z.string(),
Expand Down Expand Up @@ -118,6 +120,13 @@ export const createCodeSample = async (
request_syntax: 'bash',
response_syntax: 'json',
},
go: {
title: 'Go',
request: createGoRequest(codeSampleDefinition, context),
response: createGoResponse(codeSampleDefinition, context),
request_syntax: 'go',
response_syntax: 'json',
},
}

return {
Expand Down
Loading