aws-amplify / amplify-swift

A declarative library for application development using cloud services.
Apache License 2.0
435 stars 192 forks source link

AppSyncRealTimeClient Subscription events failing #3134

Open ChadyG opened 10 months ago

ChadyG commented 10 months ago

Describe the bug

Moving to GraphQL transformer v2, now getting this error when running our app against the newly deployed backend.

GraphQLResponseError<MutationSync<AnyModel>>: GraphQL service returned a successful response containing errors: [Amplify.GraphQLError(message: "subscription filter has invalid filter value for operator `containsAny`.", locations: nil, path: nil, extensions: Optional(["errorCode": Amplify.JSONValue.number(400.0)]))]```

We added group list authorization as well as our owner authorization. The group authorization is meant to be optional, where we enable it as needed. However this seems to be incurring an error from the resolvers. I'm not sure this is exactly where the error is, I'm just getting this error on the swift client and not seeing any errors when enabling logging on the AppSync API. See log output below for (potentially) relevant request mapping log output.

Steps To Reproduce

Steps to reproduce the behavior:
1. Add @auth rule for groups with groupsField
2. Deploy changes to backend
3. Run app client
4. See error

Expected behavior

App sync continues to work after graphQL changes

Amplify Framework Version

1.30.4

Amplify Categories

API

Dependency manager

Cocoapods

Swift version

5.8.1

CLI version

12.2.4

Xcode version

15.0 b2

Relevant log output

<details>
<summary>Log Messages</summary>

{
    "logType": "RequestMapping",
    "fieldName": "onUpdateLmShot",
    "resolverArn": "arn:aws:appsync:us-east-1:XXX:apis/XXX/types/Subscription/resolvers/onUpdateLmShot",
    "functionName": "SubscriptiononCreateLmSessionauth0Function",
    "fieldInError": false,
    "parentType": "Subscription",
    "path": [
        "onUpdateLmShot"
    ],
    "requestId": "455faac6-4829-4e57-9b69-2b1092578056",
    "context": {
        "arguments": {
            "owner": "XXX",
            "filter": {
                "or": [
                    {
                        "owner": {
                            "eq": "XXX"
                        }
                    },
                    {
                        "groups": {
                            "containsAny": []
                        }
                    }
                ]
            }
        },
        "prev": {
            "result": {}
        },
        "stash": {
            "authRole": "arn:aws:sts::XXX:assumed-role/amplify-XXX-prod-XXX-authRole/CognitoIdentityCredentials",
            "conditions": [],
            "connectionAttributes": {},
            "fieldName": "onUpdateLmShot",
            "hasAuth": true,
            "metadata": {
                "dataSourceType": "NONE",
                "apiId": "XXX"
            },
            "typeName": "Subscription",
            "unauthRole": "arn:aws:sts::XXX:assumed-role/amplify-XXX-prod-X-unauthRole/CognitoIdentityCredentials"
        },
        "outErrors": []
    },
    "errors": [],
    "graphQLAPIId": "XXX",
    "functionArn": "arn:aws:appsync:us-east-1:XXX:apis/XXX/functions/XXX",
    "transformedTemplate": "\n                    \n        \n                                  \n          \n        \n                {\"version\":\"2018-05-29\",\"payload\":{}}\n"
}



### Is this a regression?

No

### Regression additional context

_No response_

### Platforms

iOS

### OS Version

iOS 16.6

### Device

iPhone 13 Pro max

### Specific to simulators

_No response_

### Additional context

_No response_
ChadyG commented 10 months ago

Removing the auth rules for group field seems to be a workaround for now.

lawmicha commented 10 months ago

Hey @ChadyG, could you provide us with the schema containing auth rules for groups with groupsField so that we can try to reproduce it?

ChadyG commented 10 months ago

Of course. The LmShot and LmSession (which contains shots) are the models adding the groups field authorization rules.

"""
User information is stored in the database to facilitate offline user data.

NOTE: Must be synced with Authorization remote when possible
"""
type LmUser @model
  @auth(rules: [
    { allow: owner, identityClaim: "username" },
    { allow: groups, groups: ["Admin"] }]) {
  id: ID!
  setupComplete: Boolean!
  email: String
  phone: String
  fullName: String
  profileImage: String
  userType: String
  gender: String
  handedness: LmHandedness
  birthdate: Float
  # Registration Information
  companyName: String
  shippingAddressLine1: String
  shippingAddressLine2: String
  shippingPostcode: String
  shippingLocality: String
  shippingRegion: String
  shippingCountry: String
  subscriptions: String
}

"""
Profile is accessible by searching by userId
"""
type LmProfile @model
  @auth(rules: [
    { allow: owner, identityClaim: "username" },
    { allow: groups, groups: ["Admin"] }]) {
  id: ID!
  userId: ID! @index(name: "byUser")

  setupComplete: Boolean!
  showDevicePlacement: Boolean
  recordClubs: Boolean
  useTileRangeView: Boolean
  videoCapture: Boolean
  chosenPanel: Int

  normalized: Boolean
  normalizedElevation: Int
  normalizedTemperature: Int
  normalizedBallType: LmBallType
  normalizedOutdoorBallType: LmBallType

  distanceUnits: LmDistanceUnits
  speedUnits: LmSpeedUnits
  apexUnits: LmDistanceUnits
  elevationUnits: LmDistanceUnits
  temperatureUnits: LmTemperatureUnits
  deviceDisplayMode: LmDisplayMode
  language: String
  displayMode: LmDisplayMode
  targetOption: LmTargetOption @deprecated(reason: "Not supported.")
  cameraView: LmCameraView @deprecated(reason: "Not supported.")
  ballPath: LmBallPath @deprecated(reason: "Not supported.")
  theme: LmTheme

  appTiles: [ValueType]
  watchTiles: [ValueType]
  deviceTiles: [ValueType]
  appTilesEnabled: [Boolean]
  watchTilesEnabled: [Boolean]

  clubSpeedAudio: Boolean
  ballSpeedAudio: Boolean
  smashFactorAudio: Boolean
  attackAngleAudio: Boolean
  clubPathAudio: Boolean
  launchAngleAudio: Boolean
  horizontalLaunchAngleAudio: Boolean
  faceAngleAudio: Boolean
  spinRateAudio: Boolean
  spinAxisAudio: Boolean
  carryDistanceAudio: Boolean
  totalDistanceAudio: Boolean
  sideAudio: Boolean
  sideTotalAudio: Boolean
  apexAudio: Boolean

  devices: [LmDevice] @hasMany(indexName: "byProfile", fields: ["id"])
  sessions: [LmSession] @hasMany(indexName: "byProfile", fields: ["id"])
  clubs: [LmClub] @hasMany(indexName: "byProfile", fields: ["id"])
  user: LmUser @hasOne(fields: ["userId"])
}

enum LmDistanceUnits {
  Feet
  Yards
  Meters
}

enum LmSpeedUnits {
  Mph
  Kph
  Mps
}

enum LmTemperatureUnits {
  Fahrenheit
  Celcius
}

enum LmDisplayMode {
  Graphic
  Tiles
  MultiData
  SingleData
}

enum LmTheme {
  Light
  Dark
}

enum LmTargetOption {
  None
  Basket
  Green
  Circle
}

enum LmCameraView {
  Stationary
  Ball
  Flight
}

enum LmBallPath {
  Simulated
  Raw
}

enum LmBallType {
  Premium
  TitleistRct
}

enum LmHandedness {
  Left
  Right
}

type LmDevice @model
  @auth(rules: [
    { allow: owner, identityClaim: "username" },
    { allow: groups, groups: ["Admin"] }]) {
  id: ID!
  lmProfileId: ID! 
    @index(name: "byProfile", queryField: "devicesByProfile")
  owner: String 
    @index(name: "byUser", queryField: "devicesByUser")

  deviceID: String
  advertisementName: String
  connectID: String @deprecated(reason: "Not supported.")
  configuredWifiSSID: String
  serialNumber: String
  modelNumber: String
  firmwareVersion: String
  autoConnect: Boolean @deprecated(reason: "Not supported.")
  registered: Boolean
  registeredUser: String
  registrationDate: Int
  registrationReminder: Boolean
  registrationReminderTime: Int
}

"""
Sessions for a user are found by searching for sessions for the userId
"""
type LmSession @model
  @auth(rules: [
    { allow: owner, identityClaim: "username" },
    { allow: groups, groupsField: "groups" },
    { allow: groups, groups: ["Admin"] }]) {
  id: ID!
  lmProfileId: ID!
    @index(name: "byProfile", queryField: "sessionsByProfile")
  owner: String @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }]) 
    @index(name: "byUser", queryField: "sessionsByUser") 
    @index(name: "byUserInRange", sortKeyFields: ["startTimestamp"], queryField: "sessionsByUserInRange")
  deviceID: String @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])
  groups: [String]

  startTimestamp: Int!
  endTimestamp: Int!
  duration: Int

  name: String @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])
  city: String @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])
  state: String @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])
  country: String @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])
  address: String @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])
  courseName: String @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])
  elevation: Float
  latitude: Float @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])
  longitude: Float @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])
  temperature: Float
  humidity: Float
  location: LmLocation
  normalizedElevation: Float
  normalizedTemperature: Float
  normalizedBallType: LmBallType

  shots: [LmShot] @hasMany(indexName: "bySession", fields: ["id"])
  profile: LmProfile @belongsTo(fields: ["lmProfileId"]) @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])
}

type LmShareSession @model
  @auth(rules: [
    { allow: owner, identityClaim: "username" }, 
    { allow: private, operations: [get] },
    { allow: groups, groups: ["Admin"] }]) {
  shareUrl: ID! @primaryKey
  lmSessionId: ID! 
    @index(name: "bySession", queryField: "shareBySession")
  owner: String @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }]) 
    @index(name: "byUser", queryField: "shareByUser") 

  impactUrl: String
  videoUrl: String

  sessionName: String
  sessionAddress: String

  session: LmSession @hasOne(fields: ["lmSessionId"])
}

enum LmLocation {
  Net
  Screen
  Simulator
  OutdoorRange
  IndoorRange
  Course
}

"""
If a value is missing from the shot model it means it could not be calculated
and should not be considered as valid data

Additional notes on fields:
  impactKey - Key to retrieve pre-impact photo
  videoKey - Key to retreive shot video
  pointCloudKey - Key to retreive point cloud file
"""
type LmShot @model
  @auth(rules: [
    { allow: owner, identityClaim: "username" },
    { allow: groups, groupsField: "groups" },
    { allow: groups, groups: ["Admin"] }]) {
  id: ID! 
  lmSessionId: ID! 
    @index(name: "bySession", queryField: "shotsBySession") 
    @index(name: "bySessionInRange", sortKeyFields: ["timestamp"], queryField: "shotsBySessionInRange") 
  clubId: ID! 
    @index(name: "byClub", queryField: "shotsByClub") 
    @index(name: "byClubInRange", sortKeyFields: ["timestamp"], queryField: "shotsByClubInRange") 
  owner: String @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }]) 
    @index(name: "byUser", queryField: "shotsByUser")
    @index(name: "byUserInRange", sortKeyFields: ["timestamp"], queryField: "shotsByUserInRange") 
  groups: [String]

  clubCategory: String 
    @index(name: "byClubCategoryInRange", sortKeyFields: ["timestamp"], queryField: "shotsByClubCategoryInRange") 
  pointId: String @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])

  timestamp: Int!
  isFavorite: Boolean

  clubSpeed: Float
  ballSpeed: Float
  smashFactor: Float
  attackAngle: Float
  clubPath: Float
  launchAngle: Float
  horizontalLaunchAngle: Float
  faceAngle: Float
  spinRate: Int
  spinAxis: Float
  carryDistance: Int 
  totalDistance: Int
  side: Int
  sideTotal: Int
  apex: Float
  ballDirection: Float
  ballCurve: Float

  impactKey: String @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])
  videoKey: String @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])
  pointCloudKey: String @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])
  protobufKey: String @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])

  clubSpeedValid: Boolean
  ballSpeedValid: Boolean
  smashFactorValid: Boolean
  attackAngleValid: Boolean
  clubPathValid: Boolean
  launchAngleValid: Boolean
  horizontalLaunchAngleValid: Boolean
  faceAngleValid: Boolean
  spinRateValid: Boolean
  spinAxisValid: Boolean
  carryDistanceValid: Boolean
  totalDistanceValid: Boolean
  sideValid: Boolean
  sideTotalValid: Boolean
  apexValid: Boolean

  xFit: [Float]
  yFit: [Float]
  zFit: [Float]
  club: LmClub @hasOne(fields: ["clubId"]) 
  normalizedValues: LmShotNormalizedValues
}

type LmShotNormalizedValues {
  carryDistance: Int
  totalDistance: Int
  side: Int
  sideTotal: Int
  apex: Float
}

enum LmClubType {
  Unknown
  Driver
  Wood2
  Wood3
  Wood4
  Wood5
  Wood6
  Wood7
  Wood8
  Wood9
  Iron1
  Iron2
  Iron3
  Iron4
  Iron5
  Iron6
  Iron7
  Iron8
  Iron9
  PitchingWedge
  ApproachWedge
  GapWedge
  SandWedge
  LobWedge
  Wedge46
  Wedge48
  Wedge50
  Wedge52
  Wedge54
  Wedge56
  Wedge58
  Wedge60
  Wedge62
  Wedge64
  Putter
  Hybrid
  Hybrid1
  Hybrid2
  Hybrid3
  Hybrid4
  Hybrid5
  Hybrid6
  Hybrid7
  Hybrid8
  Hybrid9
  Other
}

type LmClub @model
  @auth(rules: [
    { allow: owner, identityClaim: "username" },
    { allow: groups, groups: ["Admin"] }]) {
  id: ID! 
  lmProfileId: ID! 
    @index(name: "byProfile", queryField: "clubsByProfile") 

  owner: String @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }]) 
    @index(name: "byUser", queryField: "clubsByOwner") 

  type: LmClubType
  name: String
  brand: String
  model: String
  shaft: String
  listOrder: Int @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])
  isActive: Boolean @auth(rules: [{ allow: owner, identityClaim: "username" }, { allow: groups, groups: ["Admin"] }])
}

type ShotValues {
  clubId: ID!
  clubSpeed: [Float]
  ballSpeed: [Float]
  smashFactor: [Float]
  attackAngle: [Float]
  clubPath: [Float]
  launchAngle: [Float]
  horizontalLaunchAngle: [Float]
  faceAngle: [Float]
  spinRate: [Int]
  spinAxis: [Float]
  carryDistance: [Int]
  totalDistance: [Int]
  side: [Int]
  sideTotal: [Int]
  apex: [Float]
  ballDirection: [Float]
  ballCurve: [Float]

  normalizedCarryDistance: [Int]
  normalizedTotalDistance: [Int]
  normalizedSide: [Int]
  normalizedSideTotal: [Int]
  normalizedApex: [Float]
}

type ShotAverage {
  clubId: ID!
  clubSpeed: Float
  ballSpeed: Float
  smashFactor: Float
  attackAngle: Float
  clubPath: Float
  launchAngle: Float
  horizontalLaunchAngle: Float
  faceAngle: Float
  spinRate: Float
  spinAxis: Float
  carryDistance: Float
  totalDistance: Float
  side: Float
  sideTotal: Float
  apex: Float
  ballDirection: Float
  ballCurve: Float

  normalizedCarryDistance: Float
  normalizedTotalDistance: Float
  normalizedSide: Float
  normalizedSideTotal: Float
  normalizedApex: Float
}

type LmSessionStats @model
  @auth(rules: [
    { allow: owner, identityClaim: "username" },
    { allow: groups, groups: ["Admin"] }]) {
  id: ID!
  lmSessionId: ID! @index(name: "bySession", queryField: "statsBySession") 
  owner: String @index(name: "byUser", queryField: "sessionStatsByOwner") 

  shotCount: Int
  values: [ShotValues]
  averages: [ShotAverage]
}

type LmUserStats @model
  @auth(rules: [
    { allow: private, provider: iam },
    { allow: owner, identityClaim: "username" },
    { allow: groups, groups: ["Admin"] }]) {
  id: ID!
  lmProfileId: ID! @index(name: "byProfile", queryField: "statsByProfile") 
  owner: String @index(name: "byUser", queryField: "statsByOwner") 

  sessionCount: Int
  shotCount: Int
  clubCount: Int
}

type RangeCount {
  range: Int!
  count: Int!
}

type TimeWindowCount {
  start: Int!
  end: Int!
  count: Int!
}

type ClubCount {
  clubId: ID!
  count: Int!
}

type ClubAverage {
  clubId: ID!
  average: Float!
}

enum DateResolution {
  Day
  Week
  Month
}

enum ValueType {
  ClubSpeed
  BallSpeed
  SmashFactor
  AttackAngle
  ClubPath
  LaunchAngle
  HorizontalLaunchAngle
  FaceAngle
  FaceToPath
  SpinRate
  SpinAxis
  CarryDistance
  TotalDistance
  Side
  SideTotal
  Apex
  BallDirection
  BallCurve
}

type RegisterShadow {
  regUser: String!
  prodRegDate: String!
}

type GraphQLResult {
    status: String!
}

type Query {
  accuracy(owner: String, clubCategory: String, start: Int, end: Int): [RangeCount] @function(name: "shotsbycategory-${env}")
  countByClub(owner: String, start: Int, end: Int): [ClubCount] @function(name: "shotsbyclub-${env}")
  sessionCount(owner: String, resolution: DateResolution, start: Int, end: Int): [TimeWindowCount] @function(name: "sessionsinrange-${env}")
  shotCount(owner: String, resolution: DateResolution, start: Int, end: Int): [TimeWindowCount] @function(name: "shotsinrange-${env}")
  averageByClub(owner: String, value: ValueType, start: Int, end: Int): [ClubAverage] @function(name: "clubaverages-${env}")
}
harsh62 commented 9 months ago

@ChadyG

Thanks for providing the information. Our team is actively prioritizing and working on issues. We will provide an update as soon as we have one on the issue.

ChadyG commented 9 months ago

Thanks @harsh62! FYI this isn't currently a blocker for us. We transitioned away from this strategy for our current feature development. But we will likely revisit it in the future.

lawmicha commented 7 months ago

Hi @ChadyG, thanks for the schema. From the error message,

GraphQLResponseError<MutationSync<AnyModel>>: GraphQL service returned a successful response containing errors: [Amplify.GraphQLError(message: "subscription filter has invalid filter value for operator `containsAny`.", locations: nil, path: nil, extensions: Optional(["errorCode": Amplify.JSONValue.number(400.0)]))]```

