smithy-lang/smithy

Representing a list at the root of the output

SimeonYovchev opened this issue · 8 comments

I am encountering an issue with representing a list at the root of the output. I have an API endpoint that returns an array of users like this:
[{ id: 1, name: 'A' }, { id: 2, name: 'B' }]
I am trying to build a model to handle this scenario
I have the following model:

@restJson1
service MyService {
    version: "1.0.0"
    operations: [
        GetUsersList
    ]
}

@readonly
@http(method: "GET", uri: "/api/users", code: 200)
operation GetUsersList {
    input := {
        @httpQuery("permissions")
        permissions: String
        @httpQuery("show_inactive")
        show_inactive: Boolean
    }
    output := {
        @httpPayload
        content: UserListContent
    }
}

structure UserListContent {
    list: UserList
}

list UserList {
    member: User
}

structure User {
    id: String
    name: String
}

When I try to call getUsersList operation I get the following error:
TypeError: Expected object, got array

How can I solve this?

Looks like you're using restJson1 which has the following caveat given here:

This protocol only permits the httpPayload trait to be applied to members that target structures, documents, strings, blobs, or unions.

output := {
        @httpPayload
        content: UserList
    }

do this instead, get rid of UserListContent and you should be all set. If not, I would suggest you post more detail, ideally a complete reproduction such as a github repo.

Looks like you're using restJson1 which has the following caveat given here:

This protocol only permits the httpPayload trait to be applied to members that target structures, documents, strings, blobs, or unions.

oh, I didn't know about that part. So yeah, my advice won't help 😓 I suppose it'll just fail validation due to targetting a list shape.

While it's not as straightforward (and not exactly what you're desiring), if you adjust your model as @kubukoz said, you can try modifying your server to return a payload like { "content": [{ "id": 1, "name": "A" }, { "id": 2, "name": "B" }]}, you should see that it works.

Thanks for reaching out @haydenbaker @kubukoz. Is it possible to achieve the desired result using some other protocol? If yes, could you suggest me one?

One other question:
Today I tried to use simpleRestJson protocol and changed the model to this:

use alloy#simpleRestJson

@simpleRestJson
service MyService {
    version: "1.0.0"
    operations: [
        GetUsersList
    ]
}

My smithy-build.json file looks like this:

{
  "version": "1.0",
  "sources": ["model"],
  "maven": {
    "dependencies": [
      "software.amazon.smithy:smithy-aws-traits:1.48.0",
      "software.amazon.smithy.typescript:smithy-aws-typescript-codegen:0.19.0",
      "com.disneystreaming.alloy:alloy-core:0.3.7"
    ]
  },
  "plugins": {
    "typescript-codegen": {
      "package": "@my-api/client",
      "packageVersion": "0.0.1"
    }
  }
}

but when I run smithy build I got the following warning in the terminal:

[WARNING] Unable to find a protocol generator for pdapi#PlantDemandService: The pdapi#PlantDemandService service supports the following unsupported protocols [alloy#simpleRestJson]. The following protocol generators were found on the class path: [aws.protocols#restXml, aws.protocols#ec2Query, aws.protocols#awsJson1_1, aws.protocols#awsQuery, aws.protocols#awsJson1_0, aws.protocols#restJson1]

When I open one of generated typescript commands, the serializer and deserializer methods don't have any implementation, just throws an error:

private serialize(
    input: GetUsersListCommandInput,
    context: __SerdeContext
  ): Promise<__HttpRequest> {
    throw new Error("No supported protocol was found");
  }
  
  private deserialize(
    output: __HttpResponse,
    context: __SerdeContext
  ): Promise<GetUsersListCommandOutput> {
    throw new Error("No supported protocol was found");
  }

Do you have any idea why this happens?
Thanks!

I think simpleRestJson doesn't have a Typescript implementation, at least not a public one.

You can try the other JSON protocols given in the error message. However, your best bet is to change the server response.

Closing due to inactivity and having proposed solutions. Please reopen if you need additional assistance.