Compare commits

...

1 Commits

Author SHA1 Message Date
Julien Fontanet
4b5d97f978 WiP: xapi-typegen 2022-09-02 11:10:15 +02:00
5 changed files with 286 additions and 0 deletions

View File

@@ -0,0 +1 @@
../../scripts/npmignore

View File

@@ -0,0 +1,90 @@
let indentLevel = 0
function indent() {
return ' '.repeat(indentLevel)
}
function quoteId(name) {
return /^[a-z0-9_]+$/i.test(name) ? name : JSON.stringify(name)
}
function genType(type, schema) {
if (type === 'array' && schema.items !== undefined) {
const { items } = schema
if (Array.isArray(items)) {
if (items.length !== 0) {
return ['[' + items.map(genTs).join(', ') + ']']
}
} else {
const { type } = items
if (type !== undefined && type.length !== 0) {
return genTs(items, true) + '[]'
} else {
return 'unknown[]'
}
}
}
if (type !== 'object') {
return type
}
const code = []
const { title } = schema
const isInterface = title !== undefined
if (isInterface) {
code.push('interface ', title, ' ')
}
const fieldDelimiter = (isInterface ? ';' : ',') + '\n'
const { additionalProperties, properties } = schema
const hasAdditionalProperties = additionalProperties?.type !== undefined
const propertiesKeys = Object.keys(properties ?? {})
if (!hasAdditionalProperties && propertiesKeys.length === 0) {
code.push('{}')
return code.join('')
}
code.push('{\n')
++indentLevel
for (const name of propertiesKeys.sort()) {
const schema = properties[name]
code.push(indent(), quoteId(name))
if (schema.optional) {
code.push('?')
}
code.push(': ')
code.push(genTs(schema))
code.push(fieldDelimiter)
}
if (hasAdditionalProperties) {
code.push(indent(), '[key: string]: ', genTs(additionalProperties), fieldDelimiter)
}
--indentLevel
code.push(indent(), '}')
return code.join('')
}
export function genTs(schema, groupMultiple = false) {
let { type } = schema
if (Array.isArray(type)) {
if (type.length !== 1) {
const code = type
.sort()
.map(type => genType(type, schema))
.join(' | ')
return groupMultiple ? '(' + code + ')' : code
}
type = type[0]
}
return genType(type, schema)
}

View File

@@ -0,0 +1,110 @@
const JSON_TYPES = {
__proto__: null,
array: true,
boolean: true,
null: true,
number: true,
object: true,
string: true,
}
function addType(schema, type) {
const previous = schema.type
if (previous === undefined) {
schema.type = type
} else if (Array.isArray(previous)) {
if (previous.indexOf(type) === -1) {
previous.push(type)
}
} else if (previous !== type) {
schema.type = [previous, type]
}
}
function getType(value) {
let type = typeof value
if (type === 'object') {
if (value === null) {
type = 'null'
} else if (Array.isArray(value)) {
type = 'array'
}
}
if (type in JSON_TYPES) {
return type
}
throw new TypeError('unsupported type: ' + type)
}
// like Math.max but v1 can be undefined
const max = (v1, v2) => (v1 > v2 ? v1 : v2)
// like Math.min but v1 can be undefined
const min = (v1, v2) => (v1 < v2 ? v1 : v2)
function updateSchema_(path, value, schema = { __proto__: null }, getOption) {
if (value === undefined) {
schema.optional = true
} else {
const type = getType(value)
addType(schema, type)
if (type === 'array') {
const items = schema.items ?? (schema.items = { __proto__: null })
const pathLength = path.length
if (Array.isArray(items)) {
for (let i = 0, n = value.length; i < n; ++i) {
path[pathLength] = i
items[i] = updateSchema_(path, value[i], items[i], getOption)
}
} else {
for (let i = 0, n = value.length; i < n; ++i) {
path[pathLength] = i
updateSchema_(path, value[i], items, getOption)
}
}
path.length = pathLength
} else if (type === 'number') {
if (getOption('computeMinimum', path)) {
schema.minimum = min(schema.minimum, value)
}
if (getOption('computeMaximum', path)) {
schema.maximum = max(schema.maximum, value)
}
} else if (type === 'object') {
const pathLength = path.length
const { additionalProperties } = schema
if (typeof additionalProperties === 'object') {
for (const key of Object.keys(value)) {
path[pathLength] = key
updateSchema_(path, value[key], additionalProperties, getOption)
}
} else {
const properties = schema.properties ?? (schema.properties = { __proto__: null })
// handle missing properties
for (const key of Object.keys(properties)) {
if (!Object.hasOwn(value, key)) {
properties[key].optional = true
}
}
// handle existing properties
for (const key of Object.keys(value)) {
path[pathLength] = key
properties[key] = updateSchema_(path, value[key], properties[key], getOption)
}
}
path.length = pathLength
}
}
return schema
}
export function updateSchema(value, schema, options) {
const getOption = options == null ? Function.prototype : typeof options === 'object' ? opt => options[opt] : options
return updateSchema_([], value, schema, getOption)
}

View File

@@ -0,0 +1,65 @@
import { readFileSync } from 'fs'
import { genTs } from './_genTs.mjs'
import { updateSchema } from './_updateSchema.mjs'
const upperCamelCase = s =>
s
.split(/[^a-zA-Z]+/)
.map(s => s[0].toUpperCase() + s.slice(1).toLocaleLowerCase())
.join('')
const objects = JSON.parse(readFileSync('./objects.json'))
for (const type of Object.keys(objects).sort()) {
const schema = {
__proto__: null,
title: upperCamelCase(type),
type: 'object',
properties: {
assigned_ips: {
additionalProperties: {},
},
bios_strings: {
additionalProperties: {},
},
features: {
additionalProperties: {},
},
license_params: {
additionalProperties: {},
},
networks: {
additionalProperties: {},
},
other_config: {
additionalProperties: {},
},
other: {
additionalProperties: {},
},
restrictions: {
additionalProperties: {},
},
sm_config: {
additionalProperties: {},
},
xenstore_data: {
additionalProperties: {},
},
VCPUs_utilisation: {
additionalProperties: {},
},
},
}
for (const object of Object.values(objects[type])) {
updateSchema(object, schema)
}
for (const name of Object.keys(schema.properties)) {
if (schema.properties[name].type === undefined) {
delete schema.properties[name]
}
}
console.log(genTs(schema))
}

View File

@@ -0,0 +1,20 @@
{
"private": true,
"name": "@xen-orchestra/xapi-typegen",
"homepage": "https://github.com/vatesfr/xen-orchestra/tree/master/@xen-orchestra/xapi-typegen",
"bugs": "https://github.com/vatesfr/xen-orchestra/issues",
"repository": {
"directory": "@xen-orchestra/xapi-typegen",
"type": "git",
"url": "https://github.com/vatesfr/xen-orchestra.git"
},
"author": {
"name": "Vates SAS",
"url": "https://vates.fr"
},
"license": "AGPL-3.0-or-later",
"version": "0.0.0",
"engines": {
"node": ">=16.9"
}
}