are you by any chance passing in a syncExpression? subscription filters are applied from translating the sync expression. If there is, there could be a problem with the translation from syncExpression to GraphQL filter variable. If there isn't then we will have to pass this forward to https://github.com/aws-amplify/amplify-category-api. Our next steps here from the library perspective is to try out your schema and apply the same sync expression if there is.

If it is reproducible, we would then replay the request directly in the AppSync console against the LmShot model.

ChadyG commented 7 months ago

Hello @lawmicha, Yes, we sync only user owned data. So our sync expressions look like this in Swift:

    func syncExpressions() -> [DataStoreSyncExpression] {
        func userId() -> String {
            Amplify.Auth.getCurrentUser()?.userId ?? "dont-sync"
        }

        return [
            DataStoreSyncExpression.syncExpression(LmClub.schema, where: {
                return LmClub.keys.owner.eq(userId())
            }),
            DataStoreSyncExpression.syncExpression(LmDevice.schema, where: {
                return LmDevice.keys.owner.eq(userId())
            }),
            DataStoreSyncExpression.syncExpression(LmProfile.schema, where: {
                return LmProfile.keys.userId.eq(userId())
            }),
            DataStoreSyncExpression.syncExpression(LmSession.schema, where: {
                return LmSession.keys.owner.eq(userId())
            }),
            DataStoreSyncExpression.syncExpression(LmSessionStats.schema, where: {
                return LmSessionStats.keys.owner.eq(userId())
            }),
            DataStoreSyncExpression.syncExpression(LmUserStats.schema, where: {
                return LmUserStats.keys.owner.eq(userId())
            }),
            DataStoreSyncExpression.syncExpression(LmUser.schema, where: {
                return LmUser.keys.id.eq(userId())
            }),
            DataStoreSyncExpression.syncExpression(LmShot.schema, where: {
                return LmShot.keys.owner.eq(userId())
            })
        ]
    }
