forked from PolymerLabs/arcs
-
Notifications
You must be signed in to change notification settings - Fork 0
/
kotlin-schema-generator.ts
122 lines (110 loc) · 4.74 KB
/
kotlin-schema-generator.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
/**
* @license
* Copyright (c) 2020 Google Inc. All rights reserved.
* This code may only be used under the BSD style license found at
* http://polymer.github.io/LICENSE.txt
* Code distributed by Google as part of this project is also
* subject to an additional IP rights grant found at
* http://polymer.github.io/PATENTS.txt
*/
import {KotlinGenerationUtils, leftPad, quote} from './kotlin-generation-utils.js';
import {Schema} from '../types/lib-types.js';
import {KTExtracter} from './kotlin-refinement-generator.js';
import {assert} from '../platform/assert-web.js';
import {annotationsToKotlin} from './annotations-utils.js';
const ktUtils = new KotlinGenerationUtils();
/**
* Generates a Kotlin schema instance.
*/
export async function generateSchema(schema: Schema): Promise<string> {
if (schema.equals(Schema.EMPTY)) return `arcs.core.data.Schema.EMPTY`;
const schemaNames = schema.names.map(n => `arcs.core.data.SchemaName("${n}")`);
const {refinement, query} = generatePredicates(schema);
const singletons: string[] = [];
const collections: string[] = [];
await visitSchemaFields(schema, ({isCollection, field, schemaType}) =>
(isCollection ? collections : singletons).push(`"${field}" to ${schemaType}`));
return `\
arcs.core.data.Schema(
setOf(${ktUtils.joinWithIndents(schemaNames, {startIndent: 8})}),
arcs.core.data.SchemaFields(
singletons = ${leftPad(ktUtils.mapOf(singletons, 30), 8, true)},
collections = ${leftPad(ktUtils.mapOf(collections, 30), 8, true)}
),
${quote(await schema.hash())},
refinementExpression = ${refinement},
queryExpression = ${query}
)`;
}
interface SchemaField {
field: string;
isCollection: boolean;
schemaType: string;
}
async function visitSchemaFields(schema: Schema, visitor: (field: SchemaField) => void) {
for (const [field, descriptor] of Object.entries(schema.fields)) {
switch (descriptor.kind) {
case 'schema-collection':
visitor({field, isCollection: true, schemaType: await getSchemaType(field, descriptor.schema)});
break;
case 'schema-primitive':
case 'kotlin-primitive':
case 'schema-reference':
case 'schema-nested':
visitor({field, isCollection: false, schemaType: await getSchemaType(field, descriptor)});
break;
case 'schema-ordered-list':
visitor({field, isCollection: false, schemaType: await getSchemaType(field, {
...descriptor,
innerType: await getSchemaType(field, descriptor.schema)
})});
break;
default:
throw new Error(`Schema kind '${descriptor.kind}' for field '${field}' is not supported`);
}
}
}
async function getSchemaType(field: string, {kind, schema, type, innerType, annotations}): Promise<string> {
// Annotations are only passed on for references, we can add support for other field types as needed.
const fieldType = 'arcs.core.data.FieldType';
if (kind === 'schema-primitive') {
switch (type) {
case 'Text': return `${fieldType}.Text`;
case 'URL': return `${fieldType}.Text`;
case 'Number': return `${fieldType}.Number`;
case 'BigInt': return `${fieldType}.BigInt`;
case 'Boolean': return `${fieldType}.Boolean`;
default: break;
}
} else if (kind === 'kotlin-primitive') {
switch (type) {
case 'Byte': return `${fieldType}.Byte`;
case 'Short': return `${fieldType}.Short`;
case 'Int': return `${fieldType}.Int`;
case 'Long': return `${fieldType}.Long`;
case 'Char': return `${fieldType}.Char`;
case 'Float': return `${fieldType}.Float`;
case 'Double': return `${fieldType}.Double`;
default: break;
}
} else if (kind === 'schema-reference') {
const kannotations = (annotations && annotations.length) ? ', ' + annotationsToKotlin(annotations) : '';
return `${fieldType}.EntityRef(${quote(await schema.model.getEntitySchema().hash())}${kannotations})`;
} else if (kind === 'schema-nested') {
return `${fieldType}.InlineEntity(${quote(await schema.model.getEntitySchema().hash())})`;
} else if (kind === 'schema-ordered-list') {
assert(innerType, 'innerType must be provided for Lists');
return `${fieldType}.ListOf(${innerType})`;
}
throw new Error(`Schema kind '${kind}' for field '${field}' and type '${type}' is not supported`);
}
function generatePredicates(schema: Schema): {query: string, refinement: string} {
const hasRefinement = !!schema.refinement;
const hasQuery = hasRefinement && schema.refinement.getQueryParams().get('?');
const expression = leftPad(KTExtracter.fromSchema(schema), 8);
return {
// TODO(cypher1): Support multiple queries.
query: hasQuery ? `${expression}` : 'true.asExpr()',
refinement: (hasRefinement && !hasQuery) ? `${expression}` : `true.asExpr()`
};
}