Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TypeScript - mark response properties required if they're guaranteed to be present #6238

Closed
2 tasks
jedwards1211 opened this issue Jun 29, 2024 · 10 comments
Closed
2 tasks
Assignees
Labels
duplicate This issue is a duplicate. feature-request New feature or enhancement. May require GitHub community feedback. guidance General information and guidance, answers to FAQs, or recommended best practices/resources. p2 This is a standard priority issue response-requested Waiting on additional info and feedback. Will move to \"closing-soon\" in 7 days.

Comments

@jedwards1211
Copy link

jedwards1211 commented Jun 29, 2024

Describe the feature

It would be way better if not every response property was optional.
I'm sure there were reasons for this, but I'm complaining anyway -- are the reasons for it really that justified? ಠ_ಠ I doubt it, I think AWS could invest in making it better.
Overall the SDK is pretty great to work with, but the blanket optional properties waste a lot of time.

Use Case

All response properties being optional makes TypeScript code cantankerous and ugly:

const { Stacks: [Stack] = [] } = await cfn.send(new DescribeStacksCommand({ StackName })

if (!Stack) {
  throw new Error(`this shouldn't happen, but we need a non-null assertion here`)
}

const Parameters = Object.fromEntries(
  // it's silly for ParameterKey to be marked optional
  Stack.Parameters?.flatMap(p => p.ParameterKey ? [[p.ParameterKey, p.ParameterValue]] : [])
    || []
)

Proposed Solution

I don't know why exactly this is (microservices being implemented in a language where all values are nullable or something? Some obscure option to select which properties of the response we want?) But, there has to be a way to write up metadata on which properties are guaranteed to be defined in service responses.

Other Information

You might say, why not use TypeScript non-null operations (the trailing !), but it feels too dangerous. It's reasonable to assume an ARN or Id property will always be defined, but there are plenty of properties where I don't think I can say for sure what the API guarantees.

Acknowledgements

  • I may be able to implement this feature request
  • This feature might incur a breaking change

SDK version used

3.588

Environment details (OS name and version, etc.)

macOS any

@jedwards1211 jedwards1211 added feature-request New feature or enhancement. May require GitHub community feedback. needs-triage This issue or PR still needs to be triaged. labels Jun 29, 2024
@tracy-french
Copy link

+1.

Every field of a response seems to be set as a union with undefined. This typically leads to boiler-plate defaulting code on responses for fields which are actually required. The same is true for response types, which can lead to bugs, as you can end up sending an invalid request fairly easily.

I've been overcoming this limitation using type-fest's SetNonNullable utility type. It removes the undefined unions while retaining the ? optional annotations. For example:

import type { DescribeProjectResponse } from '@aws-sdk/client-iotsitewise';

type ActualDescribeProjectResponse = SetNonNullable<DescribeProjectResponse>;

The SDK response is typed as:

interface DescribeProjectResponse {
  portalId: string | undefined;
  projectArn: string | undefined;
  projectCreationDate: Date | undefined;
  projectDescription?: string | undefined;
  projectId: string | undefined;
  projectLastUpdateDate: Date | undefined;
  projectName: string | undefined;
}

After using SetNonNullable:

interface ActualDescribeProjectResponse {
  portalId: string;
  projectArn: string;
  projectCreationDate: Date;
  projectDescription?: string;
  projectId: string;
  projectLastUpdateDate: Date;
  projectName: string;
}

The fields are now correctly typed, with projectDescription retaining the optional annotation.

It's not a perfect utility, as it's not recursive. For a List* API with an array field, you would need to do the same on the array elements. You also need to cast the responses returned to the correct types, which makes me a bit nervous.

I'm the owner of the API in my example, so I feel pretty confident about the ActualDescribeProjectResponse type being correct and I don't understand why the SDK would use the undefined union pattern. Could someone provide some clarity for why this decision was made?

I imagine it would be a major breaking change to remove the undefined unions, so I don't expect that to happen. It would be cool if we could add a second set of types with the correct typings, or a "strict" mode on the SDK for the methods/commands to return the correct types.

Thank you! :)

@RanVaknin
Copy link
Contributor

Hi there,

I have covered this in my response here #5992 (comment)

Please let us know if you have any other questions.
Thanks,
Ran~

@RanVaknin RanVaknin self-assigned this Jul 1, 2024
@RanVaknin RanVaknin added response-requested Waiting on additional info and feedback. Will move to \"closing-soon\" in 7 days. p2 This is a standard priority issue and removed needs-triage This issue or PR still needs to be triaged. labels Jul 1, 2024
@jedwards1211
Copy link
Author

jedwards1211 commented Jul 1, 2024

Thanks @RanVaknin.

I understand the forward compatibility goals but I'm not sure how well that actually accomplishes it. There are many processes we have to write that can only continue if an id, ARN, or other field is present in a response. If AWS made one of those fields optional in the future, even if our process checks for that and avoids errors, it would have to abort without being able to accomplish its intended purpose, so the change would be effectively breaking anyway.