harsh62 commented 7 months ago

Our team is still investigating the issue. We will provide an update as soon as we have more information.

Krishna-Dockare commented 3 months ago

@harsh62 , I am also getting the issue like this: ConnectionProviderError.jsonParse; identifier=7B3A7667-A421-49E2-ACC4-D48682793036; additionalInfo=Optional(["errors": AppSyncRealTimeClient.AppSyncJSONValue.array([AppSyncRealTimeClient.AppSyncJSONValue.object(["message": AppSyncRealTimeClient.AppSyncJSONValue.string("subscription exceeds maximum value limit 20 for operatorcontainsAny."), "errorCode": AppSyncRealTimeClient.AppSyncJSONValue.number(400.0)])])])

My use case is a user will be a part of multiple channels, The amplify observe query is working fine when the user is part of small number of channels. If the number of channels increases the above error is coming.

harsh62 commented 3 months ago

@Krishna-Dockare would you be able to share your schema? Also if you can help me figure out what is approximately a large number of channels?

Krishna-Dockare commented 3 months ago

@harsh62

Here is the Object from schema that we are listening to:

type MessageRecipient @model  @auth(rules: [
    {allow: groups, groupsField: "permittedTo"}
]) {
    id: ID!
    messageId: ID! @index(name:"byMessage")
    groupId: String! @index(name: "byGroup")
    userId: Int! @index(name:"byUser")
    status: MessageStatus!
    sentAt: AWSDateTime!
    readAt: AWSDateTime
    permittedTo: [String!]
}

