diff --git a/.changeset/happy-weeks-ring.md b/.changeset/happy-weeks-ring.md new file mode 100644 index 00000000000..0af026ad77c --- /dev/null +++ b/.changeset/happy-weeks-ring.md @@ -0,0 +1,7 @@ +--- +"@smithy/shared-ini-file-loader": patch +"@smithy/util-endpoints": patch +"@smithy/util-stream": patch +--- + +remove and ban circular imports diff --git a/packages/shared-ini-file-loader/src/constants.ts b/packages/shared-ini-file-loader/src/constants.ts new file mode 100644 index 00000000000..bf7dc920b68 --- /dev/null +++ b/packages/shared-ini-file-loader/src/constants.ts @@ -0,0 +1,4 @@ +/** + * @internal + */ +export const CONFIG_PREFIX_SEPARATOR = "."; diff --git a/packages/shared-ini-file-loader/src/getConfigData.ts b/packages/shared-ini-file-loader/src/getConfigData.ts index 8cc394f5398..193f6486f2b 100644 --- a/packages/shared-ini-file-loader/src/getConfigData.ts +++ b/packages/shared-ini-file-loader/src/getConfigData.ts @@ -1,7 +1,7 @@ import type { ParsedIniData } from "@smithy/types"; import { IniSectionType } from "@smithy/types"; -import { CONFIG_PREFIX_SEPARATOR } from "./loadSharedConfigFiles"; +import { CONFIG_PREFIX_SEPARATOR } from "./constants"; /** * Returns the config data from parsed ini data. diff --git a/packages/shared-ini-file-loader/src/loadSharedConfigFiles.ts b/packages/shared-ini-file-loader/src/loadSharedConfigFiles.ts index cf10c46ec2a..79f854e7145 100644 --- a/packages/shared-ini-file-loader/src/loadSharedConfigFiles.ts +++ b/packages/shared-ini-file-loader/src/loadSharedConfigFiles.ts @@ -40,10 +40,7 @@ export interface SharedConfigInit { const swallowError = () => ({}); -/** - * @internal - */ -export const CONFIG_PREFIX_SEPARATOR = "."; +export { CONFIG_PREFIX_SEPARATOR } from "./constants"; /** * Loads the config and credentials files. diff --git a/packages/shared-ini-file-loader/src/parseIni.ts b/packages/shared-ini-file-loader/src/parseIni.ts index dfb01f1e542..83d05336fb4 100644 --- a/packages/shared-ini-file-loader/src/parseIni.ts +++ b/packages/shared-ini-file-loader/src/parseIni.ts @@ -1,7 +1,7 @@ import type { ParsedIniData } from "@smithy/types"; import { IniSectionType } from "@smithy/types"; -import { CONFIG_PREFIX_SEPARATOR } from "./loadSharedConfigFiles"; +import { CONFIG_PREFIX_SEPARATOR } from "./constants"; const prefixKeyRegex = /^([\w-]+)\s(["'])?([\w-@\+\.%:/]+)\2$/; const profileNameBlockList = ["__proto__", "profile __proto__"]; diff --git a/packages/util-endpoints/src/utils/callFunction.spec.ts b/packages/util-endpoints/src/utils/callFunction.spec.ts index cc98883f110..6d2fadc8d79 100644 --- a/packages/util-endpoints/src/utils/callFunction.spec.ts +++ b/packages/util-endpoints/src/utils/callFunction.spec.ts @@ -1,13 +1,13 @@ import { afterEach, beforeEach, describe, expect, test as it, vi } from "vitest"; -import { callFunction } from "./callFunction"; import { customEndpointFunctions } from "./customEndpointFunctions"; import { endpointFunctions } from "./endpointFunctions"; -import { evaluateExpression } from "./evaluateExpression"; - -vi.mock("./evaluateExpression"); +import { callFunction, group } from "./evaluateExpression"; describe(callFunction.name, () => { + vi.spyOn(group, "evaluateExpression").mockImplementation(vi.fn()); + const { evaluateExpression } = group; + const mockOptions = { endpointParams: {}, referenceRecord: {}, diff --git a/packages/util-endpoints/src/utils/callFunction.ts b/packages/util-endpoints/src/utils/callFunction.ts index aafdcaa4b8d..25d15c0b7da 100644 --- a/packages/util-endpoints/src/utils/callFunction.ts +++ b/packages/util-endpoints/src/utils/callFunction.ts @@ -1,17 +1,2 @@ -import type { EvaluateOptions, Expression, FunctionObject, FunctionReturn } from "../types"; -import { customEndpointFunctions } from "./customEndpointFunctions"; -import { endpointFunctions } from "./endpointFunctions"; -import { evaluateExpression } from "./evaluateExpression"; - -export const callFunction = ({ fn, argv }: FunctionObject, options: EvaluateOptions): FunctionReturn => { - const evaluatedArgs = argv.map((arg) => - ["boolean", "number"].includes(typeof arg) ? arg : evaluateExpression(arg as Expression, "arg", options) - ); - const fnSegments = fn.split("."); - if (fnSegments[0] in customEndpointFunctions && fnSegments[1] != null) { - // @ts-ignore Element implicitly has an 'any' type - return customEndpointFunctions[fnSegments[0]][fnSegments[1]](...evaluatedArgs); - } - // @ts-ignore Element implicitly has an 'any' type - return endpointFunctions[fn](...evaluatedArgs); -}; +// breaks circular import +export { callFunction } from "./evaluateExpression"; diff --git a/packages/util-endpoints/src/utils/evaluateExpression.spec.ts b/packages/util-endpoints/src/utils/evaluateExpression.spec.ts index 81c136bf830..9b3747fd095 100644 --- a/packages/util-endpoints/src/utils/evaluateExpression.spec.ts +++ b/packages/util-endpoints/src/utils/evaluateExpression.spec.ts @@ -1,16 +1,17 @@ import { afterEach, describe, expect, test as it, vi } from "vitest"; import { EndpointError } from "../types"; -import { callFunction } from "./callFunction"; -import { evaluateExpression } from "./evaluateExpression"; +import { evaluateExpression, group } from "./evaluateExpression"; import { evaluateTemplate } from "./evaluateTemplate"; import { getReferenceValue } from "./getReferenceValue"; -vi.mock("./callFunction"); vi.mock("./getReferenceValue"); vi.mock("./evaluateTemplate"); describe(evaluateExpression.name, () => { + vi.spyOn(group, "callFunction").mockImplementation(vi.fn()); + const { callFunction } = group; + const mockOptions = { endpointParams: {}, referenceRecord: {}, diff --git a/packages/util-endpoints/src/utils/evaluateExpression.ts b/packages/util-endpoints/src/utils/evaluateExpression.ts index c3cdffc50be..e720c06828a 100644 --- a/packages/util-endpoints/src/utils/evaluateExpression.ts +++ b/packages/util-endpoints/src/utils/evaluateExpression.ts @@ -1,6 +1,7 @@ -import type { EvaluateOptions, Expression, FunctionObject, ReferenceObject } from "../types"; +import type { EvaluateOptions, Expression, FunctionObject, FunctionReturn, ReferenceObject } from "../types"; import { EndpointError } from "../types"; -import { callFunction } from "./callFunction"; +import { customEndpointFunctions } from "./customEndpointFunctions"; +import { endpointFunctions } from "./endpointFunctions"; import { evaluateTemplate } from "./evaluateTemplate"; import { getReferenceValue } from "./getReferenceValue"; @@ -8,9 +9,27 @@ export const evaluateExpression = (obj: Expression, keyName: string, options: Ev if (typeof obj === "string") { return evaluateTemplate(obj, options); } else if ((obj as FunctionObject)["fn"]) { - return callFunction(obj as FunctionObject, options); + return group.callFunction(obj as FunctionObject, options); } else if ((obj as ReferenceObject)["ref"]) { return getReferenceValue(obj as ReferenceObject, options); } throw new EndpointError(`'${keyName}': ${String(obj)} is not a string, function or reference.`); }; + +export const callFunction = ({ fn, argv }: FunctionObject, options: EvaluateOptions): FunctionReturn => { + const evaluatedArgs = argv.map((arg) => + ["boolean", "number"].includes(typeof arg) ? arg : group.evaluateExpression(arg as Expression, "arg", options) + ); + const fnSegments = fn.split("."); + if (fnSegments[0] in customEndpointFunctions && fnSegments[1] != null) { + // @ts-ignore Element implicitly has an 'any' type + return customEndpointFunctions[fnSegments[0]][fnSegments[1]](...evaluatedArgs); + } + // @ts-ignore Element implicitly has an 'any' type + return endpointFunctions[fn](...evaluatedArgs); +}; + +export const group = { + evaluateExpression, + callFunction, +}; diff --git a/packages/util-endpoints/src/utils/evaluateRules.spec.ts b/packages/util-endpoints/src/utils/evaluateRules.spec.ts index f8dea4e0da9..c4f6a6cc02d 100644 --- a/packages/util-endpoints/src/utils/evaluateRules.spec.ts +++ b/packages/util-endpoints/src/utils/evaluateRules.spec.ts @@ -4,14 +4,16 @@ import type { EndpointRuleObject, ErrorRuleObject, TreeRuleObject } from "../typ import { EndpointError } from "../types"; import { evaluateEndpointRule } from "./evaluateEndpointRule"; import { evaluateErrorRule } from "./evaluateErrorRule"; -import { evaluateRules } from "./evaluateRules"; -import { evaluateTreeRule } from "./evaluateTreeRule"; +import { evaluateRules, group } from "./evaluateRules"; vi.mock("./evaluateEndpointRule"); vi.mock("./evaluateErrorRule"); vi.mock("./evaluateTreeRule"); describe(evaluateRules.name, () => { + vi.spyOn(group, "evaluateTreeRule").mockImplementation(vi.fn()); + const { evaluateTreeRule } = group; + const mockOptions = { endpointParams: {}, referenceRecord: {}, diff --git a/packages/util-endpoints/src/utils/evaluateRules.ts b/packages/util-endpoints/src/utils/evaluateRules.ts index 3920d1cc6df..c8023585531 100644 --- a/packages/util-endpoints/src/utils/evaluateRules.ts +++ b/packages/util-endpoints/src/utils/evaluateRules.ts @@ -1,10 +1,10 @@ import type { EndpointV2 } from "@smithy/types"; -import type { EvaluateOptions, RuleSetRules } from "../types"; +import type { EvaluateOptions, RuleSetRules, TreeRuleObject } from "../types"; import { EndpointError } from "../types"; +import { evaluateConditions } from "./evaluateConditions"; import { evaluateEndpointRule } from "./evaluateEndpointRule"; import { evaluateErrorRule } from "./evaluateErrorRule"; -import { evaluateTreeRule } from "./evaluateTreeRule"; export const evaluateRules = (rules: RuleSetRules, options: EvaluateOptions): EndpointV2 => { for (const rule of rules) { @@ -16,7 +16,7 @@ export const evaluateRules = (rules: RuleSetRules, options: EvaluateOptions): En } else if (rule.type === "error") { evaluateErrorRule(rule, options); } else if (rule.type === "tree") { - const endpointOrUndefined = evaluateTreeRule(rule, options); + const endpointOrUndefined = group.evaluateTreeRule(rule, options); if (endpointOrUndefined) { return endpointOrUndefined; } @@ -26,3 +26,22 @@ export const evaluateRules = (rules: RuleSetRules, options: EvaluateOptions): En } throw new EndpointError(`Rules evaluation failed`); }; + +export const evaluateTreeRule = (treeRule: TreeRuleObject, options: EvaluateOptions): EndpointV2 | undefined => { + const { conditions, rules } = treeRule; + + const { result, referenceRecord } = evaluateConditions(conditions, options); + if (!result) { + return; + } + + return group.evaluateRules(rules, { + ...options, + referenceRecord: { ...options.referenceRecord, ...referenceRecord }, + }); +}; + +export const group = { + evaluateRules, + evaluateTreeRule, +}; diff --git a/packages/util-endpoints/src/utils/evaluateTreeRule.spec.ts b/packages/util-endpoints/src/utils/evaluateTreeRule.spec.ts index 0fe6efa9447..a41aabcfc9b 100644 --- a/packages/util-endpoints/src/utils/evaluateTreeRule.spec.ts +++ b/packages/util-endpoints/src/utils/evaluateTreeRule.spec.ts @@ -2,13 +2,15 @@ import { describe, expect, test as it, vi } from "vitest"; import type { TreeRuleObject } from "../types"; import { evaluateConditions } from "./evaluateConditions"; -import { evaluateRules } from "./evaluateRules"; +import { group } from "./evaluateRules"; import { evaluateTreeRule } from "./evaluateTreeRule"; vi.mock("./evaluateConditions"); -vi.mock("./evaluateRules"); describe(evaluateTreeRule.name, () => { + vi.spyOn(group, "evaluateRules").mockImplementation(() => ({}) as any); + const { evaluateRules } = group; + const mockOptions = { endpointParams: {}, referenceRecord: {}, diff --git a/packages/util-endpoints/src/utils/evaluateTreeRule.ts b/packages/util-endpoints/src/utils/evaluateTreeRule.ts index ae0f54b8353..a9a32cbafc3 100644 --- a/packages/util-endpoints/src/utils/evaluateTreeRule.ts +++ b/packages/util-endpoints/src/utils/evaluateTreeRule.ts @@ -1,19 +1,2 @@ -import type { EndpointV2 } from "@smithy/types"; - -import type { EvaluateOptions, TreeRuleObject } from "../types"; -import { evaluateConditions } from "./evaluateConditions"; -import { evaluateRules } from "./evaluateRules"; - -export const evaluateTreeRule = (treeRule: TreeRuleObject, options: EvaluateOptions): EndpointV2 | undefined => { - const { conditions, rules } = treeRule; - - const { result, referenceRecord } = evaluateConditions(conditions, options); - if (!result) { - return; - } - - return evaluateRules(rules, { - ...options, - referenceRecord: { ...options.referenceRecord, ...referenceRecord }, - }); -}; +// breaks circular import +export { evaluateTreeRule } from "./evaluateRules"; diff --git a/packages/util-endpoints/src/utils/getEndpointProperties.spec.ts b/packages/util-endpoints/src/utils/getEndpointProperties.spec.ts index 951c7a8d62f..8ca427189c3 100644 --- a/packages/util-endpoints/src/utils/getEndpointProperties.spec.ts +++ b/packages/util-endpoints/src/utils/getEndpointProperties.spec.ts @@ -1,11 +1,11 @@ import { afterEach, describe, expect, test as it, vi } from "vitest"; -import { getEndpointProperties } from "./getEndpointProperties"; -import { getEndpointProperty } from "./getEndpointProperty"; - -vi.mock("./getEndpointProperty"); +import { getEndpointProperties, group } from "./getEndpointProperties"; describe(getEndpointProperties.name, () => { + vi.spyOn(group, "getEndpointProperty"); + const { getEndpointProperty } = group; + const mockOptions = { endpointParams: {}, referenceRecord: {}, diff --git a/packages/util-endpoints/src/utils/getEndpointProperties.ts b/packages/util-endpoints/src/utils/getEndpointProperties.ts index da0e667ef30..1f823c777e3 100644 --- a/packages/util-endpoints/src/utils/getEndpointProperties.ts +++ b/packages/util-endpoints/src/utils/getEndpointProperties.ts @@ -1,11 +1,41 @@ +import type { EndpointObjectProperty } from "@smithy/types"; + import type { EndpointObjectProperties, EvaluateOptions } from "../types"; -import { getEndpointProperty } from "./getEndpointProperty"; +import { EndpointError } from "../types"; +import { evaluateTemplate } from "./evaluateTemplate"; export const getEndpointProperties = (properties: EndpointObjectProperties, options: EvaluateOptions) => Object.entries(properties).reduce( (acc, [propertyKey, propertyVal]) => ({ ...acc, - [propertyKey]: getEndpointProperty(propertyVal, options), + [propertyKey]: group.getEndpointProperty(propertyVal, options), }), {} ); + +export const getEndpointProperty = ( + property: EndpointObjectProperty, + options: EvaluateOptions +): EndpointObjectProperty => { + if (Array.isArray(property)) { + return property.map((propertyEntry) => getEndpointProperty(propertyEntry, options)); + } + switch (typeof property) { + case "string": + return evaluateTemplate(property, options); + case "object": + if (property === null) { + throw new EndpointError(`Unexpected endpoint property: ${property}`); + } + return group.getEndpointProperties(property, options); + case "boolean": + return property; + default: + throw new EndpointError(`Unexpected endpoint property type: ${typeof property}`); + } +}; + +export const group = { + getEndpointProperty, + getEndpointProperties, +}; diff --git a/packages/util-endpoints/src/utils/getEndpointProperty.spec.ts b/packages/util-endpoints/src/utils/getEndpointProperty.spec.ts index a36344a2345..e4f37205809 100644 --- a/packages/util-endpoints/src/utils/getEndpointProperty.spec.ts +++ b/packages/util-endpoints/src/utils/getEndpointProperty.spec.ts @@ -2,13 +2,14 @@ import { afterEach, beforeEach, describe, expect, test as it, vi } from "vitest" import { EndpointError } from "../types"; import { evaluateTemplate } from "./evaluateTemplate"; -import { getEndpointProperties } from "./getEndpointProperties"; -import { getEndpointProperty } from "./getEndpointProperty"; +import { getEndpointProperty, group } from "./getEndpointProperties"; vi.mock("./evaluateTemplate"); -vi.mock("./getEndpointProperties"); describe(getEndpointProperty.name, () => { + vi.spyOn(group, "getEndpointProperties").mockImplementation(vi.fn()); + const { getEndpointProperties } = group; + const mockOptions = { endpointParams: {}, referenceRecord: {}, diff --git a/packages/util-endpoints/src/utils/getEndpointProperty.ts b/packages/util-endpoints/src/utils/getEndpointProperty.ts index 20bd0d6ef8c..51c91aa3494 100644 --- a/packages/util-endpoints/src/utils/getEndpointProperty.ts +++ b/packages/util-endpoints/src/utils/getEndpointProperty.ts @@ -1,28 +1,2 @@ -import type { EndpointObjectProperty } from "@smithy/types"; - -import type { EvaluateOptions } from "../types"; -import { EndpointError } from "../types"; -import { evaluateTemplate } from "./evaluateTemplate"; -import { getEndpointProperties } from "./getEndpointProperties"; - -export const getEndpointProperty = ( - property: EndpointObjectProperty, - options: EvaluateOptions -): EndpointObjectProperty => { - if (Array.isArray(property)) { - return property.map((propertyEntry) => getEndpointProperty(propertyEntry, options)); - } - switch (typeof property) { - case "string": - return evaluateTemplate(property, options); - case "object": - if (property === null) { - throw new EndpointError(`Unexpected endpoint property: ${property}`); - } - return getEndpointProperties(property, options); - case "boolean": - return property; - default: - throw new EndpointError(`Unexpected endpoint property type: ${typeof property}`); - } -}; +// breaks circular import +export { getEndpointProperty } from "./getEndpointProperties"; diff --git a/packages/util-stream/src/blob/Uint8ArrayBlobAdapter.ts b/packages/util-stream/src/blob/Uint8ArrayBlobAdapter.ts index 2a78e35906b..3be815e5027 100644 --- a/packages/util-stream/src/blob/Uint8ArrayBlobAdapter.ts +++ b/packages/util-stream/src/blob/Uint8ArrayBlobAdapter.ts @@ -1,4 +1,5 @@ -import { transformFromString, transformToString } from "./transforms"; +import { fromBase64, toBase64 } from "@smithy/util-base64"; +import { fromUtf8, toUtf8 } from "@smithy/util-utf8"; /** * Adapter for conversions of the native Uint8Array type. @@ -12,7 +13,10 @@ export class Uint8ArrayBlobAdapter extends Uint8Array { */ public static fromString(source: string, encoding = "utf-8"): Uint8ArrayBlobAdapter { if (typeof source === "string") { - return transformFromString(source, encoding); + if (encoding === "base64") { + return Uint8ArrayBlobAdapter.mutate(fromBase64(source)); + } + return Uint8ArrayBlobAdapter.mutate(fromUtf8(source)); } throw new Error(`Unsupported conversion from ${typeof source} to Uint8ArrayBlobAdapter.`); } @@ -31,6 +35,9 @@ export class Uint8ArrayBlobAdapter extends Uint8Array { * @returns the blob as string. */ public transformToString(encoding = "utf-8"): string { - return transformToString(this, encoding); + if (encoding === "base64") { + return toBase64(this); + } + return toUtf8(this); } } diff --git a/packages/util-stream/src/blob/transforms.ts b/packages/util-stream/src/blob/transforms.ts deleted file mode 100644 index f619ed46458..00000000000 --- a/packages/util-stream/src/blob/transforms.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { fromBase64, toBase64 } from "@smithy/util-base64"; -import { fromUtf8, toUtf8 } from "@smithy/util-utf8"; - -import { Uint8ArrayBlobAdapter } from "./Uint8ArrayBlobAdapter"; - -/** - * @internal - */ -export function transformToString(payload: Uint8Array, encoding = "utf-8"): string { - if (encoding === "base64") { - return toBase64(payload); - } - return toUtf8(payload); -} - -/** - * @internal - */ -export function transformFromString(str: string, encoding?: string): Uint8ArrayBlobAdapter { - if (encoding === "base64") { - return Uint8ArrayBlobAdapter.mutate(fromBase64(str)); - } - return Uint8ArrayBlobAdapter.mutate(fromUtf8(str)); -} diff --git a/scripts/compilation/Inliner.js b/scripts/compilation/Inliner.js index 4a6e1f4e6f3..66432ea9630 100644 --- a/scripts/compilation/Inliner.js +++ b/scripts/compilation/Inliner.js @@ -176,6 +176,16 @@ module.exports = class Inliner { }, }), ], + onwarn(warning) { + /* + Circular imports are not an error in the language spec, + but reasoning about the program and bundling becomes easier. + For that reason let's avoid them. + */ + if (warning.code === "CIRCULAR_DEPENDENCY") { + throw Error(warning.message); + } + }, external: (id) => { const relative = !!id.match(/^\.?\.?\//); if (!relative) {