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/resource-class.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,24 @@
{{> resource-class}}
{{/each}}
{{#each resourceAccessors}}
{{#if description}}
{{{rubyDoc description ../docIndent}}}
{{/if}}
{{../bodyIndent}}# @return [{{rubyResourceType this}}]
{{../bodyIndent}}resource_accessor :{{name}}, {{className}}
{{/each}}
{{#each resourceListAccessors}}
{{#if description}}
{{{rubyDoc description ../docIndent}}}
{{/if}}
{{../bodyIndent}}# @return [{{rubyResourceType this}}]
{{../bodyIndent}}resource_list_accessor :{{name}}, {{className}}
{{/each}}
{{#each accessors}}
{{#if description}}
{{{rubyDoc description ../docIndent}}}
{{/if}}
{{../bodyIndent}}# @return [{{rubyPropertyType this}}]
{{#if isDeprecated}}
{{{rubyDeprecatedDoc this ../docIndent}}}
{{/if}}
Expand All @@ -21,6 +30,7 @@
{{#if description}}
{{{rubyDoc description ../docIndent}}}
{{/if}}
{{../bodyIndent}}# @return [{{rubyPropertyType this}}]
{{#if isDeprecated}}
{{{rubyDeprecatedDoc this ../docIndent}}}
{{/if}}
Expand Down
10 changes: 10 additions & 0 deletions codegen/layouts/resource.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,24 @@ module Seam
{{> resource-class}}
{{/each}}
{{#each resourceAccessors}}
{{#if description}}
{{{rubyDoc description 6}}}
{{/if}}
# @return [{{rubyResourceType this}}]
resource_accessor :{{name}}, {{className}}
{{/each}}
{{#each resourceListAccessors}}
{{#if description}}
{{{rubyDoc description 6}}}
{{/if}}
# @return [{{rubyResourceType this}}]
resource_list_accessor :{{name}}, {{className}}
{{/each}}
{{#each accessors}}
{{#if description}}
{{{rubyDoc description 6}}}
{{/if}}
# @return [{{rubyPropertyType this}}]
{{#if isDeprecated}}
{{{rubyDeprecatedDoc this 6}}}
{{/if}}
Expand All @@ -32,6 +41,7 @@ module Seam
{{#if description}}
{{{rubyDoc description 6}}}
{{/if}}
# @return [{{rubyPropertyType this}}]
{{#if isDeprecated}}
{{{rubyDeprecatedDoc this 6}}}
{{/if}}
Expand Down
77 changes: 75 additions & 2 deletions codegen/lib/handlebars-helpers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type { Parameter, Property } from '@seamapi/blueprint'

export const identity = (x: unknown): unknown => x

export interface Documented {
Expand Down Expand Up @@ -27,8 +29,79 @@ export const rubyDeprecatedDoc = (
)
: ''

const nullable = (
type: string,
value: { isOptional: boolean; isNullable: boolean },
): string => (value.isOptional || value.isNullable ? `${type}, nil` : type)

const scalarType = (format: string, isInt = false): string => {
switch (format) {
case 'boolean':
return 'Boolean'
case 'number':
return isInt ? 'Integer' : 'Float'
case 'datetime':
return 'Time'
case 'id':
case 'string':
case 'enum':
return 'String'
case 'record':
case 'object':
return 'Hash'
default:
return 'Object'
}
}

export const rubyPropertyType = (property: Property): string => {
const type =
property.format === 'list'
? `Array<${scalarType(property.itemFormat, property.itemFormat === 'number' && property.isItemInt)}>`
: scalarType(
property.format,
property.format === 'number' && property.isInt,
)
// BaseResource normalizes response lists to [] even when the API sends nil.
return nullable(
type,
property.format === 'list'
? { ...property, isOptional: false, isNullable: false }
: property,
)
}

export const rubyResourceType = (
property: Property & { className: string },
): string => {
const type =
property.format === 'list'
? `Array<${property.className}>`
: property.className
return nullable(
type,
property.format === 'list'
? { ...property, isOptional: false, isNullable: false }
: property,
)
}

export const rubyParameterType = (parameter: Parameter): string => {
const type =
parameter.format === 'list'
? `Array<${scalarType(parameter.itemFormat, parameter.itemFormat === 'number' && parameter.isItemInt)}>`
: scalarType(
parameter.format,
parameter.format === 'number' && parameter.isInt,
)
return nullable(type, {
isOptional: !parameter.isRequired,
isNullable: parameter.isNullable,
})
}

export const rubyParamDoc = (
parameter: Documented & { name: string },
parameter: Documented & { name: string; rubyType?: string },
indentation: number,
): string => {
const [firstLine = '', ...remainingLines] = parameter.description.split('\n')
Expand All @@ -39,7 +112,7 @@ export const rubyParamDoc = (
: []
return comment(
[
`@param ${parameter.name} ${firstLine}`.trimEnd(),
`@param ${parameter.name}${parameter.rubyType == null ? '' : ` [${parameter.rubyType}]`} ${firstLine}`.trimEnd(),
...remainingLines,
...deprecation,
],
Expand Down
7 changes: 7 additions & 0 deletions codegen/lib/merge-properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ interface MergedDocs {
description: string
isDeprecated: boolean
deprecationMessage: string
isOptional: boolean
isNullable: boolean
}

// Each variant documents a property for its own case, which is accurate there
Expand All @@ -34,6 +36,11 @@ const mergeDocs = (occurrences: Property[]): MergedDocs => {
// is never dropped just because another variant omits it.
isDeprecated: deprecated != null,
deprecationMessage: deprecated?.deprecationMessage ?? '',
// A property is optional on the merged shape when any variant can omit it.
// Likewise, nullability must not be lost when the first occurrence is
// non-nullable.
isOptional: occurrences.some(({ isOptional }) => isOptional),
isNullable: occurrences.some(({ isNullable }) => isNullable),
}
}

Expand Down
2 changes: 2 additions & 0 deletions codegen/lib/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { pascalCase } from 'change-case'
import type Metalsmith from 'metalsmith'

import { convertCustomResourceName } from './custom-resource-name-conversions.js'
import { rubyParameterType } from './handlebars-helpers.js'
import { setClientLayoutContext } from './layouts/client.js'
import { setImportsLayoutContext } from './layouts/imports.js'
import { setResourceLayoutContext } from './layouts/resource.js'
Expand Down Expand Up @@ -217,6 +218,7 @@ const createClientMethod = (endpoint: Endpoint): ClientMethod => {
description: parameter.description,
isDeprecated: parameter.isDeprecated,
deprecationMessage: parameter.deprecationMessage,
rubyType: rubyParameterType(parameter),
required: parameter.isRequired,
position:
endpoint.name === 'get' && parameter.name === `${returnPath}_id`
Expand Down
1 change: 1 addition & 0 deletions codegen/lib/ruby-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export interface ClientMethodParameter {
description: string
isDeprecated: boolean
deprecationMessage: string
rubyType?: string
required?: boolean | undefined
position?: number | undefined
}
Expand Down
Loading
Loading