Skip to content

Commit 186f8cd

Browse files
authored
fix(appsync-modelgen-plugin): non model type with id field in java (#260)
1 parent 8c78e97 commit 186f8cd

File tree

3 files changed

+198
-46
lines changed

3 files changed

+198
-46
lines changed

packages/appsync-modelgen-plugin/src/__tests__/visitors/__snapshots__/appsync-java-visitor.test.ts.snap

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2605,6 +2605,125 @@ public final class Location {
26052605
"
26062606
`;
26072607

2608+
exports[`AppSyncModelVisitor Non model type should generate class for non model types with id field 1`] = `
2609+
"package com.amplifyframework.datastore.generated.model;
2610+
2611+
2612+
import androidx.core.util.ObjectsCompat;
2613+
2614+
import java.util.Objects;
2615+
import java.util.List;
2616+
2617+
/** This is an auto generated class representing the Reference type in your schema. */
2618+
public final class Reference {
2619+
private final String id;
2620+
private final ReferenceIdTypeEnum idType;
2621+
public String getId() {
2622+
return id;
2623+
}
2624+
2625+
public ReferenceIdTypeEnum getIdType() {
2626+
return idType;
2627+
}
2628+
2629+
private Reference(String id, ReferenceIdTypeEnum idType) {
2630+
this.id = id;
2631+
this.idType = idType;
2632+
}
2633+
2634+
@Override
2635+
public boolean equals(Object obj) {
2636+
if (this == obj) {
2637+
return true;
2638+
} else if(obj == null || getClass() != obj.getClass()) {
2639+
return false;
2640+
} else {
2641+
Reference reference = (Reference) obj;
2642+
return ObjectsCompat.equals(getId(), reference.getId()) &&
2643+
ObjectsCompat.equals(getIdType(), reference.getIdType());
2644+
}
2645+
}
2646+
2647+
@Override
2648+
public int hashCode() {
2649+
return new StringBuilder()
2650+
.append(getId())
2651+
.append(getIdType())
2652+
.toString()
2653+
.hashCode();
2654+
}
2655+
2656+
public static IdStep builder() {
2657+
return new Builder();
2658+
}
2659+
2660+
public CopyOfBuilder copyOfBuilder() {
2661+
return new CopyOfBuilder(id,
2662+
idType);
2663+
}
2664+
public interface IdStep {
2665+
IdTypeStep id(String id);
2666+
}
2667+
2668+
2669+
public interface IdTypeStep {
2670+
BuildStep idType(ReferenceIdTypeEnum idType);
2671+
}
2672+
2673+
2674+
public interface BuildStep {
2675+
Reference build();
2676+
}
2677+
2678+
2679+
public static class Builder implements IdStep, IdTypeStep, BuildStep {
2680+
private String id;
2681+
private ReferenceIdTypeEnum idType;
2682+
@Override
2683+
public Reference build() {
2684+
2685+
return new Reference(
2686+
id,
2687+
idType);
2688+
}
2689+
2690+
@Override
2691+
public IdTypeStep id(String id) {
2692+
Objects.requireNonNull(id);
2693+
this.id = id;
2694+
return this;
2695+
}
2696+
2697+
@Override
2698+
public BuildStep idType(ReferenceIdTypeEnum idType) {
2699+
Objects.requireNonNull(idType);
2700+
this.idType = idType;
2701+
return this;
2702+
}
2703+
}
2704+
2705+
2706+
public final class CopyOfBuilder extends Builder {
2707+
private CopyOfBuilder(String id, ReferenceIdTypeEnum idType) {
2708+
super.id(id)
2709+
.idType(idType);
2710+
}
2711+
2712+
@Override
2713+
public CopyOfBuilder id(String id) {
2714+
return (CopyOfBuilder) super.id(id);
2715+
}
2716+
2717+
@Override
2718+
public CopyOfBuilder idType(ReferenceIdTypeEnum idType) {
2719+
return (CopyOfBuilder) super.idType(idType);
2720+
}
2721+
}
2722+
2723+
}
2724+
"
2725+
`;
2726+
26082727
exports[`AppSyncModelVisitor One to Many connection with no nullable and non nullable fields should generate class for many side of the connection 1`] = `
26092728
"package com.amplifyframework.datastore.generated.model;
26102729

packages/appsync-modelgen-plugin/src/__tests__/visitors/appsync-java-visitor.test.ts

Lines changed: 72 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,36 @@ const buildSchemaWithDirectives = (schema: String): GraphQLSchema => {
99
return buildSchema([schema, directives, scalars].join('\n'));
1010
};
1111

12-
const getVisitor = (schema: string, selectedType?: string, generate: CodeGenGenerateEnum = CodeGenGenerateEnum.code, usePipelinedTransformer: boolean = false) => {
12+
const getVisitor = (
13+
schema: string,
14+
selectedType?: string,
15+
generate: CodeGenGenerateEnum = CodeGenGenerateEnum.code,
16+
usePipelinedTransformer: boolean = false,
17+
) => {
1318
const ast = parse(schema);
1419
const builtSchema = buildSchemaWithDirectives(schema);
1520
const visitor = new AppSyncModelJavaVisitor(
1621
builtSchema,
17-
{ directives, target: 'android', generate, scalars: JAVA_SCALAR_MAP, isTimestampFieldsAdded: true, handleListNullabilityTransparently: true, usePipelinedTransformer: usePipelinedTransformer },
22+
{
23+
directives,
24+
target: 'android',
25+
generate,
26+
scalars: JAVA_SCALAR_MAP,
27+
isTimestampFieldsAdded: true,
28+
handleListNullabilityTransparently: true,
29+
usePipelinedTransformer: usePipelinedTransformer,
30+
},
1831
{ selectedType },
1932
);
2033
visit(ast, { leave: visitor });
2134
return visitor;
2235
};
2336

24-
const getVisitorPipelinedTransformer = (schema: string, selectedType?: string, generate: CodeGenGenerateEnum = CodeGenGenerateEnum.code) => {
37+
const getVisitorPipelinedTransformer = (
38+
schema: string,
39+
selectedType?: string,
40+
generate: CodeGenGenerateEnum = CodeGenGenerateEnum.code,
41+
) => {
2542
return getVisitor(schema, selectedType, generate, true);
2643
};
2744

@@ -98,7 +115,7 @@ describe('AppSyncModelVisitor', () => {
98115
it('Should generate a class a model with all optional fields except id field', () => {
99116
const schema = /* GraphQL */ `
100117
type SimpleModel @model {
101-
id: ID!,
118+
id: ID!
102119
name: String
103120
bar: String
104121
}
@@ -196,23 +213,23 @@ describe('AppSyncModelVisitor', () => {
196213
describe('vNext transformer feature parity tests', () => {
197214
it('should produce the same result for @primaryKey as the primary key variant of @key', async () => {
198215
const schemaV1 = /* GraphQL */ `
199-
type authorBook @model @key(fields: ["author_id"]) {
200-
id: ID!
201-
author_id: ID!
202-
book_id: ID!
203-
author: String
204-
book: String
205-
}
206-
`;
216+
type authorBook @model @key(fields: ["author_id"]) {
217+
id: ID!
218+
author_id: ID!
219+
book_id: ID!
220+
author: String
221+
book: String
222+
}
223+
`;
207224
const schemaV2 = /* GraphQL */ `
208-
type authorBook @model {
209-
id: ID!
210-
author_id: ID! @primaryKey
211-
book_id: ID!
212-
author: String
213-
book: String
214-
}
215-
`;
225+
type authorBook @model {
226+
id: ID!
227+
author_id: ID! @primaryKey
228+
book_id: ID!
229+
author: String
230+
book: String
231+
}
232+
`;
216233
const visitorV1 = getVisitor(schemaV1, 'authorBook');
217234
const visitorV2 = getVisitorPipelinedTransformer(schemaV2, 'authorBook');
218235
const version1Code = visitorV1.generate();
@@ -223,23 +240,23 @@ describe('AppSyncModelVisitor', () => {
223240

224241
it('should produce the same result for @index as the secondary index variant of @key', async () => {
225242
const schemaV1 = /* GraphQL */ `
226-
type authorBook @model @key(fields: ["id"]) @key(name: "authorSecondary", fields: ["author_id", "author"]) {
227-
id: ID!
228-
author_id: ID!
229-
book_id: ID!
230-
author: String
231-
book: String
232-
}
233-
`;
243+
type authorBook @model @key(fields: ["id"]) @key(name: "authorSecondary", fields: ["author_id", "author"]) {
244+
id: ID!
245+
author_id: ID!
246+
book_id: ID!
247+
author: String
248+
book: String
249+
}
250+
`;
234251
const schemaV2 = /* GraphQL */ `
235-
type authorBook @model {
236-
id: ID! @primaryKey
237-
author_id: ID! @index(name: "authorSecondary", sortKeyFields: ["author"])
238-
book_id: ID!
239-
author: String
240-
book: String
241-
}
242-
`;
252+
type authorBook @model {
253+
id: ID! @primaryKey
254+
author_id: ID! @index(name: "authorSecondary", sortKeyFields: ["author"])
255+
book_id: ID!
256+
author: String
257+
book: String
258+
}
259+
`;
243260
const visitorV1 = getVisitor(schemaV1, 'authorBook');
244261
const visitorV2 = getVisitorPipelinedTransformer(schemaV2, 'authorBook');
245262
const version1Code = visitorV1.generate();
@@ -260,9 +277,7 @@ describe('AppSyncModelVisitor', () => {
260277
name: String
261278
}
262279
263-
type ListContainer
264-
@model
265-
{
280+
type ListContainer @model {
266281
id: ID!
267282
name: String
268283
list: [Int]
@@ -383,11 +398,11 @@ describe('AppSyncModelVisitor', () => {
383398

384399
it('should generate class with non-default providers', () => {
385400
const schema = /* GraphQL */ `
386-
type Employee @model @auth(rules: [{ allow: owner }, { allow: private, provider:"iam" } ]) {
401+
type Employee @model @auth(rules: [{ allow: owner }, { allow: private, provider: "iam" }]) {
387402
id: ID!
388403
name: String!
389404
address: String!
390-
ssn: String @auth(rules: [{ allow: groups, provider:"oidc", groups: ["Admins"] }])
405+
ssn: String @auth(rules: [{ allow: groups, provider: "oidc", groups: ["Admins"] }])
391406
}
392407
`;
393408
const visitor = getVisitor(schema, 'Employee');
@@ -453,6 +468,16 @@ describe('AppSyncModelVisitor', () => {
453468
lang: String!
454469
}
455470
`;
471+
const nonModelwithIdSchema = /* GraphQL */ `
472+
enum ReferenceIdTypeEnum {
473+
ASIN
474+
OBJECT_ID
475+
}
476+
type Reference {
477+
id: String!
478+
idType: ReferenceIdTypeEnum!
479+
}
480+
`;
456481
it('should generate class for non model types', () => {
457482
const visitor = getVisitor(schema, 'Location');
458483
const generatedCode = visitor.generate();
@@ -465,6 +490,12 @@ describe('AppSyncModelVisitor', () => {
465490
expect(() => validateJava(generatedCode)).not.toThrow();
466491
expect(generatedCode).toMatchSnapshot();
467492
});
493+
it('should generate class for non model types with id field', () => {
494+
const visitor = getVisitor(nonModelwithIdSchema, 'Reference');
495+
const generatedCode = visitor.generate();
496+
expect(() => validateJava(generatedCode)).not.toThrow();
497+
expect(generatedCode).toMatchSnapshot();
498+
});
468499
});
469500

470501
it('should generate Temporal type for AWSDate* scalars', () => {

packages/appsync-modelgen-plugin/src/visitors/appsync-java-visitor.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ export class AppSyncModelJavaVisitor<
261261
this.generateHashCodeMethod(nonModel, classDeclarationBlock);
262262

263263
// builder
264-
this.generateBuilderMethod(nonModel, classDeclarationBlock);
264+
this.generateBuilderMethod(nonModel, classDeclarationBlock, false);
265265

266266
// copyBuilder method
267267
this.generateCopyOfBuilderMethod(nonModel, classDeclarationBlock);
@@ -346,7 +346,7 @@ export class AppSyncModelJavaVisitor<
346346
protected generateStepBuilderInterfaces(model: CodeGenModel, isModel: boolean = true): JavaDeclarationBlock[] {
347347
const nonNullableFields = this.getWritableFields(model).filter(field => this.isRequiredField(field));
348348
const nullableFields = this.getWritableFields(model).filter(field => !this.isRequiredField(field));
349-
const requiredInterfaces = nonNullableFields.filter((field: CodeGenField) => !this.READ_ONLY_FIELDS.includes(field.name));
349+
const requiredInterfaces = nonNullableFields.filter((field: CodeGenField) => !(isModel && this.READ_ONLY_FIELDS.includes(field.name)));
350350
const interfaces = requiredInterfaces.map((field, idx) => {
351351
const isLastField = requiredInterfaces.length - 1 === idx ? true : false;
352352
const returnType = isLastField ? 'Build' : requiredInterfaces[idx + 1].name;
@@ -394,7 +394,7 @@ export class AppSyncModelJavaVisitor<
394394
protected generateBuilderClass(model: CodeGenModel, classDeclaration: JavaDeclarationBlock, isModel: boolean = true): void {
395395
const nonNullableFields = this.getWritableFields(model).filter(field => this.isRequiredField(field));
396396
const nullableFields = this.getWritableFields(model).filter(field => !this.isRequiredField(field));
397-
const stepFields = nonNullableFields.filter((field: CodeGenField) => !this.READ_ONLY_FIELDS.includes(field.name));
397+
const stepFields = nonNullableFields.filter((field: CodeGenField) => !(isModel && this.READ_ONLY_FIELDS.includes(field.name)));
398398
const stepInterfaces = stepFields.map((field: CodeGenField) => this.getStepInterfaceName(field.name));
399399

400400
const builderClassDeclaration = new JavaDeclarationBlock()
@@ -722,8 +722,10 @@ export class AppSyncModelJavaVisitor<
722722
* @param model
723723
* @param classDeclaration
724724
*/
725-
protected generateBuilderMethod(model: CodeGenModel, classDeclaration: JavaDeclarationBlock): void {
726-
const requiredFields = this.getWritableFields(model).filter(field => !field.isNullable && !this.READ_ONLY_FIELDS.includes(field.name));
725+
protected generateBuilderMethod(model: CodeGenModel, classDeclaration: JavaDeclarationBlock, isModel: boolean = true): void {
726+
const requiredFields = this.getWritableFields(model).filter(
727+
field => !field.isNullable && !(isModel && this.READ_ONLY_FIELDS.includes(field.name)),
728+
);
727729
const returnType = requiredFields.length ? this.getStepInterfaceName(requiredFields[0].name) : this.getStepInterfaceName('Build');
728730
classDeclaration.addClassMethod(
729731
'builder',

0 commit comments

Comments
 (0)