Skip to content

Commit 8621c58

Browse files
authored
Generating Schemas for Particle Accelerator with schema2kotlin (#4829)
## Summary Modifies schema2kotlin tool to add generated schemas to Entity_Specs. Schemas can be accessed from the specs statically as a .schema property. This PR also introduces a SchemaRegistry. All generated schemas are automatically registered in the registry. Currently, collections of references are not supported. ## Changelog * First iteration of schema gen on entities * packaging in a schema registry * Generation adds to schema registry * excluding wasm from generation * Improved shape of registry * minor adjustments * sigh lint * Added test for mapOf * no difference w/ wasm golden * rm import * rm unused field * grooming schema registry * addField arguments are more ergonomic * ktlint fix * EntitySpec Interface can return a schema * fixed build * revising from feedback * Fixed indentation * Potential presub fix: added dep to rule * Potential Fix: create alias in 3rd party * Found right place to assign deps * Need to handle wasm use case * Treat wasm, jvm separately. * rm alias for wasm * Cleaner sdk solution: export core/data * rm whitespace, ran buildifier
1 parent 623daf7 commit 8621c58

File tree

10 files changed

+206
-19
lines changed

10 files changed

+206
-19
lines changed

java/arcs/core/data/SchemaRegistry.kt

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2020 Google LLC.
3+
*
4+
* This code may only be used under the BSD style license found at
5+
* http://polymer.github.io/LICENSE.txt
6+
*
7+
* Code distributed by Google as part of this project is also subject to an additional IP rights
8+
* grant found at
9+
* http://polymer.github.io/PATENTS.txt
10+
*/
11+
package arcs.core.data
12+
13+
typealias SchemaHash = String
14+
15+
/**
16+
* A registry for generated [Schema]s.
17+
*/
18+
object SchemaRegistry {
19+
private val schemas = mutableMapOf<SchemaHash, Schema>()
20+
21+
/** Store a [Schema] in the registry. */
22+
fun register(schema: Schema) {
23+
schemas[schema.hash] = schema
24+
}
25+
26+
/** Given a [SchemaHash], return the [Schema] for that hash, if it exists. */
27+
operator fun get(hash: SchemaHash) = schemas[hash]
28+
}

java/arcs/core/storage/api/BUILD

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ arcs_kt_library(
1212
srcs = glob(["*.kt"]),
1313
deps = [
1414
"//java/arcs/core/common",
15-
"//java/arcs/core/data:rawentity",
15+
"//java/arcs/core/data",
1616
"//java/arcs/core/data/util:data-util",
1717
"//third_party/kotlin/kotlinx_coroutines",
1818
],

java/arcs/core/storage/api/Entity.kt

+4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ package arcs.core.storage.api
1313

1414
import arcs.core.common.Referencable
1515
import arcs.core.data.RawEntity
16+
import arcs.core.data.Schema
1617
import arcs.core.data.util.ReferencablePrimitive
1718
import kotlin.IllegalArgumentException
1819
import kotlin.reflect.KClass
@@ -38,6 +39,9 @@ interface EntitySpec<T : Entity> {
3839
* TODO: replace this with kotlinx.serialization
3940
*/
4041
fun deserialize(data: RawEntity): T
42+
43+
/** Returns the corresponding [Schema] for the specified [Entity]. */
44+
fun schema(): Schema
4145
}
4246

4347
/**

java/arcs/sdk/BUILD

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ arcs_kt_jvm_library(
1818
]),
1919
visibility = ["//visibility:public"],
2020
exports = [
21+
"//java/arcs/core/data",
2122
"//java/arcs/core/data:rawentity",
2223
"//java/arcs/core/data/util:data-util",
2324
"//java/arcs/core/host",

src/tools/schema2base.ts

+17-3
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,16 @@ import {Runtime} from '../runtime/runtime.js';
1515
import {SchemaGraph, SchemaNode} from './schema2graph.js';
1616
import {ParticleSpec} from '../runtime/particle-spec.js';
1717

18+
export type AddFieldOptions = Readonly<{
19+
field: string;
20+
typeChar: string;
21+
isOptional?: boolean;
22+
refClassName?: string;
23+
isCollection?: boolean;
24+
}>;
25+
1826
export interface ClassGenerator {
19-
addField(field: string, typeChar: string, isOptional: boolean, refClassName: string|null): void;
27+
addField(opts: AddFieldOptions): void;
2028
generate(schemaHash: string, fieldCount: number): string;
2129
}
2230

@@ -78,14 +86,20 @@ export abstract class Schema2Base {
7886
for (const [field, descriptor] of fields) {
7987
if (descriptor.kind === 'schema-primitive') {
8088
if (['Text', 'URL', 'Number', 'Boolean'].includes(descriptor.type)) {
81-
generator.addField(field, descriptor.type[0], false, null);
89+
generator.addField({field, typeChar: descriptor.type[0]});
8290
} else {
8391
throw new Error(`Schema type '${descriptor.type}' for field '${field}' is not supported`);
8492
}
8593
} else if (descriptor.kind === 'schema-reference') {
86-
generator.addField(field, 'R', false, node.refs.get(field).name);
94+
generator.addField({field, typeChar: 'R', refClassName: node.refs.get(field).name});
8795
} else if (descriptor.kind === 'schema-collection' && descriptor.schema.kind === 'schema-reference') {
8896
// TODO: support collections of references
97+
} else if (descriptor.kind === 'schema-collection') {
98+
const schema = descriptor.schema;
99+
if (!['Text', 'URL', 'Number', 'Boolean'].includes(schema.type)) {
100+
throw new Error(`Schema type '${schema.type}' for field '${field}' is not supported`);
101+
}
102+
generator.addField({field, typeChar: schema.type[0], isCollection: true});
89103
} else {
90104
throw new Error(`Schema kind '${descriptor.kind}' for field '${field}' is not supported`);
91105
}

src/tools/schema2cpp.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* subject to an additional IP rights grant found at
88
* http://polymer.github.io/PATENTS.txt
99
*/
10-
import {Schema2Base, ClassGenerator} from './schema2base.js';
10+
import {Schema2Base, ClassGenerator, AddFieldOptions} from './schema2base.js';
1111
import {SchemaNode} from './schema2graph.js';
1212
import {ParticleSpec} from '../runtime/particle-spec.js';
1313
import {Type} from '../runtime/type.js';
@@ -113,7 +113,7 @@ class CppGenerator implements ClassGenerator {
113113

114114
constructor(readonly node: SchemaNode, readonly namespace: string) {}
115115

116-
addField(field: string, typeChar: string, isOptional: boolean, refClassName: string|null) {
116+
addField({field, typeChar, refClassName, isOptional = false, isCollection = false}: AddFieldOptions) {
117117
const fixed = fixName(field);
118118
const valid = `${field}_valid_`;
119119
let {type, defaultVal, isString} = typeMap[typeChar];

src/tools/schema2kotlin.ts

+68-8
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* subject to an additional IP rights grant found at
88
* http://polymer.github.io/PATENTS.txt
99
*/
10-
import {Schema2Base, ClassGenerator} from './schema2base.js';
10+
import {Schema2Base, ClassGenerator, AddFieldOptions} from './schema2base.js';
1111
import {SchemaNode} from './schema2graph.js';
1212
import {ParticleSpec} from '../runtime/particle-spec.js';
1313
import minimist from 'minimist';
@@ -28,10 +28,10 @@ const keywords = [
2828
];
2929

3030
const typeMap = {
31-
'T': {type: 'String', decodeFn: 'decodeText()', defaultVal: `""`},
32-
'U': {type: 'String', decodeFn: 'decodeText()', defaultVal: `""`},
33-
'N': {type: 'Double', decodeFn: 'decodeNum()', defaultVal: '0.0'},
34-
'B': {type: 'Boolean', decodeFn: 'decodeBool()', defaultVal: 'false'},
31+
'T': {type: 'String', decodeFn: 'decodeText()', defaultVal: `""`, schemaType: 'FieldType.Text'},
32+
'U': {type: 'String', decodeFn: 'decodeText()', defaultVal: `""`, schemaType: 'FieldType.Text'},
33+
'N': {type: 'Double', decodeFn: 'decodeNum()', defaultVal: '0.0', schemaType: 'FieldType.Number'},
34+
'B': {type: 'Boolean', decodeFn: 'decodeBool()', defaultVal: 'false', schemaType: 'FieldType.Boolean'},
3535
};
3636

3737
export class Schema2Kotlin extends Schema2Base {
@@ -54,7 +54,14 @@ package ${this.scope}
5454
// Current implementation doesn't support references or optional field detection
5555
5656
import arcs.sdk.*
57-
${this.opts.wasm ? 'import arcs.sdk.wasm.*' : 'import arcs.core.storage.api.toPrimitiveValue\nimport arcs.core.data.RawEntity\nimport arcs.core.data.util.toReferencable\nimport arcs.core.data.util.ReferencablePrimitive'}
57+
${this.opts.wasm ?
58+
`import arcs.sdk.wasm.*` :
59+
`\
60+
import arcs.sdk.Entity
61+
import arcs.core.data.*
62+
import arcs.core.data.util.toReferencable
63+
import arcs.core.data.util.ReferencablePrimitive
64+
import arcs.core.storage.api.toPrimitiveValue`}
5865
`;
5966
}
6067

@@ -128,7 +135,7 @@ abstract class Abstract${particleName} : ${this.opts.wasm ? 'WasmParticleImpl' :
128135
}
129136
}
130137

131-
class KotlinGenerator implements ClassGenerator {
138+
export class KotlinGenerator implements ClassGenerator {
132139
fields: string[] = [];
133140
fieldVals: string[] = [];
134141
setFields: string[] = [];
@@ -142,11 +149,13 @@ class KotlinGenerator implements ClassGenerator {
142149
fieldSerializes: string[] = [];
143150
fieldDeserializes: string[] = [];
144151
fieldsForToString: string[] = [];
152+
singletonSchemaFields: string[] = [];
153+
collectionSchemaFields: string[] = [];
145154

146155
constructor(readonly node: SchemaNode, private readonly opts: minimist.ParsedArgs) {}
147156

148157
// TODO: allow optional fields in kotlin
149-
addField(field: string, typeChar: string, isOptional: boolean, refClassName: string|null) {
158+
addField({field, typeChar, refClassName, isOptional = false, isCollection = false}: AddFieldOptions) {
150159
// TODO: support reference types in kotlin
151160
if (typeChar === 'R') return;
152161

@@ -179,6 +188,46 @@ class KotlinGenerator implements ClassGenerator {
179188
this.fieldSerializes.push(`"${field}" to ${fixed}.toReferencable()`);
180189
this.fieldDeserializes.push(`${fixed} = data.singletons["${fixed}"].toPrimitiveValue(${type}::class, ${defaultVal})`);
181190
this.fieldsForToString.push(`${fixed} = $${fixed}`);
191+
if (isCollection) {
192+
this.collectionSchemaFields.push(`"${field}" to ${typeMap[typeChar].schemaType}`);
193+
} else {
194+
this.singletonSchemaFields.push(`"${field}" to ${typeMap[typeChar].schemaType}`);
195+
}
196+
}
197+
198+
mapOf(items: string[]): string {
199+
switch (items.length) {
200+
case 0:
201+
return `emptyMap()`;
202+
case 1:
203+
return `mapOf(${items[0]})`;
204+
default:
205+
return `\
206+
mapOf(
207+
${this.leftPad(items.join(',\n'), 4)}
208+
)`;
209+
}
210+
211+
}
212+
213+
createSchema(schemaHash: string): string {
214+
const schemaNames = this.node.schema.names.map(n => `SchemaName("${n}")`);
215+
return `\
216+
Schema(
217+
listOf(${schemaNames.join(',\n' + ' '.repeat(8))}),
218+
SchemaFields(
219+
singletons = ${this.leftPad(this.mapOf(this.singletonSchemaFields), 8, true)},
220+
collections = ${this.leftPad(this.mapOf(this.collectionSchemaFields), 8, true)}
221+
),
222+
"${schemaHash}"
223+
)`;
224+
}
225+
226+
leftPad(input: string, indent: number, skipFirst: boolean = false) {
227+
return input
228+
.split('\n')
229+
.map((line: string, idx: number) => (idx === 0 && skipFirst) ? line : ' '.repeat(indent) + line)
230+
.join('\n');
182231
}
183232

184233
generate(schemaHash: string, fieldCount: number): string {
@@ -254,7 +303,18 @@ ${this.opts.wasm ? `
254303
}
255304
256305
class ${name}_Spec() : ${this.getType('EntitySpec')}<${name}> {
306+
${this.opts.wasm ? '' : `\
257307
308+
companion object {
309+
val schema = ${this.leftPad(this.createSchema(schemaHash), 8, true)}
310+
311+
init {
312+
SchemaRegistry.register(schema)
313+
}
314+
}
315+
316+
override fun schema() = schema
317+
`}
258318
override fun create() = ${name}()
259319
${!this.opts.wasm ? `
260320
override fun deserialize(data: RawEntity): ${name} {

src/tools/tests/goldens/generated-schemas.jvm.kt

+42-2
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ package arcs.sdk
99
// Current implementation doesn't support references or optional field detection
1010

1111
import arcs.sdk.*
12-
import arcs.core.storage.api.toPrimitiveValue
13-
import arcs.core.data.RawEntity
12+
import arcs.sdk.Entity
13+
import arcs.core.data.*
1414
import arcs.core.data.util.toReferencable
1515
import arcs.core.data.util.ReferencablePrimitive
16+
import arcs.core.storage.api.toPrimitiveValue
1617

1718
class GoldInternal1() : Entity {
1819

@@ -71,6 +72,23 @@ class GoldInternal1() : Entity {
7172

7273
class GoldInternal1_Spec() : EntitySpec<GoldInternal1> {
7374

75+
companion object {
76+
val schema = Schema(
77+
listOf(),
78+
SchemaFields(
79+
singletons = mapOf("val" to FieldType.Text),
80+
collections = emptyMap()
81+
),
82+
"485712110d89359a3e539dac987329cd2649d889"
83+
)
84+
85+
init {
86+
SchemaRegistry.register(schema)
87+
}
88+
}
89+
90+
override fun schema() = schema
91+
7492
override fun create() = GoldInternal1()
7593

7694
override fun deserialize(data: RawEntity): GoldInternal1 {
@@ -179,6 +197,28 @@ class Gold_Data() : Entity {
179197

180198
class Gold_Data_Spec() : EntitySpec<Gold_Data> {
181199

200+
companion object {
201+
val schema = Schema(
202+
listOf(),
203+
SchemaFields(
204+
singletons = mapOf(
205+
"num" to FieldType.Number,
206+
"txt" to FieldType.Text,
207+
"lnk" to FieldType.Text,
208+
"flg" to FieldType.Boolean
209+
),
210+
collections = emptyMap()
211+
),
212+
"d8058d336e472da47b289eafb39733f77eadb111"
213+
)
214+
215+
init {
216+
SchemaRegistry.register(schema)
217+
}
218+
}
219+
220+
override fun schema() = schema
221+
182222
override fun create() = Gold_Data()
183223

184224
override fun deserialize(data: RawEntity): Gold_Data {

src/tools/tests/schema2kotlin-test.ts

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* @license
3+
* Copyright (c) 2020 Google Inc. All rights reserved.
4+
* This code may only be used under the BSD style license found at
5+
* http://polymer.github.io/LICENSE.txt
6+
* Code distributed by Google as part of this project is also
7+
* subject to an additional IP rights grant found at
8+
* http://polymer.github.io/PATENTS.txt
9+
*/
10+
11+
12+
import {assert} from '../../platform/chai-node.js';
13+
import {KotlinGenerator} from '../schema2kotlin.js';
14+
import {SchemaNode} from '../schema2graph.js';
15+
import {Schema} from '../../runtime/schema.js';
16+
17+
18+
describe('schema2wasm', () => {
19+
describe('kotlin-generator', () => {
20+
const ktGen = new KotlinGenerator(new SchemaNode(new Schema([], {}), 'dummyNode'), {arg: '', _: []});
21+
it('when no items are present, it creates an empty map', () => {
22+
const actual = ktGen.mapOf([]);
23+
24+
assert.strictEqual('emptyMap()', actual);
25+
});
26+
it('when one item is present, it creates a single-line map', () => {
27+
const actual = ktGen.mapOf([`"a" to "b"`]);
28+
29+
assert.strictEqual('mapOf("a" to "b")', actual);
30+
});
31+
it('when multiple items are present, it creates a multi-line map', () => {
32+
const actual = ktGen.mapOf([`"a" to "b"`, `"b" to "c"`]);
33+
34+
assert.strictEqual(`\
35+
mapOf(
36+
"a" to "b",
37+
"b" to "c"
38+
)`, actual);
39+
});
40+
});
41+
});

src/tools/tests/schema2wasm-test.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@
99
*/
1010
import {assert} from '../../platform/chai-web.js';
1111
import {Manifest} from '../../runtime/manifest.js';
12-
import {Schema} from '../../runtime/schema.js';
1312
import {Dictionary} from '../../runtime/hot.js';
14-
import {Schema2Base, ClassGenerator} from '../schema2base.js';
13+
import {Schema2Base, ClassGenerator, AddFieldOptions} from '../schema2base.js';
1514
import {SchemaNode} from '../schema2graph.js';
1615
import {Schema2Cpp} from '../schema2cpp.js';
1716
import {Schema2Kotlin} from '../schema2kotlin.js';
@@ -32,7 +31,7 @@ class Schema2Mock extends Schema2Base {
3231
const collector = {count: 0, adds: []};
3332
this.res[node.name] = collector;
3433
return {
35-
addField(field: string, typeChar: string, isOptional: boolean, refClassName: string|null) {
34+
addField({field, typeChar, isOptional, refClassName}: AddFieldOptions) {
3635
const refInfo = refClassName ? `<${refClassName}>` : '';
3736
collector.adds.push(field + ':' + typeChar + refInfo + (isOptional ? '?' : ''));
3837
},

0 commit comments

Comments
 (0)