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

chore(middleware-flexible-checksums): add RequestChecksumCalculation and ResponseChecksumValidation #6465

Merged
merged 4 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/middleware-flexible-checksums/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
},
"license": "Apache-2.0",
"dependencies": {
"@smithy/node-config-provider": "^3.1.5",
"@aws-crypto/crc32": "5.2.0",
"@aws-crypto/crc32c": "5.2.0",
"@aws-sdk/types": "*",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { LoadedConfigSelectors } from "@smithy/node-config-provider";

import { RequestChecksumCalculation } from "./constants";
import { SelectorType, stringUnionSelector } from "./stringUnionSelector";

export const ENV_REQUEST_CHECKSUM_CALCULATION = "AWS_REQUEST_CHECKSUM_CALCULATION";
export const CONFIG_REQUEST_CHECKSUM_CALCULATION = "request_checksum_calculation";
export const DEFAULT_REQUEST_CHECKSUM_CALCULATION = RequestChecksumCalculation.WHEN_SUPPORTED;

export const NODE_REQUEST_CHECKSUM_CALCULATION_CONFIG_OPTIONS: LoadedConfigSelectors<string> = {
environmentVariableSelector: (env) =>
stringUnionSelector(env, ENV_REQUEST_CHECKSUM_CALCULATION, RequestChecksumCalculation, SelectorType.ENV),
configFileSelector: (profile) =>
stringUnionSelector(profile, CONFIG_REQUEST_CHECKSUM_CALCULATION, RequestChecksumCalculation, SelectorType.CONFIG),
default: DEFAULT_REQUEST_CHECKSUM_CALCULATION,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { LoadedConfigSelectors } from "@smithy/node-config-provider";

import { RequestChecksumCalculation } from "./constants";
import { SelectorType, stringUnionSelector } from "./stringUnionSelector";

export const ENV_RESPONSE_CHECKSUM_VALIDATION = "AWS_RESPONSE_CHECKSUM_VALIDATION";
export const CONFIG_RESPONSE_CHECKSUM_VALIDATION = "response_checksum_validation";
export const DEFAULT_RESPONSE_CHECKSUM_VALIDATION = RequestChecksumCalculation.WHEN_SUPPORTED;

export const NODE_RESPONSE_CHECKSUM_VALIDATION_CONFIG_OPTIONS: LoadedConfigSelectors<string> = {
environmentVariableSelector: (env) =>
stringUnionSelector(env, ENV_RESPONSE_CHECKSUM_VALIDATION, RequestChecksumCalculation, SelectorType.ENV),
configFileSelector: (profile) =>
stringUnionSelector(profile, CONFIG_RESPONSE_CHECKSUM_VALIDATION, RequestChecksumCalculation, SelectorType.CONFIG),
default: DEFAULT_RESPONSE_CHECKSUM_VALIDATION,
};
46 changes: 46 additions & 0 deletions packages/middleware-flexible-checksums/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,49 @@
/**
* Determines when a checksum will be calculated for request payloads.
*/
export const RequestChecksumCalculation = {
/**
* When set, a checksum will be calculated for all request payloads of operations
* modeled with the {@link httpChecksum} trait where `requestChecksumRequired` is `true`
* AND/OR a `requestAlgorithmMember` is modeled.
* {@link https://smithy.io/2.0/aws/aws-core.html#aws-protocols-httpchecksum-trait httpChecksum}
*/
WHEN_SUPPORTED: "WHEN_SUPPORTED",

/**
* When set, a checksum will only be calculated for request payloads of operations
* modeled with the {@link httpChecksum} trait where `requestChecksumRequired` is `true`
* OR where a `requestAlgorithmMember` is modeled and the user sets it.
* {@link https://smithy.io/2.0/aws/aws-core.html#aws-protocols-httpchecksum-trait httpChecksum}
*/
WHEN_REQUIRED: "WHEN_REQUIRED",
} as const;

export type RequestChecksumCalculation = (typeof RequestChecksumCalculation)[keyof typeof RequestChecksumCalculation];

/**
* Determines when checksum validation will be performed on response payloads.
*/
export const ResponseChecksumValidation = {
/**
* When set, checksum validation MUST be performed on all response payloads of operations
* modeled with the {@link httpChecksum} trait where `responseAlgorithms` is modeled,
* except when no modeled checksum algorithms are supported by an SDK.
* {@link https://smithy.io/2.0/aws/aws-core.html#aws-protocols-httpchecksum-trait httpChecksum}
*/
WHEN_SUPPORTED: "WHEN_SUPPORTED",

/**
* When set, checksum validation MUST NOT be performed on response payloads of operations UNLESS
* the SDK supports the modeled checksum algorithms AND the user has set the `requestValidationModeMember` to `ENABLED`.
* It is currently impossible to model an operation as requiring a response checksum,
* but this setting leaves the door open for future updates.
*/
WHEN_REQUIRED: "WHEN_REQUIRED",
} as const;

export type ResponseChecksumValidation = (typeof ResponseChecksumValidation)[keyof typeof ResponseChecksumValidation];

/**
* Checksum Algorithms supported by the SDK.
*/
Expand Down
2 changes: 2 additions & 0 deletions packages/middleware-flexible-checksums/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export * from "./NODE_REQUEST_CHECKSUM_CALCULATION_CONFIG_OPTIONS";
export * from "./NODE_RESPONSE_CHECKSUM_VALIDATION_CONFIG_OPTIONS";
export * from "./constants";
export * from "./flexibleChecksumsMiddleware";
export * from "./getFlexibleChecksumsPlugin";
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { SelectorType, stringUnionSelector } from "./stringUnionSelector";

describe(stringUnionSelector.name, () => {
const key = "key";
const value = "VALUE";
const obj: { [key]: any } = {} as any;
const union = { [key]: value };

describe.each(Object.entries(SelectorType))(`Selector %s`, (selectorKey, selectorValue) => {
beforeEach(() => {
delete obj[key];
});

it(`should return undefined if ${key} is not defined`, () => {
// @ts-expect-error Element implicitly has an 'any' type
expect(stringUnionSelector(obj, key, union, SelectorType[selectorKey])).toBeUndefined();
});

it.each([
[value, value],
[value, value.toLowerCase()],
[value, [...value].map((c, i) => (i % 2 === 0 ? c.toLowerCase() : c.toUpperCase())).join("")],
])(`should return number %s if ${key}="%s"`, (output, input) => {
obj[key] = input;
// @ts-expect-error Element implicitly has an 'any' type
expect(stringUnionSelector(obj, key, union, SelectorType[selectorKey])).toBe(output);
});

// Thows if the value is something other than different case.
it.each(["value1", "1value", [...value].reverse().join("")])(`should throw if ${key}=%s`, (input) => {
obj[key] = input;
// @ts-expect-error Element implicitly has an 'any' type
expect(() => stringUnionSelector(obj, key, union, SelectorType[selectorKey])).toThrow(
new TypeError(
`Cannot load ${selectorValue} '${key}'. Expected one of ${Object.values(union)}, got '${obj[key]}'.`
)
);
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export enum SelectorType {
ENV = "env",
CONFIG = "shared config entry",
}

/**
* Returns undefined, if obj[key] is not defined.
* Returns string value, if the string is defined in obj[key] and it's uppercase matches union value.
* Throws error for all other cases.
*
* @internal
*/
export const stringUnionSelector = (
obj: Record<string, string | undefined>,
key: string,
union: Record<string, string>,
type: SelectorType
) => {
if (!(key in obj)) return undefined;

const value = obj[key]!.toUpperCase();
if (!Object.values(union).includes(value)) {
throw new TypeError(`Cannot load ${type} '${key}'. Expected one of ${Object.values(union)}, got '${obj[key]}'.`);
}

return value;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it looks like this could/should be reused for other enum specifications like AccountIdEndpointMode

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Their validator does not check for casing, but flexible checksums one needs to.

export function validateAccountIdEndpointMode(value: any): value is AccountIdEndpointMode {
return ACCOUNT_ID_ENDPOINT_MODE_VALUES.includes(value);
}

Loading