Search code examples
amazon-web-servicesaws-lambdaamazon-dynamodbsetaws-api-gateway

Lambda-Dynamo API returns empty object from a Number Set that `has` data


thank you in advance for stopping by :)

Something weird is going on with my API Gateway - Lambda - DynamoDB combo: I am inserting items using a PutCommand

try {
        return await documentClient.send(new PutCommand({
            TableName: process.env.TABLE_NAME,
            Item: {
                'PK': '#LEADERBOARDS',
                'SK': `LEADERBOARD#${requestBody.leaderboard_id}`,
                'leaderboard_display_name': requestBody.leaderboard_display_name,
                'participant_type': requestBody.participant_type,
                'active_leaderboard': requestBody.active_leaderboard,
                'tournaments': requestBody.tournaments,
                'associated_tournaments': new Set(requestBody.associated_tournaments)
            },
            ConditionExpression: 'attribute_not_exists(PK) AND attribute_not_exists(SK)',
            ReturnConsumedCapacity: 'TOTAL'
        }))
    } catch (error) {
        return error
    }

Everything works as expected, except for the number set 'associated_tournaments', which when I try to retrieve via a GetCommand:

try {
        return await documentClient.send(new GetCommand({
            TableName: process.env.TABLE_NAME,
            Key: {
                'PK': '#LEADERBOARDS',
                'SK': `LEADERBOARD#${leaderboardName}`
            },
            ReturnConsumedCapacity: 'TOTAL'
        }))
    } catch (error) {
        return error
    }

it returns an empty object '{}' to the caller (using curl, or postman), however from the Dynamo UI and by querying via the CLI I can see the data is actually present in the attribute and item.

This is the CLI command I'm using:

aws dynamodb get-item   --table-name STGLeaderboards   --key '{"PK": {"S": "#LEADERBOARDS"}, "SK": {"S": "LEADERBOARD#LeaderboardPM"}}'  --return-consumed-capacity TOTAL --profile qlash --region eu-central-1

which returns the numbers in the set.

The curl command hitting the API Gateway endpoint:

curl 'https://api.stg.tournaments.qlash.gg/admin/leaderboard/LeaderboardPM'

which triggers the GetCommand I shared above via this handler:

export const handler = async (event) => {

    switch (event.routeKey) {
        case 'POST /admin/leaderboard':
            return await createLeaderboard(JSON.parse(event.body))
        case 'GET /admin/leaderboards':
            return await getLeaderboards()
        case 'GET /admin/leaderboard/{leaderboard_name}':
            return await getLeaderboard(event.pathParameters.leaderboard_name)
        case 'PATCH /admin/leaderboard/{leaderboard_name}':
            return await updateLeaderboard(event.pathParameters.leaderboard_name, JSON.parse(event.body))
        case 'DELETE /admin/leaderboard/{leaderboard_name}':
            return await deleteLeaderboard(event.pathParameters.leaderboard_name)
        default:
            return {
                statusCode: 404,
                body: JSON.stringify({'message': 'No matching path'}),
            }
    }
}

Here is the output of the curl command:

{
    "$metadata": {
        "httpStatusCode": 200,
        "requestId": "CDDA7KIQSDR7JE4PA06S6AB3HNVV4KQNSO5AEMVJF66Q9ASUAAJG",
        "attempts": 1,
        "totalRetryDelay": 0
    },
    "ConsumedCapacity": {
        "CapacityUnits": 0.5,
        "TableName": "STGLeaderboards"
    },
    "Item": {
        "SK": "LEADERBOARD#LeaderboardPM",
        "associated_tournaments": {},
        "active_leaderboard": true,
        "leaderboard_display_name": "This Leaderboards is AWESOME",
        "participant_type": "SOLO"
    }
}

as you can see it returns an empty set, but from the dynamodb dashboard I can see two associated tournaments in the form {7121, 7224}

What is going on here? :/ Am I missing something about converting back the Set in some way?

Thank you in advance, and have a nice day!

Ettore


Solution

  • I bumped into the same issue recently. I solved it manipulating the response, converting the set back into an array in the response, and now it is working as expected.

    Here is how you'd do it: (for a single item)

    const response = await getItem();
    
                return JSON.stringify({
                    ...response,
                    Item: {
                        ...response.Item,
                        associated_items: [...response.Item.associated_items]
                    }
                });
    

    (and for a scan/query)

    const response = await getLeaderboards();
    
                return JSON.stringify({
                    ...response,
                    Items: response.Items.map(item => ({
                        ...item,
                        associated_tournaments: [...item.associated_tournaments]
                    }))
                })
    

    I know it cloud be a hit in terms of performance for a response with many items, but I don't know what to say... I think there's something funky going on with the API Gateway... AWS FIX YOUR **** LMAO

    Jokes aside, hope this helps :)