Here is how we are observing the object

let predicate = MessageRecipient.keys.userId == userId

        let subscription = Amplify.DataStore.observeQuery(
            for: MessageRecipient.self,
            where:predicate,
            sort: .descending(MessageRecipient.keys.sentAt)
        )

The user can be in n number of channels. For a new user the error is not coming but when the user is logged in multiple times in multiple devices.

thisisabhash commented 3 months ago

Thank you - We will investigate and provide an update on this ticket.

Krishna-Dockare commented 3 months ago

@thisisabhash , I have removed the group fields from auth rules from the above Object that I have mentioned. But when calling the query for the first time, it is not returning all the results. It is taking sometime to load all the results. How can I handle this?

harsh62 commented 3 months ago

@Krishna-Dockare Would you be able to share how much time it is taking to load all the results? Our team will try to repro the slowness and reply to the issue as soon as we can.

Krishna-Dockare commented 3 months ago

@harsh62 , It is taking around 10 seconds to fetch all the results after sign in to the Amplify.

paulnolan7 commented 1 month ago

I get this issue as well. It appears to be when the user is not a member of any cognito groups. The resolver does n't handle that case and created a filter condition that doesn't make sense. A workaround is add all users to a default group but that is not feasible long term as the number of users grow.

lawmicha commented 1 month ago

