aws-amplify / amplify-category-api

The AWS Amplify CLI is a toolchain for simplifying serverless web and mobile development. This plugin provides functionality for the API category, allowing for the creation and management of GraphQL and REST based backends for your amplify project.
https://docs.amplify.aws/
Apache License 2.0
90 stars 76 forks source link

@primaryKey transformer does not override id field in appsync [continue] #695

Closed dobeerman closed 1 year ago

dobeerman commented 2 years ago

Before opening, please confirm:

How did you install the Amplify CLI?

yarn

If applicable, what version of Node.js are you using?

No response

Amplify CLI Version

9.1.0

What operating system are you using?

Ubuntu 20.04

Did you make any manual changes to the cloud resources managed by Amplify? Please describe the changes made.

We are on local storage and did not pushed to the cloud yet.

Amplify Categories

api

Amplify Commands

codegen

Describe the bug

With specified @primaryKey amplify api gql-compile generates incorrect resolvers and respectively, amplify codegen models generates incorrect models that still include ids.

Expected behavior

Default id field is overridden with the specified as @primaryKey one.

Reproduction steps

  1. Create a model with no id
  2. Specify any field with @primaryKey directive
  3. Run amplify api gql-compile
  4. Resolvers will contain id field

GraphQL schema(s)

```graphql type Profile @model @auth(rules: [{ allow: owner }]) { userId: ID! @primaryKey firstName: String lastName: String createdAt: AWSDateTime! updatedAt: AWSDateTime! } ```

Project Identifier

5716387d43d0cb85d4d69e4e2309abcf

Log output

``` $ cat ~/.amplify/logs/amplify-cli-2022-07-25.log 2022-07-25T07:46:01.353Z|info : amplify models codegen 2022-07-25T07:46:01.398Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} 2022-07-25T07:48:48.933Z|info : amplify models codegen 2022-07-25T07:48:48.989Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} 2022-07-25T07:50:01.790Z|info : amplify models codegen 2022-07-25T07:50:01.830Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} 2022-07-25T07:53:04.137Z|info : amplify gql-compile api 2022-07-25T07:53:04.190Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} 2022-07-25T07:53:34.327Z|info : amplify gql-compile api 2022-07-25T07:53:34.366Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} 2022-07-25T07:53:40.757Z|info : amplify models codegen 2022-07-25T07:53:40.804Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} 2022-07-25T07:55:44.141Z|info : amplify gql-compile api 2022-07-25T07:55:44.183Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} 2022-07-25T07:55:49.907Z|info : amplify models codegen 2022-07-25T07:55:49.960Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} 2022-07-25T07:56:26.828Z|info : amplify gql-compile api 2022-07-25T07:56:26.871Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} 2022-07-25T07:56:32.184Z|info : amplify models codegen 2022-07-25T07:56:32.225Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} 2022-07-25T07:57:33.941Z|info : amplify models codegen 2022-07-25T07:57:33.991Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} 2022-07-25T08:00:30.157Z|info : amplify upgrade core 2022-07-25T08:00:30.196Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} 2022-07-25T08:01:15.064Z|info : amplify gql-compile api 2022-07-25T08:01:15.102Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} 2022-07-25T08:01:21.169Z|info : amplify models codegen 2022-07-25T08:01:21.212Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} 2022-07-25T08:02:46.015Z|info : amplify gql-compile api 2022-07-25T08:02:46.066Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} 2022-07-25T08:03:05.358Z|info : amplify gql-compile api 2022-07-25T08:03:05.400Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} 2022-07-25T08:13:57.475Z|info : amplify version core 2022-07-25T10:09:40.299Z|info : amplify diagnose core {"send-report":true,"yes":false} 2022-07-25T10:09:40.339Z|info : amplify-cli-core.banner-message/index.ts.fetch banner messages from https://aws-amplify.github.io/amplify-cli/banner-message.json({} ```

Additional information

Flutter project

sundersc commented 2 years ago

Hi @dobeerman - Could you provide the resolver names containing the id field? The only resolver I see contains id field with custom primary key is Mutation.createModel.init.1.req.vtl - It assigns default value to id field if it is defined on the model. However this doesn't affect the functionality in any way, the model should work as expected - the dynamoDB table should have the partition and sort keys as configured using primaryKey directive. Please let us know if you have any questions.

dobeerman commented 2 years ago

Hi @sundersc

Firstly, thanks for your quick reply.

This is the auto-generated (by amplify api gql-compile command) Mutation.createProfile.req.vtl file

```vtl ## [Start] Create Request template. ** #set( $args = $util.defaultIfNull($ctx.stash.transformedArgs, $ctx.args) ) ## Set the default values to put request ** #set( $mergedValues = $util.defaultIfNull($ctx.stash.defaultValues, {}) ) ## copy the values from input ** $util.qr($mergedValues.putAll($util.defaultIfNull($args.input, {}))) ## set the typename ** $util.qr($mergedValues.put("__typename", "Profile")) #set( $PutObject = { "version": "2018-05-29", "operation": "PutItem", "attributeValues": $util.dynamodb.toMapValues($mergedValues), "condition": $condition } ) #if( $args.condition ) $util.qr($ctx.stash.conditions.add($args.condition)) #end ## Begin - key condition ** #if( $ctx.stash.metadata.modelObjectKey ) #set( $keyConditionExpr = {} ) #set( $keyConditionExprNames = {} ) #foreach( $entry in $ctx.stash.metadata.modelObjectKey.entrySet() ) $util.qr($keyConditionExpr.put("keyCondition$velocityCount", { "attributeExists": false })) $util.qr($keyConditionExprNames.put("#keyCondition$velocityCount", "$entry.key")) #end $util.qr($ctx.stash.conditions.add($keyConditionExpr)) #else $util.qr($ctx.stash.conditions.add({ "id": { "attributeExists": false } })) #end ## End - key condition ** ## Start condition block ** #if( $ctx.stash.conditions && $ctx.stash.conditions.size() != 0 ) #set( $mergedConditions = { "and": $ctx.stash.conditions } ) #set( $Conditions = $util.parseJson($util.transform.toDynamoDBConditionExpression($mergedConditions)) ) #if( $Conditions.expressionValues && $Conditions.expressionValues.size() == 0 ) #set( $Conditions = { "expression": $Conditions.expression, "expressionNames": $Conditions.expressionNames } ) #end ## End condition block ** #end #if( $Conditions ) #if( $keyConditionExprNames ) $util.qr($Conditions.expressionNames.putAll($keyConditionExprNames)) #end $util.qr($PutObject.put("condition", $Conditions)) #end #if( $ctx.stash.metadata.modelObjectKey ) $util.qr($PutObject.put("key", $ctx.stash.metadata.modelObjectKey)) #else #set( $Key = { "id": $util.dynamodb.toDynamoDB($mergedValues.id) } ) $util.qr($PutObject.put("key", $Key)) #end $util.toJson($PutObject) ## [End] Create Request template. ** ```

and this is the auto-generated by amplify codegen models command model

