Skip to content

Instantly share code, notes, and snippets.

@matv-stripe
Created June 27, 2024 20:21
Show Gist options
  • Save matv-stripe/024852b3293e239add2e8f8da7ded412 to your computer and use it in GitHub Desktop.
Save matv-stripe/024852b3293e239add2e8f8da7ded412 to your computer and use it in GitHub Desktop.
Generate generate-sorbet-types from open api spec
import * as https from 'https';
import * as fs from 'fs';
interface SchemaProperty {
type: string;
format?: string;
nullable?: boolean;
$ref?: string;
additionalProperties?: any;
items?: SchemaProperty;
}
interface Schema {
type: string;
properties: { [key: string]: SchemaProperty };
required?: string[];
}
function fetchOpenApiSpec(): Promise<any> {
return new Promise((resolve, reject) => {
https.get('https://raw.githubusercontent.com/braintrustdata/braintrust-openapi/main/openapi/spec.json', (res) => {
let data = '';
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (error) {
reject(error);
}
});
}).on('error', (error) => {
reject(error);
});
});
}
function convertType(property: SchemaProperty): string {
if (property.$ref) {
return property.$ref.split('/').pop() || '';
}
switch (property.type) {
case 'string':
if (property.format === 'date-time') return 'Time';
if (property.format === 'uuid') return 'String';
return 'String';
case 'integer':
return 'Integer';
case 'datetime':
return 'Datetime'
case 'number':
return 'Float';
case 'boolean':
return 'T::Boolean';
case 'object':
if (property.additionalProperties) {
return `T::Hash[String, T.untyped]`;
}
return 'T::Hash[String, T.untyped]';
case 'array':
const subType = convertType(property.items as SchemaProperty);
return `T::Array[${subType}]`;
default:
return 'T.untyped';
}
}
function generateSorbetType(name: string, schema: Schema): string {
if (!schema.properties) {
return '';
}
let output = `class ${name} < T::Struct\n`;
for (const [propName, propSchema] of Object.entries(schema.properties)) {
const type = convertType(propSchema);
const isRequired = schema.required?.includes(propName);
const sorbetType = isRequired ? type : `T.nilable(${type})`;
output += ` const :${propName}, ${sorbetType}\n`;
}
output += 'end\n\n';
return output;
}
async function main() {
try {
const spec = await fetchOpenApiSpec();
const schemas = spec.components.schemas;
let output = '';
for (const [name, schema] of Object.entries(schemas)) {
output += generateSorbetType(name, schema as Schema);
}
fs.writeFileSync('sorbet_types.rb', output);
console.log('Sorbet types have been generated in sorbet_types.rb');
} catch (error) {
console.error('Error:', error);
}
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment