aws-amplify / amplify-android

The fastest and easiest way to use AWS from your Android app.
https://docs.amplify.aws/lib/q/platform/android/
Apache License 2.0
249 stars 117 forks source link

[DataStore] Invalid AWSPhone values cause silent failure when saving model to DynamoDB #1462

Open HuiSF opened 3 years ago

HuiSF commented 3 years ago

Before opening, please confirm:

Language and Async Model

Java, Kotlin

Amplify Categories

DataStore

Gradle script dependencies

```groovy // Put output below this line dependencies { implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'com.google.android.material:material:1.3.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'com.amplifyframework:aws-api:1.24.1' implementation 'com.amplifyframework:aws-datastore:1.24.1' testImplementation 'junit:junit:4.+' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' } ```

Environment information

``` # Put output below this line ------------------------------------------------------------ Gradle 6.7.1 ------------------------------------------------------------ Build time: 2020-11-16 17:09:24 UTC Revision: 2972ff02f3210d2ceed2f1ea880f026acfbab5c0 Kotlin: 1.3.72 Groovy: 2.5.12 Ant: Apache Ant(TM) version 1.10.8 compiled on May 10 2020 JVM: 1.8.0_292 (AdoptOpenJDK 25.292-b10) OS: Mac OS X 10.16 x86_64 ```

Please include any relevant guides or documentation you're referencing

No response

Describe the bug

While using schemas with AWSPhone typed fields, if invalid phone numbers are provided, save API call succeeded, model saved into local DB, modal is not saved into DynamoDB due to DynamoDB value validation raised GraphQL error.

Expected behavior: save API call should return error with clear error message. Invalid data should not persisted in local DB.

Original issue: https://github.com/aws-amplify/amplify-flutter/issues/822#issuecomment-904919041 amplify-ios issue: https://github.com/aws-amplify/amplify-ios/issues/1393

Schema example

type Person @model {
  id: ID!
  name: String!
  phone: AWSPhone
}

Reproduction steps (if applicable)

  1. create a minimal app using amplify-android with above listed schema
  2. Save model with invalid phone number
  3. Observe

Code Snippet

Java code example

        Person person = Person.builder()
                .name("Test with phone")
                .phone("1110004444")
                .build();
        Amplify.DataStore.save(person,
                success -> Log.i("Tutorial", "Saved item: " + success.item().getPhone()),
                error -> Log.e("Tutorial", "Could not save item to DataStore", error)
        );

Log output

``` // Put your logs below this line I/Tutorial: Person {id=f153f9f6-f62d-4b65-8360-3f693cd1494e, name=Test with phone, department=null, phone=1110004444, createdAt=null, updatedAt=null} I/Tutorial: Saved item: 1110004444 W/System.err: SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. I/Tutorial: Record{id='d6ccd81c-051a-11ec-b8e4-4785562e7c29', containedModelId='f153f9f6-f62d-4b65-8360-3f693cd1494e', serializedMutationData='{"modelSchema":{"associations":{"department":{"associatedName":null,"associatedType":"Department","name":"BelongsTo","targetName":"departmentId"},"posts":{"associatedName":"author","associatedType":"Post","name":"HasMany","targetName":null}},"authRules":[],"fields":{"createdAt":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":true,"isRequired":false,"javaClassForValue":"com.amplifyframework.core.model.temporal.Temporal$DateTime","name":"createdAt","targetType":"AWSDateTime"},"department":{"authRules":[],"isArray":false,"isEnum":false,"isModel":true,"isReadOnly":false,"isRequired":false,"javaClassForValue":"com.amplifyframework.datastore.generated.model.Department","name":"department","targetType":"Department"},"id":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":false,"isRequired":true,"javaClassForValue":"java.lang.String","name":"id","targetType":"ID"},"name":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":false,"isRequired":true,"javaClassForValue":"java.lang.String","name":"name","targetType":"String"},"phone":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":false,"isRequired":false,"javaClassForValue":"java.lang.String","name":"phone","targetType":"AWSPhone"},"posts":{"authRules":[],"isArray":true,"isEnum":false,"isModel":false,"isReadOnly":false,"isRequired":false,"javaClassForValue":"java.util.List","name":"posts","targetType":"Post"},"updatedAt":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":true,"isRequired":false,"javaClassForValue":"com.amplifyframework.core.model.temporal.Temporal$DateTime","name":"updatedAt","targetType":"AWSDateTime"}},"indexes":{"byDepartmentId":{"indexFieldNames":["departmentId"],"indexName":"byDepartmentId"}},"modelClass":"com.amplifyframework.datastore.generated.model.Person","name":"Person","pluralName":"People"},"mutatedItem":{"id":"f153f9f6-f62d-4b65-8360-3f693cd1494e","modelSchema":{"associations":{"department":{"associatedName":null,"associatedType":"Department","name":"BelongsTo","targetName":"departmentId"},"posts":{"associatedName":"author","associatedType":"Post","name":"HasMany","targetName":null}},"authRules":[],"fields":{"createdAt":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":true,"isRequired":false,"javaClassForValue":"com.amplifyframework.core.model.temporal.Temporal$DateTime","name":"createdAt","targetType":"AWSDateTime"},"department":{"authRules":[],"isArray":false,"isEnum":false,"isModel":true,"isReadOnly":false,"isRequired":false,"javaClassForValue":"com.amplifyframework.datastore.generated.model.Department","name":"department","targetType":"Department"},"id":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":false,"isRequired":true,"javaClassForValue":"java.lang.String","name":"id","targetType":"ID"},"name":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":false,"isRequired":true,"javaClassForValue":"java.lang.String","name":"name","targetType":"String"},"phone":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":false,"isRequired":false,"javaClassForValue":"java.lang.String","name":"phone","targetType":"AWSPhone"},"posts":{"authRules":[],"isArray":true,"isEnum":false,"isModel":false,"isReadOnly":false,"isRequired":false,"javaClassForValue":"java.util.List","name":"posts","targetType":"Post"},"updatedAt":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":true,"isRequired":false,"javaClassForValue":"com.amplifyframework.core.model.temporal.Temporal$DateTime","name":"updatedAt","targetType":"AWSDateTime"}},"indexes":{"byDepartmentId":{"indexFieldNames":["departmentId"],"indexName":"byDepartmentId"}},"modelClass":"com.amplifyframework.datastore.generated.model.Person","name":"Person","pluralName":"People"},"serializedData":{"name":"Test w I/amplify:aws-datastore: Successfully enqueued PendingMutation{mutatedItem=SerializedModel{id='f153f9f6-f62d-4b65-8360-3f693cd1494e', serializedData={name=Test with phone, createdAt=null, id=f153f9f6-f62d-4b65-8360-3f693cd1494e, phone=1110004444, updatedAt=null}, modelName=Person}, mutationType=CREATE, mutationId=d6ccd81c-051a-11ec-b8e4-4785562e7c29, predicate=MatchAllQueryPredicate} I/Tutorial: Record{id='d6ccd81c-051a-11ec-b8e4-4785562e7c29', containedModelId='f153f9f6-f62d-4b65-8360-3f693cd1494e', serializedMutationData='{"modelSchema":{"associations":{"department":{"associatedName":null,"associatedType":"Department","name":"BelongsTo","targetName":"departmentId"},"posts":{"associatedName":"author","associatedType":"Post","name":"HasMany","targetName":null}},"authRules":[],"fields":{"createdAt":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":true,"isRequired":false,"javaClassForValue":"com.amplifyframework.core.model.temporal.Temporal$DateTime","name":"createdAt","targetType":"AWSDateTime"},"department":{"authRules":[],"isArray":false,"isEnum":false,"isModel":true,"isReadOnly":false,"isRequired":false,"javaClassForValue":"com.amplifyframework.datastore.generated.model.Department","name":"department","targetType":"Department"},"id":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":false,"isRequired":true,"javaClassForValue":"java.lang.String","name":"id","targetType":"ID"},"name":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":false,"isRequired":true,"javaClassForValue":"java.lang.String","name":"name","targetType":"String"},"phone":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":false,"isRequired":false,"javaClassForValue":"java.lang.String","name":"phone","targetType":"AWSPhone"},"posts":{"authRules":[],"isArray":true,"isEnum":false,"isModel":false,"isReadOnly":false,"isRequired":false,"javaClassForValue":"java.util.List","name":"posts","targetType":"Post"},"updatedAt":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":true,"isRequired":false,"javaClassForValue":"com.amplifyframework.core.model.temporal.Temporal$DateTime","name":"updatedAt","targetType":"AWSDateTime"}},"indexes":{"byDepartmentId":{"indexFieldNames":["departmentId"],"indexName":"byDepartmentId"}},"modelClass":"com.amplifyframework.datastore.generated.model.Person","name":"Person","pluralName":"People"},"mutatedItem":{"id":"f153f9f6-f62d-4b65-8360-3f693cd1494e","modelSchema":{"associations":{"department":{"associatedName":null,"associatedType":"Department","name":"BelongsTo","targetName":"departmentId"},"posts":{"associatedName":"author","associatedType":"Post","name":"HasMany","targetName":null}},"authRules":[],"fields":{"createdAt":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":true,"isRequired":false,"javaClassForValue":"com.amplifyframework.core.model.temporal.Temporal$DateTime","name":"createdAt","targetType":"AWSDateTime"},"department":{"authRules":[],"isArray":false,"isEnum":false,"isModel":true,"isReadOnly":false,"isRequired":false,"javaClassForValue":"com.amplifyframework.datastore.generated.model.Department","name":"department","targetType":"Department"},"id":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":false,"isRequired":true,"javaClassForValue":"java.lang.String","name":"id","targetType":"ID"},"name":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":false,"isRequired":true,"javaClassForValue":"java.lang.String","name":"name","targetType":"String"},"phone":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":false,"isRequired":false,"javaClassForValue":"java.lang.String","name":"phone","targetType":"AWSPhone"},"posts":{"authRules":[],"isArray":true,"isEnum":false,"isModel":false,"isReadOnly":false,"isRequired":false,"javaClassForValue":"java.util.List","name":"posts","targetType":"Post"},"updatedAt":{"authRules":[],"isArray":false,"isEnum":false,"isModel":false,"isReadOnly":true,"isRequired":false,"javaClassForValue":"com.amplifyframework.core.model.temporal.Temporal$DateTime","name":"updatedAt","targetType":"AWSDateTime"}},"indexes":{"byDepartmentId":{"indexFieldNames":["departmentId"],"indexName":"byDepartmentId"}},"modelClass":"com.amplifyframework.datastore.generated.model.Person","name":"Person","pluralName":"People"},"serializedData":{"name":"Test w I/amplify:aws-datastore: Successfully removed from mutations outboxPendingMutation{mutatedItem=SerializedModel{id='f153f9f6-f62d-4b65-8360-3f693cd1494e', serializedData={name=Test with phone, createdAt=null, id=f153f9f6-f62d-4b65-8360-3f693cd1494e, phone=1110004444, updatedAt=null}, modelName=Person}, mutationType=CREATE, mutationId=d6ccd81c-051a-11ec-b8e4-4785562e7c29, predicate=MatchAllQueryPredicate} ```

amplifyconfiguration.json

No response

GraphQL Schema

```graphql // Put your schema below this line ```

Additional information and screenshots

GraphQL request

{
    "query": "mutation CreatePerson($input: CreatePersonInput!) {\n  createPerson(input: $input) {\n    _deleted\n    _lastChangedAt\n    _version\n    createdAt\n    department {\n      id\n    }\n    id\n    name\n    phone\n    posts {\n      items {\n        id\n      }\n      nextToken\n      startedAt\n    }\n    updatedAt\n  }\n}\n",
    "variables": {
        "input": {
            "name": "Test with phone",
            "id": "f153f9f6-f62d-4b65-8360-3f693cd1494e",
            "phone": "1110004444"
        }
    }
}

GraphQL response

{
    "data": null,
    "errors": [{
        "path": null,
        "locations": [{
            "line": 1,
            "column": 23,
            "sourceName": null
        }],
        "message": "Variable 'phone' has an invalid value. Unable to parse `1110004444` as a valid phone number."
    }]
}
lawmicha commented 3 years ago

what is codegenerated? does the schema contain information that the phone is of type AWSPhone?

HuiSF commented 3 years ago
type Person @model {
  id: ID!
  name: String!
  phone: AWSPhone
}

The phone field type is string in codegen generated models (schema).

AWSPhone is used and validated within AppSync API.

chrisbonifacio commented 2 years ago

Related to https://github.com/aws-amplify/amplify-js/issues/9065

@HuiSF Is the error handler/callback being triggered when you run into this issue?

HuiSF commented 2 years ago

No @chrisbonifacio error handler cannot capture this error due to this issue https://github.com/aws-amplify/amplify-android/issues/1637 .