AppSync appears to be loading the incorrect resolver template for some fields of nested objects. Also, it appears to only happen when the nested object has a field with the same name as a field on the parent object.
I've included an example below because I think that might be the best way to explain the issue. As you can see, the id fields for the nested objects are not being resolved correctly.
Each type, Task, User, List, and Tag, have a resolver for their id field because the data for each has a prefix on the id field. For example, Task.id has a resolver that returns $context.source.task_id and User.id has a resolver that returns $context.source.user_id. Same for List and Tag.
What appears to be happening is AppSync is loading the id resolver template for the parent type. You can see that this is the case for task.owner.id, where owner is a User but the id gets resolved as "$context.source.task_id". Same for task.list.id where list is a List. Again we can see this for task.tags[0].owner.id. owner is once again a User except this time the parent is a Tag so task.tags[0].owner.id is resolved as "$context.source.tag_id". These three example show that the problem is not with a particular type since User and List are behaving the same when they are nested in a Task. Also, we can see that the issue is not with Task since User is behaving similarly when nested in a Tag. Lastly, we can see that task.tags[1].owner.id actually behaves correctly. This indicates that the issue only presents itself on first execution.
At this point I strongly suspect this is a bug with AppSync however, I'm not 100% on that. Has anyone else experienced this issue? Am I doing something terribly wrong?
Example
Query
{
task(id: "task-123") {
id,
title,
owner {
id,
username,
},
list {
id,
name,
},
tags {
id,
name,
owner {
id,
username,
},
},
},
}
Result
{
"data": {
"task": {
"id": "task-123",
"title": "First Task",
"owner": {
"id": "$context.source.task_id",
"username": "tom"
},
"list": {
"id": "$context.source.task_id",
"name": "Inbox"
},
"tags": [
{
"id": "tag-123",
"name": "one",
"owner": {
"id": "$context.source.tag_id",
"username": "tom"
}
},
{
"id": "tag-234",
"name": "two",
"owner": {
"id": "user-123",
"username": "tom"
}
}
]
}
}
}
Task Schema
type Task {
id: ID!
title: String!
owner: User!
list: List
tags: [Tag]
}
User Schema
type User {
id: ID!
username: String!
}
List Schema
type List {
id: ID!
name: String!
}
Tag Schema
type Tag {
id: ID!
name: String!
owner: User!
}
Task Data
{
task_id: "task-123",
title: "First Task",
owner_id: "user-123",
list_id: "list-123",
tags: [
"tag-123",
"tag-234"
]
}
User Data
{
user_id: "user-123",
username: "tom"
}
List Data
{
list_id: "list-123",
name: "Inbox"
}
Tag Data
{
tag_id: "tag-123",
name: "one",
owner_id: "user-123"
}
{
tag_id: "tag-234",
name: "two",
owner_id: "user-123"
}
Example id resolver (User)
Request Mapping Template
{
"version": "2017-02-28",
"payload": "$context.source.user_id"
}
Response Mapping Template
$util.toJson($context.result)
The id resolvers for the other types are very similar
It may also be worth noting that I created different None Data Sources for each type, Task, User, List, and Tag. The id resolver for each type is using their respective None Data Source.
Related
Background:
I'm using Amplify to create a type Item #model GraphQL datastore with an #auth allow owner which adds the owner as an CognitoID to the Item.
Question:
This item is readable for guests and I would like to show the owner (or other related metadata such as a username/email) for this item.
e.g. Cognito.GetUserAttributes(2b10fb7e-4472-43c9-9bb9-54219b5027a3)
Is somthing like this possible? How is this supposed to be designed for this use case? Do I have to add a lambda that adds the user meta data to the item when it is created?
I would like this solution to be scalable.
The schema:
type Item
#model
#auth(rules: [
{ allow: owner},
{ allow: groups, groups: ["Admin"] },
{ allow: private, operations: [read] },
{ allow: public, operations: [read] }
])
{
id: ID!
title: String!
description: String
}
From DynamoDB:
{
"__typename": "Item",
"_lastChangedAt": 1593707126605,
"_version": 1,
"createdAt": "2020-07-02T16:25:26.582Z",
"description": "Description",
"id": "0592fcd8-408e-406e-84bf-9f63eb43e147",
"owner": "2b10fb7e-4472-43c9-9bb9-54219b5027a3",
"title": "Item",
"updatedAt": "2020-07-02T16:25:26.582Z"
}
You can use a function resolver on a cognitoAttributes field. The lambda function can call admin-get-user in Cognito and return the attributes. The lambda function will receive the dynamo Item in the event.source property so you will have access to the owner id to make the call to Cognito.
type Item
#model
#auth(rules: [
{ allow: owner},
{ allow: groups, groups: ["Admin"] },
{ allow: private, operations: [read] },
{ allow: public, operations: [read] }
])
{
id: ID!
title: String!
cognitoAttributes: [String]! #function('cognitoGetUserAttributes-${env})
description: String
}
I set up a basic model and controller connected to mysql database. Everything looks good and is working.
My Definition Model looks like this.
import { Entity, model, property } from '#loopback/repository';
#model({
settings: {
mysql: {
table: 'definition'
}
}
})
export class Definition extends Entity {
#property({
type: 'number',
id: true,
generated: true,
forceId: true,
required: false,
description: 'The unique identifier for a definition',
})
id: number;
#property({
type: 'string',
required: true,
})
name: string;
#property({
type: 'string',
required: true,
})
process_id: string;
constructor(data?: Partial<Definition>) {
super(data);
}
}
export interface DefinitionRelations {
// describe navigational properties here
}
export type DefinitionWithRelations = Definition & DefinitionRelations;
When I have it up and running and click on the explorer to test it out.
I click on "Try it out" for get /definitions and the only editable field that gets enabled is the filter field.
It is repopulated with a value like this...
{
"where": {},
"fields": {
"id": true,
"name": true,
"process_id": true
},
"offset": 0,
"limit": 0,
"skip": 0,
"order": [
"string"
]
}
I have to clear that to get it to work which is fine. When I run it with no filter it returns these results.
[
{
"id": 10,
"name": "Place Manual Payoff Order Process Config",
"process_id": "Process_PlaceManualPayoffOrderProcessConfig"
},
{
"id": 11,
"name": "test",
"process_id": "Test"
},
{
"id": 12,
"name": "test2",
"process_id": "test2"
}
]
I am trying to use the filter expression to only return ones with a specific process_id field. So I change the filter to look like this.
{
"where": {"process_id": "test2"}
}
And it still returns the same results.
[
{
"id": 10,
"name": "Place Manual Payoff Order Process Config",
"process_id": "Process_PlaceManualPayoffOrderProcessConfig"
},
{
"id": 11,
"name": "test",
"process_id": "Test"
},
{
"id": 12,
"name": "test2",
"process_id": "test2"
}
]
Do filters currently work in Loopback 4 or am I using them incorrectly?
EDIT: if I post the filters in the URL string they work. It seems as though the openapi ui isn't generating that part of the filter Into the URL string.
It seems as though the openapi ui isn't generating that part of the filter Into the URL string.
Yes, this a precise description.
OpenAPI Spec version 3.x does not specify how to serialize deeply-nested values to URL queries and swagger-js, the library powering swagger-ui (which powers LoopBack's REST API Explorer), silently ignores such values.
The issue has been reported and is being discussed here:
swagger-api/swagger-js#1385
OAI/OpenAPI-Specification#1706
Here are my body's model
{
"specs": [
{
"name": "test",
"id": "7dcf2db8-858e-4c00-bbde-e0c5c734770c"
}
]
}
trying to convert this last for uri module
body_format: json
body:
specs:
id: "id"
name: "name"
how to convert this last correctly ? switch to raw ?
specs is an array of dicts so the right syntax is:
body_format: json
body:
specs:
- id: "id"
name: "name"
I am using AWS Console and NodeJS.
I have the dynamodb table of users with partition key (user_id) and sort key (company_id) and other attributes.
One of my attributes is email of user. Email is unique attribute.
I need to get user_id by email but I haven't his user_id and company_id.
I think that I should use a Global Secondary Index.
I clicked on the users table, opened the Indexes tab and created GSI for this table. (name: email, type: GSI, Partition Key: email string, attributes: user_id)
I am using method Query from documentClient. This is my payload:
payload = {
"TableName": "users",
"IndexName": "email",
"KeyConditionExpression": "#index = :index_value",
"ExpressionAttributeNames":{
"#index": "email"
},
"ExpressionAttributeValues": {
":index_value": {"S": "test#gmail.com"}
},
"ProjectionExpression": "user_id",
"ScanIndexForward": false
};
}
This is my error from CloudWatch:
"errorMessage": "One or more parameter values were invalid: Condition parameter type does not match schema type"
I have found a solution while I was writing this question.
So as I use documentClient my payload should looks like this
payload = {
"TableName": "users",
"IndexName": "email",
"KeyConditionExpression": "#index = :index_value",
"ExpressionAttributeNames":{
"#index": "email"
},
"ExpressionAttributeValues": {
":index_value": "test#gmail.com" // <----------------
},
"ProjectionExpression": "user_id",
"ScanIndexForward": false
};
}
Hope it helps to someone
Our Web API returns Ember Data compatible objects with relationships defined like so:
{containers: [
{
id: "12345678",
name: "name",
items: [1, 2]
}
],
items: [
{
id: 1
name: "item 1"
},
{
id: 2
name: "item 2"
}
]
}
Notice that the item IDs are included in the container object, and that each related item is sideloaded in the same call. Is there any way to set up this kind of relationship with RestKit?