```dart /* * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://aws.amazon.com/apache2.0 * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ // NOTE: This file is generated and may not follow lint rules defined in your app // Generated files can be excluded from analysis in analysis_options.yaml // For more info, see: https://dart.dev/guides/language/analysis-options#excluding-code-from-analysis // ignore_for_file: public_member_api_docs, annotate_overrides, dead_code, dead_codepublic_member_api_docs, depend_on_referenced_packages, file_names, library_private_types_in_public_api, no_leading_underscores_for_library_prefixes, no_leading_underscores_for_local_identifiers, non_constant_identifier_names, null_check_on_nullable_type_parameter, prefer_adjacent_string_concatenation, prefer_const_constructors, prefer_if_null_operators, prefer_interpolation_to_compose_strings, slash_for_doc_comments, sort_child_properties_last, unnecessary_const, unnecessary_constructor_name, unnecessary_late, unnecessary_new, unnecessary_null_aware_assignments, unnecessary_nullable_for_final_variable_declarations, unnecessary_string_interpolations, use_build_context_synchronously import 'package:amplify_core/amplify_core.dart'; import 'package:flutter/foundation.dart'; /** This is an auto generated class representing the Profile type in your schema. */ @immutable class Profile extends Model { static const classType = const _ProfileModelType(); final String id; final String? _userId; final String? _firstName; final String? _lastName; final TemporalDateTime? _createdAt; final TemporalDateTime? _updatedAt; @override getInstanceType() => classType; @override String getId() { return id; } String get userId { try { return _userId!; } catch(e) { throw new AmplifyCodeGenModelException( AmplifyExceptionMessages.codeGenRequiredFieldForceCastExceptionMessage, recoverySuggestion: AmplifyExceptionMessages.codeGenRequiredFieldForceCastRecoverySuggestion, underlyingException: e.toString() ); } } String? get firstName { return _firstName; } String? get lastName { return _lastName; } TemporalDateTime get createdAt { try { return _createdAt!; } catch(e) { throw new AmplifyCodeGenModelException( AmplifyExceptionMessages.codeGenRequiredFieldForceCastExceptionMessage, recoverySuggestion: AmplifyExceptionMessages.codeGenRequiredFieldForceCastRecoverySuggestion, underlyingException: e.toString() ); } } TemporalDateTime get updatedAt { try { return _updatedAt!; } catch(e) { throw new AmplifyCodeGenModelException( AmplifyExceptionMessages.codeGenRequiredFieldForceCastExceptionMessage, recoverySuggestion: AmplifyExceptionMessages.codeGenRequiredFieldForceCastRecoverySuggestion, underlyingException: e.toString() ); } } const Profile._internal({required this.id, required userId, firstName, lastName, required createdAt, required updatedAt}): _userId = userId, _firstName = firstName, _lastName = lastName, _createdAt = createdAt, _updatedAt = updatedAt; factory Profile({String? id, required String userId, String? firstName, String? lastName, required TemporalDateTime createdAt, required TemporalDateTime updatedAt}) { return Profile._internal( id: id == null ? UUID.getUUID() : id, userId: userId, firstName: firstName, lastName: lastName, createdAt: createdAt, updatedAt: updatedAt); } bool equals(Object other) { return this == other; } @override bool operator ==(Object other) { if (identical(other, this)) return true; return other is Profile && id == other.id && _userId == other._userId && _firstName == other._firstName && _lastName == other._lastName && _createdAt == other._createdAt && _updatedAt == other._updatedAt; } @override int get hashCode => toString().hashCode; @override String toString() { var buffer = new StringBuffer(); buffer.write("Profile {"); buffer.write("id=" + "$id" + ", "); buffer.write("userId=" + "$_userId" + ", "); buffer.write("firstName=" + "$_firstName" + ", "); buffer.write("lastName=" + "$_lastName" + ", "); buffer.write("createdAt=" + (_createdAt != null ? _createdAt!.format() : "null") + ", "); buffer.write("updatedAt=" + (_updatedAt != null ? _updatedAt!.format() : "null")); buffer.write("}"); return buffer.toString(); } Profile copyWith({String? id, String? userId, String? firstName, String? lastName, TemporalDateTime? createdAt, TemporalDateTime? updatedAt}) { return Profile._internal( id: id ?? this.id, userId: userId ?? this.userId, firstName: firstName ?? this.firstName, lastName: lastName ?? this.lastName, createdAt: createdAt ?? this.createdAt, updatedAt: updatedAt ?? this.updatedAt); } Profile.fromJson(Map json) : id = json['id'], _userId = json['userId'], _firstName = json['firstName'], _lastName = json['lastName'], _createdAt = json['createdAt'] != null ? TemporalDateTime.fromString(json['createdAt']) : null, _updatedAt = json['updatedAt'] != null ? TemporalDateTime.fromString(json['updatedAt']) : null; Map toJson() => { 'id': id, 'userId': _userId, 'firstName': _firstName, 'lastName': _lastName, 'createdAt': _createdAt?.format(), 'updatedAt': _updatedAt?.format() }; static final QueryField ID = QueryField(fieldName: "profile.id"); static final QueryField USERID = QueryField(fieldName: "userId"); static final QueryField FIRSTNAME = QueryField(fieldName: "firstName"); static final QueryField LASTNAME = QueryField(fieldName: "lastName"); static final QueryField CREATEDAT = QueryField(fieldName: "createdAt"); static final QueryField UPDATEDAT = QueryField(fieldName: "updatedAt"); static var schema = Model.defineSchema(define: (ModelSchemaDefinition modelSchemaDefinition) { modelSchemaDefinition.name = "Profile"; modelSchemaDefinition.pluralName = "Profiles"; modelSchemaDefinition.authRules = [ AuthRule( authStrategy: AuthStrategy.OWNER, ownerField: "owner", identityClaim: "cognito:username", provider: AuthRuleProvider.USERPOOLS, operations: [ ModelOperation.CREATE, ModelOperation.UPDATE, ModelOperation.DELETE, ModelOperation.READ ]) ]; modelSchemaDefinition.addField(ModelFieldDefinition.id()); modelSchemaDefinition.addField(ModelFieldDefinition.field( key: Profile.USERID, isRequired: true, ofType: ModelFieldType(ModelFieldTypeEnum.string) )); modelSchemaDefinition.addField(ModelFieldDefinition.field( key: Profile.FIRSTNAME, isRequired: false, ofType: ModelFieldType(ModelFieldTypeEnum.string) )); modelSchemaDefinition.addField(ModelFieldDefinition.field( key: Profile.LASTNAME, isRequired: false, ofType: ModelFieldType(ModelFieldTypeEnum.string) )); modelSchemaDefinition.addField(ModelFieldDefinition.field( key: Profile.CREATEDAT, isRequired: true, ofType: ModelFieldType(ModelFieldTypeEnum.dateTime) )); modelSchemaDefinition.addField(ModelFieldDefinition.field( key: Profile.UPDATEDAT, isRequired: true, ofType: ModelFieldType(ModelFieldTypeEnum.dateTime) )); }); } class _ProfileModelType extends ModelType { const _ProfileModelType(); @override Profile fromJson(Map jsonData) { return Profile.fromJson(jsonData); } } ```

So, as you can see, id field still persists on the model and in the resolver. Am I missed something? :thinking:

sundersc commented 2 years ago

@dobeerman - It looks like conflict resolution is enabled (using DataStore) on your project. Please correct me if I'm wrong.

DataStore expects id field as primary key (Docs ref below) https://docs.amplify.aws/lib/datastore/relational/q/platform/flutter/

You can safely ignore the id references in the resolver.

## This assigns value to id field if primary key is not explicity defined
#if( $ctx.stash.metadata.modelObjectKey )
  $util.qr($PutObject.put("key", $ctx.stash.metadata.modelObjectKey))
#else
  #set( $Key = {
    "id":   $util.dynamodb.toDynamoDB($mergedValues.id)
  } )
  $util.qr($PutObject.put("key", $Key))
#end
dobeerman commented 2 years ago

@sundersc thank you for the clarification! Now I'm clear.

The only one question remains: do I still need to specify id when I update/delete the item?

AnilMaktala commented 1 year ago

Hi @dobeerman, Sorry for the delay. Yes, you still need to specify id to update and delete the item.