The two errors have different messages and come from different APIs. The first one

GraphQLResponseError<MutationSync<AnyModel>>: GraphQL service returned a successful response containing errors: [Amplify.GraphQLError(message: "subscription filter has invalid filter value for operator `containsAny`.", locations: nil, path: nil, extensions: Optional(["errorCode": Amplify.JSONValue.number(400.0)]))]```

this should be the request-response of performing the SyncQuery call. We have the above schema and sync expression. The sync expressions is translated to the filter input of the sync query. We should be able to take the schema and provision the backend, and start DataStore with the syncExpression to see if we can observe the error.

The second error from @Krishna-Dockare

ConnectionProviderError.jsonParse; identifier=7B3A7667-A421-49E2-ACC4-D48682793036; additionalInfo=Optional(["errors": AppSyncRealTimeClient.AppSyncJSONValue.array([AppSyncRealTimeClient.AppSyncJSONValue.object(["message": AppSyncRealTimeClient.AppSyncJSONValue.string("subscription exceeds maximum value limit 20 for operator containsAny."), "errorCode": AppSyncRealTimeClient.AppSyncJSONValue.number(400.0)])])])

@Krishna-Dockare, this appears to come from the subscription API call. ObserveQuery is an API against the local database, which will kick off DataStore.start() if it hasn't started already. Starting DataStore will establish the subscriptions, which may be the shorter path to reproduce this. Can you open a new issue and provide us the full schema, and steps to reproduce? I want to keep this issue isolated from the issue @ChadyG has described.

@paulnolan7 Can you also open a new issue with the schema example and repro steps such as the API calls you are making?

lawmicha commented 1 month ago

The first error is infact from the subscription request.

I was able to replay the request in the AppSync console to produce the error, even without any filters (would have been the syncExpression at the DataStore client).

For the schema in https://github.com/aws-amplify/amplify-swift/issues/3134#issuecomment-1679194276. The LmShot model subscription request:


subscription OnDeleteLmShot($owner: String!) {
  onDeleteLmShot(owner: $owner) {
    id
    apex
    apexValid
    attackAngle
    attackAngleValid
    ballCurve
    ballDirection
    ballSpeed
    ballSpeedValid
    carryDistance
    carryDistanceValid
    clubCategory
    clubId
    clubPath
    clubPathValid
    clubSpeed
    clubSpeedValid
    createdAt
    faceAngle
    faceAngleValid
    groups
    horizontalLaunchAngle
    horizontalLaunchAngleValid
    impactKey
    isFavorite
    launchAngle
    launchAngleValid
    lmSessionId
    normalizedValues {
      apex
      carryDistance
      side
      sideTotal
      totalDistance
      __typename
    }
    owner
    pointCloudKey
    pointId
    protobufKey
    side
    sideTotal
    sideTotalValid
    sideValid
    smashFactor
    smashFactorValid
    spinAxis
    spinAxisValid
    spinRate
    spinRateValid
    timestamp
    totalDistance
    totalDistanceValid
    updatedAt
    videoKey
    xFit
    yFit
    zFit
    club {
      id
      brand
      createdAt
      isActive
      listOrder
      lmProfileId
      model
      name
      owner
      shaft
      type
      updatedAt
      __typename
      _version
      _deleted
      _lastChangedAt
    }
    __typename
    _version
    _deleted
    _lastChangedAt
  }
}

variables contains the username that I signed in with.

{
  "owner": "mchael"
}

Response:

{
    "errors": [
        {
            "message": "Connection failed: {\"errors\":[{\"message\":\"subscription filter has invalid filter value for operator `containsAny`.\",\"errorCode\":400}]}"
        }
    ]
}

AppSync was provisioned with Amplify CLI version 12.10.3, transformer v2, DataStore enabled.

lawmicha commented 1 month ago

@ChadyG I've created the issue referencing your schema in https://github.com/aws-amplify/amplify-category-api/issues/2547 please update that issue if there is any new information you'd like to provide.

@Krishna-Dockare / @paulnolan7 The errors you're seeing do seem related. Steps to reproduce will be

  1. Have verbose logging enabled, Amplify.Logging.logLevel = .verbose, before Amplify.configure()
  2. Kick off DataStore.start() and isolate the model that is failing to subscribe. You can comment out the registration of certain models in AmplifyModels.swift. Once you have the models that are failing to subscribe, you can check the logs to extract out the GraphQL request.
  3. If you are able to replay the request in AppSync directly like I have above and produce the same error, then the issue is out of scope of the library client. Please create an issue over in that repo with your schema and request/response