From 6d62ba9fd0de5f3687ffa1347ac9cca911657dd8 Mon Sep 17 00:00:00 2001 From: Zeyu Li Date: Wed, 13 Oct 2021 15:39:19 -0700 Subject: [PATCH] fix(appsync-modelgen-plugin): non model type with id field in java --- .../appsync-java-visitor.test.ts.snap | 119 ++++++++++++++++++ .../visitors/appsync-java-visitor.test.ts | 113 +++++++++++------ .../src/visitors/appsync-java-visitor.ts | 12 +- 3 files changed, 198 insertions(+), 46 deletions(-) diff --git a/packages/appsync-modelgen-plugin/src/__tests__/visitors/__snapshots__/appsync-java-visitor.test.ts.snap b/packages/appsync-modelgen-plugin/src/__tests__/visitors/__snapshots__/appsync-java-visitor.test.ts.snap index f91b787b5..646b8d724 100644 --- a/packages/appsync-modelgen-plugin/src/__tests__/visitors/__snapshots__/appsync-java-visitor.test.ts.snap +++ b/packages/appsync-modelgen-plugin/src/__tests__/visitors/__snapshots__/appsync-java-visitor.test.ts.snap @@ -2605,6 +2605,125 @@ public final class Location { " `; +exports[`AppSyncModelVisitor Non model type should generate class for non model types with id field 1`] = ` +"package com.amplifyframework.datastore.generated.model; + + +import androidx.core.util.ObjectsCompat; + +import java.util.Objects; +import java.util.List; + +/** This is an auto generated class representing the Reference type in your schema. */ +public final class Reference { + private final String id; + private final ReferenceIdTypeEnum idType; + public String getId() { + return id; + } + + public ReferenceIdTypeEnum getIdType() { + return idType; + } + + private Reference(String id, ReferenceIdTypeEnum idType) { + this.id = id; + this.idType = idType; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } else if(obj == null || getClass() != obj.getClass()) { + return false; + } else { + Reference reference = (Reference) obj; + return ObjectsCompat.equals(getId(), reference.getId()) && + ObjectsCompat.equals(getIdType(), reference.getIdType()); + } + } + + @Override + public int hashCode() { + return new StringBuilder() + .append(getId()) + .append(getIdType()) + .toString() + .hashCode(); + } + + public static IdStep builder() { + return new Builder(); + } + + public CopyOfBuilder copyOfBuilder() { + return new CopyOfBuilder(id, + idType); + } + public interface IdStep { + IdTypeStep id(String id); + } + + + public interface IdTypeStep { + BuildStep idType(ReferenceIdTypeEnum idType); + } + + + public interface BuildStep { + Reference build(); + } + + + public static class Builder implements IdStep, IdTypeStep, BuildStep { + private String id; + private ReferenceIdTypeEnum idType; + @Override + public Reference build() { + + return new Reference( + id, + idType); + } + + @Override + public IdTypeStep id(String id) { + Objects.requireNonNull(id); + this.id = id; + return this; + } + + @Override + public BuildStep idType(ReferenceIdTypeEnum idType) { + Objects.requireNonNull(idType); + this.idType = idType; + return this; + } + } + + + public final class CopyOfBuilder extends Builder { + private CopyOfBuilder(String id, ReferenceIdTypeEnum idType) { + super.id(id) + .idType(idType); + } + + @Override + public CopyOfBuilder id(String id) { + return (CopyOfBuilder) super.id(id); + } + + @Override + public CopyOfBuilder idType(ReferenceIdTypeEnum idType) { + return (CopyOfBuilder) super.idType(idType); + } + } + +} +" +`; + exports[`AppSyncModelVisitor One to Many connection with no nullable and non nullable fields should generate class for many side of the connection 1`] = ` "package com.amplifyframework.datastore.generated.model; diff --git a/packages/appsync-modelgen-plugin/src/__tests__/visitors/appsync-java-visitor.test.ts b/packages/appsync-modelgen-plugin/src/__tests__/visitors/appsync-java-visitor.test.ts index adcbbcd84..710ab9d4f 100644 --- a/packages/appsync-modelgen-plugin/src/__tests__/visitors/appsync-java-visitor.test.ts +++ b/packages/appsync-modelgen-plugin/src/__tests__/visitors/appsync-java-visitor.test.ts @@ -9,19 +9,36 @@ const buildSchemaWithDirectives = (schema: String): GraphQLSchema => { return buildSchema([schema, directives, scalars].join('\n')); }; -const getVisitor = (schema: string, selectedType?: string, generate: CodeGenGenerateEnum = CodeGenGenerateEnum.code, usePipelinedTransformer: boolean = false) => { +const getVisitor = ( + schema: string, + selectedType?: string, + generate: CodeGenGenerateEnum = CodeGenGenerateEnum.code, + usePipelinedTransformer: boolean = false, +) => { const ast = parse(schema); const builtSchema = buildSchemaWithDirectives(schema); const visitor = new AppSyncModelJavaVisitor( builtSchema, - { directives, target: 'android', generate, scalars: JAVA_SCALAR_MAP, isTimestampFieldsAdded: true, handleListNullabilityTransparently: true, usePipelinedTransformer: usePipelinedTransformer }, + { + directives, + target: 'android', + generate, + scalars: JAVA_SCALAR_MAP, + isTimestampFieldsAdded: true, + handleListNullabilityTransparently: true, + usePipelinedTransformer: usePipelinedTransformer, + }, { selectedType }, ); visit(ast, { leave: visitor }); return visitor; }; -const getVisitorPipelinedTransformer = (schema: string, selectedType?: string, generate: CodeGenGenerateEnum = CodeGenGenerateEnum.code) => { +const getVisitorPipelinedTransformer = ( + schema: string, + selectedType?: string, + generate: CodeGenGenerateEnum = CodeGenGenerateEnum.code, +) => { return getVisitor(schema, selectedType, generate, true); }; @@ -98,7 +115,7 @@ describe('AppSyncModelVisitor', () => { it('Should generate a class a model with all optional fields except id field', () => { const schema = /* GraphQL */ ` type SimpleModel @model { - id: ID!, + id: ID! name: String bar: String } @@ -196,23 +213,23 @@ describe('AppSyncModelVisitor', () => { describe('vNext transformer feature parity tests', () => { it('should produce the same result for @primaryKey as the primary key variant of @key', async () => { const schemaV1 = /* GraphQL */ ` - type authorBook @model @key(fields: ["author_id"]) { - id: ID! - author_id: ID! - book_id: ID! - author: String - book: String - } - `; + type authorBook @model @key(fields: ["author_id"]) { + id: ID! + author_id: ID! + book_id: ID! + author: String + book: String + } + `; const schemaV2 = /* GraphQL */ ` - type authorBook @model { - id: ID! - author_id: ID! @primaryKey - book_id: ID! - author: String - book: String - } - `; + type authorBook @model { + id: ID! + author_id: ID! @primaryKey + book_id: ID! + author: String + book: String + } + `; const visitorV1 = getVisitor(schemaV1, 'authorBook'); const visitorV2 = getVisitorPipelinedTransformer(schemaV2, 'authorBook'); const version1Code = visitorV1.generate(); @@ -223,23 +240,23 @@ describe('AppSyncModelVisitor', () => { it('should produce the same result for @index as the secondary index variant of @key', async () => { const schemaV1 = /* GraphQL */ ` - type authorBook @model @key(fields: ["id"]) @key(name: "authorSecondary", fields: ["author_id", "author"]) { - id: ID! - author_id: ID! - book_id: ID! - author: String - book: String - } - `; + type authorBook @model @key(fields: ["id"]) @key(name: "authorSecondary", fields: ["author_id", "author"]) { + id: ID! + author_id: ID! + book_id: ID! + author: String + book: String + } + `; const schemaV2 = /* GraphQL */ ` - type authorBook @model { - id: ID! @primaryKey - author_id: ID! @index(name: "authorSecondary", sortKeyFields: ["author"]) - book_id: ID! - author: String - book: String - } - `; + type authorBook @model { + id: ID! @primaryKey + author_id: ID! @index(name: "authorSecondary", sortKeyFields: ["author"]) + book_id: ID! + author: String + book: String + } + `; const visitorV1 = getVisitor(schemaV1, 'authorBook'); const visitorV2 = getVisitorPipelinedTransformer(schemaV2, 'authorBook'); const version1Code = visitorV1.generate(); @@ -260,9 +277,7 @@ describe('AppSyncModelVisitor', () => { name: String } - type ListContainer - @model - { + type ListContainer @model { id: ID! name: String list: [Int] @@ -383,11 +398,11 @@ describe('AppSyncModelVisitor', () => { it('should generate class with non-default providers', () => { const schema = /* GraphQL */ ` - type Employee @model @auth(rules: [{ allow: owner }, { allow: private, provider:"iam" } ]) { + type Employee @model @auth(rules: [{ allow: owner }, { allow: private, provider: "iam" }]) { id: ID! name: String! address: String! - ssn: String @auth(rules: [{ allow: groups, provider:"oidc", groups: ["Admins"] }]) + ssn: String @auth(rules: [{ allow: groups, provider: "oidc", groups: ["Admins"] }]) } `; const visitor = getVisitor(schema, 'Employee'); @@ -453,6 +468,16 @@ describe('AppSyncModelVisitor', () => { lang: String! } `; + const nonModelwithIdSchema = /* GraphQL */ ` + enum ReferenceIdTypeEnum { + ASIN + OBJECT_ID + } + type Reference { + id: String! + idType: ReferenceIdTypeEnum! + } + `; it('should generate class for non model types', () => { const visitor = getVisitor(schema, 'Location'); const generatedCode = visitor.generate(); @@ -465,6 +490,12 @@ describe('AppSyncModelVisitor', () => { expect(() => validateJava(generatedCode)).not.toThrow(); expect(generatedCode).toMatchSnapshot(); }); + it('should generate class for non model types with id field', () => { + const visitor = getVisitor(nonModelwithIdSchema, 'Reference'); + const generatedCode = visitor.generate(); + expect(() => validateJava(generatedCode)).not.toThrow(); + expect(generatedCode).toMatchSnapshot(); + }); }); it('should generate Temporal type for AWSDate* scalars', () => { diff --git a/packages/appsync-modelgen-plugin/src/visitors/appsync-java-visitor.ts b/packages/appsync-modelgen-plugin/src/visitors/appsync-java-visitor.ts index bcee006ba..3d1caa563 100644 --- a/packages/appsync-modelgen-plugin/src/visitors/appsync-java-visitor.ts +++ b/packages/appsync-modelgen-plugin/src/visitors/appsync-java-visitor.ts @@ -261,7 +261,7 @@ export class AppSyncModelJavaVisitor< this.generateHashCodeMethod(nonModel, classDeclarationBlock); // builder - this.generateBuilderMethod(nonModel, classDeclarationBlock); + this.generateBuilderMethod(nonModel, classDeclarationBlock, false); // copyBuilder method this.generateCopyOfBuilderMethod(nonModel, classDeclarationBlock); @@ -346,7 +346,7 @@ export class AppSyncModelJavaVisitor< protected generateStepBuilderInterfaces(model: CodeGenModel, isModel: boolean = true): JavaDeclarationBlock[] { const nonNullableFields = this.getWritableFields(model).filter(field => this.isRequiredField(field)); const nullableFields = this.getWritableFields(model).filter(field => !this.isRequiredField(field)); - const requiredInterfaces = nonNullableFields.filter((field: CodeGenField) => !this.READ_ONLY_FIELDS.includes(field.name)); + const requiredInterfaces = nonNullableFields.filter((field: CodeGenField) => !(isModel && this.READ_ONLY_FIELDS.includes(field.name))); const interfaces = requiredInterfaces.map((field, idx) => { const isLastField = requiredInterfaces.length - 1 === idx ? true : false; const returnType = isLastField ? 'Build' : requiredInterfaces[idx + 1].name; @@ -394,7 +394,7 @@ export class AppSyncModelJavaVisitor< protected generateBuilderClass(model: CodeGenModel, classDeclaration: JavaDeclarationBlock, isModel: boolean = true): void { const nonNullableFields = this.getWritableFields(model).filter(field => this.isRequiredField(field)); const nullableFields = this.getWritableFields(model).filter(field => !this.isRequiredField(field)); - const stepFields = nonNullableFields.filter((field: CodeGenField) => !this.READ_ONLY_FIELDS.includes(field.name)); + const stepFields = nonNullableFields.filter((field: CodeGenField) => !(isModel && this.READ_ONLY_FIELDS.includes(field.name))); const stepInterfaces = stepFields.map((field: CodeGenField) => this.getStepInterfaceName(field.name)); const builderClassDeclaration = new JavaDeclarationBlock() @@ -722,8 +722,10 @@ export class AppSyncModelJavaVisitor< * @param model * @param classDeclaration */ - protected generateBuilderMethod(model: CodeGenModel, classDeclaration: JavaDeclarationBlock): void { - const requiredFields = this.getWritableFields(model).filter(field => !field.isNullable && !this.READ_ONLY_FIELDS.includes(field.name)); + protected generateBuilderMethod(model: CodeGenModel, classDeclaration: JavaDeclarationBlock, isModel: boolean = true): void { + const requiredFields = this.getWritableFields(model).filter( + field => !field.isNullable && !(isModel && this.READ_ONLY_FIELDS.includes(field.name)), + ); const returnType = requiredFields.length ? this.getStepInterfaceName(requiredFields[0].name) : this.getStepInterfaceName('Build'); classDeclaration.addClassMethod( 'builder',