So I'd like to see AWS commit to at least guaranteeing they're not going to make ARN or id-like fields optional without warning (which would include things like RoleName or InstanceProfileName that are the only way many commands allow us to uniquely identify a resource). If a response field like that becomes optional it should be released as a noticeable breaking change.

@jedwards1211
Copy link
Author

Also, where do the docs live on GitHub now? I could make a PR to reference the smithy types you pointed out. (The documentation source link in the developer guide points to the archived https://github.com/awsdocs/aws-sdk-for-javascript-v3)

@kuhe
Copy link
Contributor

kuhe commented Jul 1, 2024

Our recommendation is to use one of the type transforms from @smithy/types.

https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-smithy-types/

import { S3 } from "@aws-sdk/client-s3";
import type { AssertiveClient, UncheckedClient } from "@smithy/types";

const s3a = new S3({}) as AssertiveClient<S3>;
const s3b = new S3({}) as UncheckedClient<S3>;

// AssertiveClient enforces required inputs are not undefined
// and required outputs are not undefined.
const get = await s3a.getObject({
  Bucket: "",
  Key: "",
});

// UncheckedClient makes output fields non-nullable.
// You should still perform type checks as you deem
// necessary, but the SDK will no longer prompt you
// with nullability errors.
const body = await (
  await s3b.getObject({
    Bucket: "",
    Key: "",
  })
).Body.transformToString();

This differs from SetNonNullable in that it only removes | undefined from required fields, and is recursive.

@kuhe kuhe added duplicate This issue is a duplicate. guidance General information and guidance, answers to FAQs, or recommended best practices/resources. labels Jul 1, 2024
@jedwards1211
Copy link
Author

jedwards1211 commented Jul 1, 2024

@kuhe Yes, I found that in the linked comment. However, that information wasn't discoverable enough for me to find it before I opened an issue; I think we should add it to the Developer Guide for SDK Version 3, because I would have found it there.

Aside from that, I want to know if y'all have any specific response about this:

There are many processes we have to write that can only continue if an id, ARN, or other field is present in a response. If AWS made one of those fields optional in the future, even if our process checks for that and avoids errors, it would have to abort without being able to accomplish its intended purpose, so the change would be effectively breaking anyway.

So I'd like to see AWS commit to at least guaranteeing they're not going to make ARN or id-like fields optional without warning...

And by commit I mean at least mark those fields required.

@github-actions github-actions bot removed the response-requested Waiting on additional info and feedback. Will move to \"closing-soon\" in 7 days. label Jul 2, 2024
@kuhe
Copy link
Contributor

kuhe commented Jul 2, 2024

We can make a request to our documentation writer teammates to add a link to the supplemental developer-written docs. @RanVaknin please open a ticket.

Our default types have | undefined on fields that are "required" as defined in the service models. We are not likely to remove this because of backwards compatibility, and to encourage runtime type-checking of service responses coming from outside the SDK application code. That is why the type transform is an opt-in addon instead of default behavior.

We may decide to remove | undefined in the future, since it does cause a fair bit of confusion, I think, but it's not currently planned, since it would require a long campaign to make users aware of the breaking change. We are also, as a matter of policy, unable to release a major version update to the AWS SDK at this time.

@RanVaknin RanVaknin added the response-requested Waiting on additional info and feedback. Will move to \"closing-soon\" in 7 days. label Jul 2, 2024
@jedwards1211
Copy link
Author

jedwards1211 commented Jul 3, 2024

@kuhe it would be nice to have a way to opt into types that are kind of a middle ground, where response properties like arns, ids, names used to uniquely identify things, tag and parameter keys etc. are required, but there's still | undefined on other properties where there's more of a risk that AWS would make a breaking change.

@RanVaknin
Copy link
Contributor

Hi,

I have forwarded your request to add this section to our developer guide to make this more discoverable.

For the reason mentioned above this request is not actionable by the SDK team. In order for this not to be a breaking change, this would need a new major version which is not planned in the foreseeable future.
The way to work around the issue you highlighted is to use the documentation from https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-smithy-types/

Since this is not actionable, I'm going to go ahead and close this.

Thanks again for reaching out.
All the best,
Ran~

@RanVaknin RanVaknin closed this as not planned Won't fix, can't repro, duplicate, stale Jul 3, 2024
Copy link

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs and link to relevant comments in this thread.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 19, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
duplicate This issue is a duplicate. feature-request New feature or enhancement. May require GitHub community feedback. guidance General information and guidance, answers to FAQs, or recommended best practices/resources. p2 This is a standard priority issue response-requested Waiting on additional info and feedback. Will move to \"closing-soon\" in 7 days.
Projects
None yet
Development

No branches or pull requests

4 participants