Skip to content
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: 10 additions & 0 deletions codegen/layouts/partials/client-method.hbs
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
{{#if description}}
{{{rubyDoc description 6}}}
{{/if}}
{{#each parameters}}
{{{rubyParamDoc this 6}}}
{{/each}}
{{{rubyReturnDoc returnResource responseDescription 6}}}
{{#if isDeprecated}}
{{{rubyDeprecatedDoc this 6}}}
{{/if}}
def {{name}}{{#if hasSignature}}({{signatureParams}}){{/if}}
{{#if usesRes}}res = {{/if}}@client.post("{{path}}"{{#if hasParams}}, {{bodyParams}}.compact{{/if}})
{{#if isResource}}
Expand Down
26 changes: 23 additions & 3 deletions codegen/layouts/resource.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,32 @@

module Seam
module Resources
{{#if resource.description}}
{{{rubyDoc resource.description 4}}}
{{/if}}
{{#if resource.isDeprecated}}
{{{rubyDeprecatedDoc resource 4}}}
{{/if}}
class {{className}} < BaseResource
attr_accessor {{attrAccessors}}
{{#if hasDateAccessors}}
{{#each accessors}}
{{#if description}}
{{{rubyDoc description 6}}}
{{/if}}
{{#if isDeprecated}}
{{{rubyDeprecatedDoc this 6}}}
{{/if}}
attr_accessor :{{name}}
{{/each}}
{{#each dateAccessors}}

date_accessor {{dateAccessors}}
{{#if description}}
{{{rubyDoc description 6}}}
{{/if}}
{{#if isDeprecated}}
{{{rubyDeprecatedDoc this 6}}}
{{/if}}
date_accessor :{{name}}
{{/each}}
{{#if hasSupportModules}}

{{#if includeErrorsSupport}}
Expand Down
62 changes: 62 additions & 0 deletions codegen/lib/handlebars-helpers.ts
Original file line number Diff line number Diff line change
@@ -1 +1,63 @@
export const identity = (x: unknown): unknown => x

export interface Documented {
description: string
isDeprecated: boolean
deprecationMessage: string
}

const comment = (lines: string[], indentation: number): string => {
const prefix = `${' '.repeat(indentation)}#`
return lines
.map((line) => `${prefix}${line === '' ? '' : ` ${line}`}`)
.join('\n')
}

export const rubyDoc = (description: string, indentation: number): string =>
comment(description === '' ? [] : description.split('\n'), indentation)

export const rubyDeprecatedDoc = (
documented: Documented,
indentation: number,
): string =>
documented.isDeprecated
? comment(
[`@deprecated ${documented.deprecationMessage}`.trimEnd()],
indentation,
)
: ''

export const rubyParamDoc = (
parameter: Documented & { name: string },
indentation: number,
): string => {
const [firstLine = '', ...remainingLines] = parameter.description.split('\n')
const deprecation = parameter.isDeprecated
? [
`@deprecated ${parameter.name}: ${parameter.deprecationMessage}`.trimEnd(),
]
: []
return comment(
[
`@param ${parameter.name} ${firstLine}`.trimEnd(),
...remainingLines,
...deprecation,
],
indentation,
)
}

export const rubyReturnDoc = (
resource: string,
description: string,
indentation: number,
): string => {
const [firstLine = '', ...remainingLines] = description.split('\n')
return comment(
[
`@return [${resource === '' ? 'nil' : `Seam::Resources::${resource}`}] ${firstLine}`.trimEnd(),
...remainingLines,
],
indentation,
)
}
10 changes: 10 additions & 0 deletions codegen/lib/layouts/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ import {
} from '../ruby-client.js'

export interface ClientMethodLayoutContext {
description: string
isDeprecated: boolean
deprecationMessage: string
responseDescription: string
parameters: ClientMethod['parameters']
name: string
hasSignature: boolean
signatureParams: string
Expand Down Expand Up @@ -55,6 +60,11 @@ const getMethodLayoutContext = (
const isNil = !hasReturnValue

return {
description: method.description,
isDeprecated: method.isDeprecated,
deprecationMessage: method.deprecationMessage,
responseDescription: method.responseDescription,
parameters: sortedParameters,
name: methodName,
hasSignature: signatureParams.length > 0,
signatureParams,
Expand Down
27 changes: 21 additions & 6 deletions codegen/lib/layouts/resource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,28 @@ import { convertCustomResourceName } from '../custom-resource-name-conversions.j

export interface ResourceLayoutContext {
className: string
attrAccessors: string
hasDateAccessors: boolean
dateAccessors: string
resource: Documented
accessors: Array<Property & Documented>
dateAccessors: Array<Property & Documented>
hasSupportModules: boolean
includeErrorsSupport: boolean
includeWarningsSupport: boolean
}

interface Documented {
description: string
isDeprecated: boolean
deprecationMessage: string
}

export const setResourceLayoutContext = (
snakeName: string,
properties: Property[],
resource: {
description: string
isDeprecated: boolean
deprecationMessage: string
},
): ResourceLayoutContext => {
const attrs = properties
.filter((property) => property.format !== 'datetime')
Expand All @@ -35,9 +46,13 @@ export const setResourceLayoutContext = (

return {
className: pascalCase(convertCustomResourceName(snakeName)),
attrAccessors: noErrorWarningAttrs.map((attr) => `:${attr}`).join(', '),
hasDateAccessors: dateAttrs.length > 0,
dateAccessors: dateAttrs.map((attr) => `:${attr}`).join(', '),
resource,
accessors: properties
.filter((property) => noErrorWarningAttrs.includes(property.name))
.map((property) => property),
dateAccessors: properties
.filter((property) => dateAttrs.includes(property.name))
.map((property) => property),
hasSupportModules: includeErrorsSupport || includeWarningsSupport,
includeErrorsSupport,
includeWarningsSupport,
Expand Down
59 changes: 42 additions & 17 deletions codegen/lib/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type {
Blueprint,
Endpoint,
Property,
Resource,
Response,
} from '@seamapi/blueprint'
import { pascalCase } from 'change-case'
Expand Down Expand Up @@ -54,11 +55,11 @@ export const routes = (
resourceNames.push(name)
}

for (const [name, properties] of getResources(blueprint)) {
for (const [name, resource] of getResources(blueprint)) {
files[`${resourcesPath}/${name}.rb`] = {
contents: Buffer.from('\n'),
layout: 'resource.hbs',
...setResourceLayoutContext(name, properties),
...setResourceLayoutContext(name, resource.properties, resource),
}
resourceNames.push(name)
}
Expand Down Expand Up @@ -94,42 +95,59 @@ export const routes = (
}
}

const getResources = (blueprint: Blueprint): Array<[string, Property[]]> => {
const resources = new Map<string, Property[]>()
type ResourceDocumentation = Pick<
Resource,
'description' | 'isDeprecated' | 'deprecationMessage'
>

interface ResourceSource extends ResourceDocumentation {
properties: Property[]
}

const getResources = (
blueprint: Blueprint,
): Array<[string, ResourceSource]> => {
const resources = new Map<string, ResourceSource>()

for (const resource of blueprint.resources) {
resources.set(resource.resourceType, resource.properties)
resources.set(resource.resourceType, resource)
}

// The event resource only has the properties common to all events, but the
// SDK exposes a single SeamEvent class, so it needs an accessor for every
// property of every event variant.
const eventProperties = resources.get('event')
if (eventProperties != null) {
resources.set(
'event',
mergeProperties([
eventProperties,
const eventResource = resources.get('event')
if (eventResource != null) {
resources.set('event', {
...eventResource,
properties: mergeProperties([
eventResource.properties,
...blueprint.events.map((event) => event.properties),
]),
)
})
}

// Action attempts are one blueprint entry per action type, but the SDK
// exposes a single ActionAttempt class.
if (blueprint.actionAttempts.length > 0) {
resources.set(
'action_attempt',
mergeProperties(
const resource = blueprint.actionAttempts[0]
if (resource == null) throw new Error('Expected an action attempt resource')
resources.set('action_attempt', {
...resource,
properties: mergeProperties(
blueprint.actionAttempts.map(
(actionAttempt) => actionAttempt.properties,
),
),
)
})
}

if (blueprint.pagination != null) {
resources.set('pagination', blueprint.pagination.properties)
resources.set('pagination', {
...blueprint.pagination,
isDeprecated: false,
deprecationMessage: '',
})
}

return [...resources.entries()].sort(([a], [b]) => a.localeCompare(b))
Expand Down Expand Up @@ -215,9 +233,16 @@ const createClientMethod = (endpoint: Endpoint): ClientMethod => {

return {
methodName: endpoint.name,
description: endpoint.description,
isDeprecated: endpoint.isDeprecated,
deprecationMessage: endpoint.deprecationMessage,
responseDescription: endpoint.response.description,
path: endpoint.path,
parameters: endpoint.request.parameters.map((parameter) => ({
name: parameter.name,
description: parameter.description,
isDeprecated: parameter.isDeprecated,
deprecationMessage: parameter.deprecationMessage,
required: parameter.isRequired,
position:
endpoint.name === 'get' && parameter.name === `${returnPath}_id`
Expand Down
7 changes: 7 additions & 0 deletions codegen/lib/ruby-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@

export interface ClientMethodParameter {
name: string
description: string
isDeprecated: boolean
deprecationMessage: string
required?: boolean | undefined
position?: number | undefined
}

export interface ClientMethod {
methodName: string
description: string
isDeprecated: boolean
deprecationMessage: string
responseDescription: string
path: string
parameters: ClientMethodParameter[]
returnResource: string | null
Expand Down
57 changes: 55 additions & 2 deletions lib/seam/resources/access_code.rb

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading