smithy-lang / smithy

Smithy is a protocol-agnostic interface definition language and set of tools for generating clients, servers, and documentation for any programming language.
https://smithy.io
Apache License 2.0
1.77k stars 209 forks source link

Representing a list at the root of the output #2270

Closed SimeonYovchev closed 4 months ago

SimeonYovchev commented 5 months ago

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?

haydenbaker commented 4 months ago

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.

kubukoz commented 4 months ago
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.

kubukoz commented 4 months ago

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.

haydenbaker commented 4 months ago

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.

SimeonYovchev commented 4 months ago

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#awsJson10, 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!

kubukoz commented 4 months ago

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

haydenbaker commented 4 months ago

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

kstich commented 4 months ago

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