-
Notifications
You must be signed in to change notification settings - Fork 4
/
templater.ts
133 lines (115 loc) · 4.64 KB
/
templater.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import { compile } from 'ejs'
import json5 from 'json5'
import commentParser, { Tag } from 'comment-parser'
import { BaseAvroSchema, RecordAvroSchema, ArrayAvroSchema, FixedAvroSchema, MapAvroSchema, EnumAvroSchema, FieldAvroSchema } from './types'
import { Common } from './base'
export const defaultTemplate = `import A from 'avroschema-definer'
<% if (comment.description) { -%>
/**
* <%= comment.description %>
*/
<% } -%>
const <%= comment.tags ? comment.tags.variableName.name : 'Schema' %> = <%- avscToDefinerCode(schema) %>
export default <%= comment.tags ? comment.tags.variableName.name : 'Schema' %>
`
export const parseSchema = (schema: string) => {
const [comment] = commentParser(schema)
const avro: BaseAvroSchema | BaseAvroSchema[] = json5.parse(schema)
return {
comment: {
description: comment?.description,
tags: comment?.tags.reduce<Record<string, Tag>>((temp, el) => ({ ...temp, [el.tag]: el }), {})
},
schema: avro,
avscToDefinerCode
}
}
export default (template: string = defaultTemplate) => {
const render = compile(template)
return (schema: string, data?: Record<string, string>) => render({ ...parseSchema(schema), ...data })
}
const defaultLogicalTypesMapping = {
'timestamp-millis': '<Date>',
date: '<Date>',
decimal: '<number>'
}
function escapeJsComment (value: string) {
return value
.replace(/[/][*]/g, '\\/*')
.replace(/[*][/]/g, '*\\/')
}
function composeJSDoc (field: FieldAvroSchema): string {
const jsdocItems = ['/**']
if (field.aliases) jsdocItems.push(` * @avro-aliases ${escapeJsComment(field.aliases.join(','))}`)
if (field.order) jsdocItems.push(` * @avro-order ${escapeJsComment(field.order)}`)
if (field.doc) jsdocItems.push(` * @avro-doc ${escapeJsComment(field.doc)}`)
if (field.default) jsdocItems.push(` * @avro-default ${escapeJsComment(field.default)}`)
jsdocItems.push(' */\n')
if (jsdocItems.length === 2) return ''
return jsdocItems.map(v => ` ${v}`).join('\n')
}
export const avscToDefinerCode = (avro: BaseAvroSchema | BaseAvroSchema[], logicalTypesMapping: Record<string, string> = defaultLogicalTypesMapping) => {
const recursive = (avro: BaseAvroSchema | BaseAvroSchema[]): string => {
const typeName = Array.isArray(avro) ? 'union' : typeof avro === 'string' ? avro : avro.type
if (!typeName) { throw new Error(`${avro} not valid`) }
const mapping: Record<string, any> = {
record: (record: RecordAvroSchema) => {
return `.record({\n${record.fields.map(field => {
const jsdocSection = composeJSDoc(field)
return `${jsdocSection} ${field.name}: ${recursive(field.type)}${suffix(field as unknown as Common)}`
}).join(',\n')}\n})`
},
array: (array: ArrayAvroSchema) => {
return `.array(${recursive(array.items)})`
},
union: (union: BaseAvroSchema[]) => {
return `.union(${union.map(recursive).join(', ')})`
},
fixed: (fixed: FixedAvroSchema) => {
return `.fixed(${fixed.size})`
},
map: (map: MapAvroSchema) => {
return `.map(${recursive(map.values)})`
},
enum: (enumerable: EnumAvroSchema) => {
return `.enum('${enumerable.symbols.join('\', \'')}')`
},
default: (type: BaseAvroSchema, typeName: string) => {
return `.${typeName}()`
}
}
const prefix = (common: Common) => {
if (typeof common !== 'object') { return '' }
const name = common.name ? `.name('${common.name}')` : ''
const namespace = common.namespace
? `.namespace('${common.namespace}')`
: ''
return name + namespace
}
const logicalType = (type: BaseAvroSchema) => {
if (typeof type !== 'object' || !type.logicalType) { return '' }
return `.logicalType${logicalTypesMapping[type.logicalType] || ''}('${type.logicalType}'${type.precision && type.scale
? `, { precision: ${type.precision}, scale: ${type.scale} }`
: ''})`
}
const suffix = (common: Common) => {
if (typeof common !== 'object') { return '' }
const doc = common.doc ? `.doc('${common.doc.replace(/'/g, "\\'")}')` : ''
const def = common.default
? `.default(${JSON.stringify(common.default)})`
: ''
const order = common.order ? `.order('${common.order}')` : ''
const aliases = common.aliases
? `.aliases('${common.aliases.join("', '")}')`
: ''
return def + doc + order + aliases
}
return ('A' +
prefix(avro as unknown as Common) +
(mapping[typeName] || mapping.default)(avro, typeName) +
logicalType(avro as BaseAvroSchema) +
suffix(avro as unknown as Common)
)
}
return recursive(avro)
}