aws-amplify / amplify-flutter

A declarative library with an easy-to-use interface for building Flutter applications on AWS.
https://docs.amplify.aws
Apache License 2.0
1.29k stars 239 forks source link

Model parsing fails for complex non-model types #4872

Closed ww-daniel-mora closed 1 day ago

ww-daniel-mora commented 4 weeks ago

Description

With amplify cli version 12.12.0 model parsing does not populate the value in any complex non-model types. The properties of any complex non-model type will be null.

Categories

Steps to Reproduce

Given a schema that contains a complex non-model type

type MyModel @model @auth(rules: [{allow: owner}]) {
  id: ID!
  owner: String
  token: MyToken
}

type Token {
  externalId: String!
  value: String!
}

When calling AppSync and setting the model type:

const decodePath = 'getMyModel';
final graphQLDocument = '''
query GetMyModel(\$id: ID!) {
  $decodePath(id: \$id) {
    id
    owner
    token {
      externalId
      value
    }
  }
}
''';
final result = await Amplify.API.query(
      request: GraphQLRequest<MyModel>(
      modelType: const MyModel.classType,
      document: graphQLDocument,
      decodePath: decodePath,

).response;

Complex non model types (e.g. Token) will not have their properties populated, i.e result.data.token.value == null would be true in the above example.

This break was introduces with CLI version 12.12.0 and worked prior to this version.

Screenshots

No response

Platforms

Flutter Version

3.19.6

Amplify Flutter Version

1.7.0

Deployment Method

Amplify CLI

Schema

type Wallet @model @auth(rules: [{ allow: owner }]) {
  id: ID!
  owner: String @auth(rules: [{ allow: owner, operations: [create, read, delete] }]) @index
  profiles: [Profile]! @hasMany
  activeProfile: Profile @hasOne
  financialInstitutions: [FinancialInstitution] @hasMany
  xpub: String!
  baseDerivationPath: String! # should be constent across wallets but here for the sake of consistency
  nextChildIndex: Int!
}

type UserData @model @auth(rules: [{ allow: owner }]) {
  id: ID!
  owner: String @auth(rules: [{ allow: owner, operations: [create, read, delete] }]) @index
  plainTextData: PlainTextData
  securedData: String!
  securedDocuments: [SecuredDocument] @hasMany
  xpub: String!
  baseDerivationPath: String! # The base derivation path for user documents under this account
  nextChildIndex: Int!
}

type FinancialInstitutionData @model @auth(rules: [{allow: private, provider: userPools}, {allow: private, provider: iam}]) {
  id: ID!
  logo: String
  name: String! @index
  externalId: String
  searchId: String! @index
}

type FinancialInstitution @model @auth(rules: [{allow: owner}, {allow: private, provider: iam}]) {
  id: ID!
  owner: String @auth(rules: [{ allow: owner, operations: [create, read, delete] }]) @index(sortKeyFields: ["searchId"])
  name: String!
  accounts: [Account] @hasMany
  accessTokens: [AccessToken] @hasMany
  wallet: Wallet! @belongsTo
  financialInstitutionData: FinancialInstitutionData! @hasOne
  externalId: String
  searchId: String!
  products: [String]
  xpub: String!
  baseDerivationPath: String!
  nextChildIndex: Int!
  createdAt: AWSDateTime!
  updatedAt: AWSDateTime!
}

type Account @model @auth(rules: [{ allow: owner }, {allow: private, provider: iam}]) {
  id: ID!
  owner: String @auth(rules: [{ allow: owner, operations: [create, read, delete] }])
  nativeId: String
  name: String!
  mask: String
  facts: [AccountFact] @hasMany
  secretFacts: String
  pendingFacts: String
  type: String!
  documents: [FinancialDocument] @hasMany
  financialInstitution: FinancialInstitution! @belongsTo
  xpub: String!
  baseDerivationPath: String! # The base derivation path for documents under this account
  nextChildIndex: Int!
  updatedAt: AWSDateTime!
}

type SecuredDocument @model @auth(rules: [{ allow: owner }, {allow: private, provider: iam}]) {
  id: ID!
  owner: String @auth(rules: [{ allow: owner, operations: [create, read, delete] }]) @index(sortKeyFields: ["createdAt"])
  derivationPath: String!
  effectiveDate: AWSDate!
  name: String
  s3Key: String!
  sha256: String!
  size: Int!
  type: DocumentType!
  createdAt: AWSDateTime!
}

type FinancialDocument @model @auth(rules: [{ allow: owner }, {allow: private, provider: iam}]) {
  id: ID!
  owner: String @auth(rules: [{ allow: owner, operations: [create, read, delete] }])
  externalId: String
  name: String
  sha256: String!
  securedDocument: SecuredDocument! @hasOne
  account: Account! @belongsTo
}

type AccessToken @model @auth(rules: [{ allow: owner }, {allow: private, provider: iam}]) {
  id: ID!
  owner: String @auth(rules: [{ allow: owner, operations: [create, read, delete] }]) @index
  externalId: String! @index
  type: TokenType!
  flinksToken: FlinksToken
  plaidToken: PlaidToken
  financialInstitutionAccessTokensId: ID! @index(name: "accessTokenByDate", queryField: "accessTokenByDate", sortKeyFields: ["createdAt"])
  financialInstitution: FinancialInstitution! @belongsTo
  createdAt: AWSDateTime!
}

type Profile @model @auth(rules: [{ allow: owner }]) {
  id: ID!
  owner: String @auth(rules: [{ allow: owner, operations: [read, delete] }])
  name: String!
  wallet: Wallet! @belongsTo
  sharePackageReports: [SharePackageReport] @hasMany
}

type ProfileLink @model @auth(rules: [{ allow: owner }, {allow: private, provider: iam}]) {
  id: ID!
  owner: String @auth(rules: [{ allow: owner, operations: [read, delete] }])
  financialInstitution: FinancialInstitution! @hasOne
  profile: Profile! @hasOne
  profileLinkFinancialInstitutionId: ID!
  profileLinkProfileId: ID! @index(name: "byProfileAndFI", sortKeyFields: ["profileLinkFinancialInstitutionId"], queryField: "profileLinksByProfileAndFI")
}

type AccountFact @model @auth(rules: [{ allow: owner }, { allow: private, provider: iam }]) {
  id: ID!
  owner: String @auth(rules: [{ allow: owner, operations: [read, delete] }])
  key: String!
  value: String!
  account: Account! @belongsTo
}

type AssetReportJob @model @auth(rules: [{ allow: owner }, { allow: private, provider: iam }]) {
  id: ID!
  owner: String @auth(rules: [{ allow: owner, operations: [read, delete] }])
  status: JobStatus!
  externalId: String! @index
  message: String
  token: String
  s3Bucket: String!
  createdAt: AWSDateTime!
  updatedAt: AWSDateTime!
  lastReportId: String
  financialInstitution: FinancialInstitution! @hasOne
  assetReportJobFinancialInstitutionId: ID! @index(name: "assetReportJobByFI", queryField: "assetReportJobByFI", sortKeyFields: ["updatedAt"])
}

type SharePackageReport @model @auth(rules: [{ allow: owner }, { allow: private, provider: iam }]) {
  id: ID!
  owner: String @auth(rules: [{ allow: owner, operations: [read, delete] }])
  profile: Profile! @belongsTo
  generalInfo: SharePackageGeneralInfo!
  identityData: SharePackageIdentityData
  createdAt: AWSDateTime!
  updatedAt: AWSDateTime!
  profileSharePackageReportsId: ID! @index(name: "byProfile", sortKeyFields: ["createdAt"], queryField: "sharePackageReportsByProfile")
}

type ShareReportDocumentLink @model @auth(rules: [{ allow: owner }, {allow: private, provider: iam}]) {
  id: ID!
  owner: String @auth(rules: [{ allow: owner, operations: [read, delete] }])
  financialDocument: FinancialDocument! @hasOne
  report: SharePackageReport! @hasOne
  shareReportDocumentLinkFinancialDocumentId: ID!
  shareReportDocumentLinkReportId: ID! @index(name: "shareReportDocumentLinksByReportAndDocument", sortKeyFields: ["shareReportDocumentLinkFinancialDocumentId"], queryField: "shareReportDocumentLinksByReportAndDocument")
}

type SharePackageGeneralInfo {
  packageName: String!
  recipient: String! # API provider or recipient's email address domain
  securedRecipientEmail: String # The secured recipient email if shared via email
  selectedCategories: [SharePackageCategory]
}

type SharePackageIdentityData {
  securedData: String!
  securedDocumentsIds: [String]
}

enum SharePackageCategory {
  identity
  income
  bank
  investment
  tax
  creditAndLoan
}

enum JobStatus {
  pending         # not ready for processing
  ready           # ready for processing
  processing      # processing
  completed       # completed successfully
  failed          # failed
}

enum ItemStatus {
  OK 
  ACCESS_NOT_GRANTED
  INSTANT_MATCH_FAILED
  INSUFFICIENT_CREDENTIALS
  INVALID_CREDENTIALS
  INVALID_MFA
  INVALID_OTP
  INVALID_PHONE_NUMBER
  INVALID_SEND_METHOD
  INVALID_UPDATED_USERNAME
  ITEM_CONCURRENTLY_DELETED
  ITEM_LOCKED
  ITEM_LOGIN_REQUIRED
  ITEM_NOT_FOUND
  ITEM_NOT_SUPPORTED
  MFA_NOT_SUPPORTED
  NO_ACCOUNTS
  NO_AUTH_ACCOUNTS
  NO_INVESTMENT_ACCOUNTS
  NO_INVESTMENT_AUTH_ACCOUNTS
  NO_LIABILITY_ACCOUNTS
  PRODUCT_NOT_ENABLED
  PRODUCT_NOT_READY
  PRODUCTS_NOT_SUPPORTED
  USER_INPUT_TIMEOUT
  USER_SETUP_REQUIRED
}

enum TokenType {
  plaid
  flinks
}

enum DocumentType {
  financialStatement
  payStub
  identity
  tax
  form1099
  w2
  other
}

type PlaidToken {
  accessToken: String!
  itemId: String!
  status: ItemStatus
  institutionId: String
  institutionName: String
}

type FlinksToken {
  loginId: String!
  institution: String!
}

type PlainTextData {
  incomeUser: IncomeUser!
  firstName: String
  financialGoals: [FinancialGoal]
}

enum FinancialGoal {
  realEstate
  rentingAHome
  wealthPlanning
}

type IncomeUser {
  id: String!
  token: String!
}

type UpdateStatementsResponse {
  statusCode: Int!
  updateResult: UpdateResult!
}

type ExchangeTokenResponse {
  statusCode: Int!
  accessTokenId: String!
  fiExternalId: String!
  fiId: String!
}

type GetExternalInstitutionIdResponse {
  statusCode: Int!
  institutionId: String!
}

type CreateIncomeUserResponse {
  id: String!
  token: String!
}

input UpdateStatementsInput {
  accessTokenId: String!
  s3Bucket: String!
  fiId: String!
}

input ExchangeTokenInput {
  institutionId: String
  institutionName: String
  nextFIIndex: Int!
  nextFIXpub: String!
  profileId: String!
  publicToken: String!
  walletId: String!
}

input NewLinkTokenInput {
  products: [String]
  optionalProducts: [String]
  incomeUserToken: String
  accessTokenId: String
  packageName: String!
}

input CreateCustomFIInput {
  accountSubtype: String!
  accountType: String!
  institutionName: String!
  isTax: Boolean! 
  logo: String
  nextFIIndex: Int!
  nextFIXpub: String!
  products: [String]!
  profileId: String!
  walletId: String!
}

type CreateCustomFIResponse {
  statusCode: Int!
  financialInstitution: FinancialInstitution!
}

input GetPayrollDataInput {
  incomeUserToken: String!
  s3Bucket: String!
}

input NewAssetReportJobsInput {
  fiIds: [String]!
  s3Bucket: String!
}

type NewAssetReportJobsResponse {
  jobs: [AssetReportJob]!
  updateResults: [UpdateResult]!
}

type GetPayrollDataResponse {
  statusCode: Int!
  accounts: Int!
  documents: Int!
}

input RefreshDataInput {
  profileId: String
  fiId: String
}

type UpdateResult {
  financialInstitutionId: String!
  accountsUpdated: Int!
  documentsUpdated: Int!
  accessTokenId: String
  accessTokenStatus: ItemStatus
}

type RefreshDataResponse {
  statusCode: Int!
  updateResults: [UpdateResult]!
}

input GenerateUserHmacInput {
  userId: String!
}

type GenerateUserHmacResponse {
  hmacWeb: String!
  hmacAndroid: String!
  hmacIos: String!
}

type Mutation {
  newLinkToken(input: NewLinkTokenInput!): String! @function(name: "newLinkToken-${env}")
  exchangeToken(input: ExchangeTokenInput!): ExchangeTokenResponse! @function(name: "exchangeToken-${env}")
  updateStatements(input: UpdateStatementsInput!): UpdateStatementsResponse! @function(name: "updateStatements-${env}")
  createIncomeUser: CreateIncomeUserResponse! @function(name: "createIncomeUser-${env}")
  createCustomFI(input: CreateCustomFIInput!): CreateCustomFIResponse! @function(name: "createCustomFI-${env}")
  getPayrollData(input: GetPayrollDataInput!): GetPayrollDataResponse! @function(name: "getPayrollData-${env}")
  newAssetReportJobs(input: NewAssetReportJobsInput!): NewAssetReportJobsResponse! @function(name: "newAssetReportJobs-${env}")
  refreshData(input: RefreshDataInput!): RefreshDataResponse! @function(name: "refreshData-${env}")
  generateUserHmac(input: GenerateUserHmacInput!): GenerateUserHmacResponse! @function(name: "generateUserHmac-${env}")
}
khatruong2009 commented 4 weeks ago

Hi @ww-daniel-mora, we are looking into the issue now and will get back to you when we hvae an update.

Equartey commented 3 weeks ago

Hi @ww-daniel-mora, sorry to hear you ran into this issue. Thank you for the detailed reproduction steps.

I've opened a PR to address this on the CLI side.

Also note this workflow is already working in Amplify Flutter 2.0, however we are still working on the full migration guide from 1.X. Stay on the lookout for updates on when it's available.

In the meantime, you can downgrade your CLI version to unblock any issue. npm install -g @aws-amplify/cli@12.11.1

I will update you when the fix becomes available.

Equartey commented 1 day ago

Hi @ww-daniel-mora, thank you for your patience.

A fix for this issue in Amplify Flutter V1 has been released in Amplify CLI 12.12.2. Please update your CLI and regenerate models to consume the fix.

I'm going to close this as resolved. If you have any additional issues, don't hesitate to reach out.