diff --git a/docs/docs/04-standard-library/04-util/api-reference.md b/docs/docs/04-standard-library/04-util/api-reference.md index 6e187d19f4f..d5fb095130e 100644 --- a/docs/docs/04-standard-library/04-util/api-reference.md +++ b/docs/docs/04-standard-library/04-util/api-reference.md @@ -23,6 +23,8 @@ Utility functions. | **Name** | **Description** | | --- | --- | +| base64Decode | Converts a string from base64 to UTF-8. | +| base64Encode | Converts a string from UTF-8 to base64. | | env | Returns the value of an environment variable. | | nanoid | Generates a unique ID using the nanoid library. | | sha256 | Computes the SHA256 hash of the given data. | @@ -33,6 +35,56 @@ Utility functions. --- +##### `base64Decode` + +```wing +bring util; + +util.base64Decode(stringToDecode: str, url?: bool); +``` + +Converts a string from base64 to UTF-8. + +###### `stringToDecode`Required + +- *Type:* str + +base64 string to decode. + +--- + +###### `url`Optional + +- *Type:* bool + +If `true`, the source is expected to be a URL-safe base64 string. + +--- + +##### `base64Encode` + +```wing +bring util; + +util.base64Encode(stringToEncode: str, url?: bool); +``` + +Converts a string from UTF-8 to base64. + +###### `stringToEncode`Required + +- *Type:* str + +--- + +###### `url`Optional + +- *Type:* bool + +If `true`, a URL-safe base64 is returned. + +--- + ##### `env` ```wing diff --git a/examples/tests/sdk_tests/util/base64.w b/examples/tests/sdk_tests/util/base64.w new file mode 100644 index 00000000000..f0171adbc37 --- /dev/null +++ b/examples/tests/sdk_tests/util/base64.w @@ -0,0 +1,25 @@ +bring util; + +let string = "https://www.winglang.io/docs"; +let base64Encode = util.base64Encode(string); +let base64urlEncode = util.base64Encode(string, true); +let base64Decode = util.base64Decode("aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw=="); +let base64urlDecode = util.base64Decode("aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw", true); + +assert(base64Encode == "aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw=="); +assert(base64urlEncode == "aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw"); +assert(base64Decode == string); +assert(base64urlDecode == string); + +test "inflight base64" { + let string = "https://www.winglang.io/docs"; + let base64Encode = util.base64Encode(string); + let base64urlEncode = util.base64Encode(string, true); + let base64Decode = util.base64Decode("aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw=="); + let base64urlDecode = util.base64Decode("aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw", true); + + assert(base64Encode == "aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw=="); + assert(base64urlEncode == "aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw"); + assert(base64Decode == string); + assert(base64urlDecode == string); +} \ No newline at end of file diff --git a/libs/wingc/src/lsp/snapshots/completions/util_static_methods.snap b/libs/wingc/src/lsp/snapshots/completions/util_static_methods.snap index f9eb8e1722c..64dd9ba3511 100644 --- a/libs/wingc/src/lsp/snapshots/completions/util_static_methods.snap +++ b/libs/wingc/src/lsp/snapshots/completions/util_static_methods.snap @@ -1,6 +1,30 @@ --- source: libs/wingc/src/lsp/completions.rs --- +- label: base64Decode + kind: 2 + detail: "(stringToDecode: str, url: bool?): str" + documentation: + kind: markdown + value: "```wing\nstatic base64Decode: (stringToDecode: str, url: bool?): str\n```\n---\nConverts a string from base64 to UTF-8.\n\n\n### Returns\nThe UTF-8 string." + sortText: ff|base64Decode + insertText: base64Decode($0) + insertTextFormat: 2 + command: + title: triggerParameterHints + command: editor.action.triggerParameterHints +- label: base64Encode + kind: 2 + detail: "(stringToEncode: str, url: bool?): str" + documentation: + kind: markdown + value: "```wing\nstatic base64Encode: (stringToEncode: str, url: bool?): str\n```\n---\nConverts a string from UTF-8 to base64.\n\n\n### Returns\nThe base64 string." + sortText: ff|base64Encode + insertText: base64Encode($0) + insertTextFormat: 2 + command: + title: triggerParameterHints + command: editor.action.triggerParameterHints - label: env kind: 2 detail: "(name: str): str" diff --git a/libs/wingsdk/src/util/util.ts b/libs/wingsdk/src/util/util.ts index d159eaffebf..70aabf9bc54 100644 --- a/libs/wingsdk/src/util/util.ts +++ b/libs/wingsdk/src/util/util.ts @@ -78,6 +78,28 @@ export class Util { return process.env[name]; } + /** + * Converts a string from UTF-8 to base64. + * @param name The name of the UTF-8 string to encode. + * @param url If `true`, a URL-safe base64 is returned. + * @returns The base64 string. + */ + public static base64Encode(stringToEncode: string, url?: boolean): string { + return Buffer.from(stringToEncode).toString(url ? "base64url" : "base64"); + } + + /** + * Converts a string from base64 to UTF-8. + * @param stringToDecode base64 string to decode. + * @param url If `true`, the source is expected to be a URL-safe base64 string + * @returns The UTF-8 string. + */ + public static base64Decode(stringToDecode: string, url?: boolean): string { + return Buffer.from(stringToDecode, url ? "base64url" : "base64").toString( + "utf8" + ); + } + /** * Suspends execution for a given duration. * @param delay The time to suspend execution. diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/util/base64.w_compile_tf-aws.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/util/base64.w_compile_tf-aws.md new file mode 100644 index 00000000000..1569a00007c --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/util/base64.w_compile_tf-aws.md @@ -0,0 +1,201 @@ +# [base64.w](../../../../../../examples/tests/sdk_tests/util/base64.w) | compile | tf-aws + +## inflight.$Closure1.js +```js +module.exports = function({ $util_Util }) { + class $Closure1 { + constructor({ }) { + const $obj = (...args) => this.handle(...args); + Object.setPrototypeOf($obj, this); + return $obj; + } + async handle() { + const string = "https://www.winglang.io/docs"; + const base64Encode = (await $util_Util.base64Encode(string)); + const base64urlEncode = (await $util_Util.base64Encode(string,true)); + const base64Decode = (await $util_Util.base64Decode("aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw==")); + const base64urlDecode = (await $util_Util.base64Decode("aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw",true)); + {((cond) => {if (!cond) throw new Error("assertion failed: base64Encode == \"aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw==\"")})((base64Encode === "aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw=="))}; + {((cond) => {if (!cond) throw new Error("assertion failed: base64urlEncode == \"aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw\"")})((base64urlEncode === "aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw"))}; + {((cond) => {if (!cond) throw new Error("assertion failed: base64Decode == string")})((base64Decode === string))}; + {((cond) => {if (!cond) throw new Error("assertion failed: base64urlDecode == string")})((base64urlDecode === string))}; + } + } + return $Closure1; +} + +``` + +## main.tf.json +```json +{ + "//": { + "metadata": { + "backend": "local", + "stackName": "root", + "version": "0.17.0" + }, + "outputs": { + "root": { + "Default": { + "cloud.TestRunner": { + "TestFunctionArns": "WING_TEST_RUNNER_FUNCTION_ARNS" + } + } + } + } + }, + "output": { + "WING_TEST_RUNNER_FUNCTION_ARNS": { + "value": "[[\"root/Default/Default/test:inflight base64\",\"${aws_lambda_function.testinflightbase64_Handler_31E9772F.arn}\"]]" + } + }, + "provider": { + "aws": [ + {} + ] + }, + "resource": { + "aws_iam_role": { + "testinflightbase64_Handler_IamRole_49F68A60": { + "//": { + "metadata": { + "path": "root/Default/Default/test:inflight base64/Handler/IamRole", + "uniqueId": "testinflightbase64_Handler_IamRole_49F68A60" + } + }, + "assume_role_policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Action\":\"sts:AssumeRole\",\"Principal\":{\"Service\":\"lambda.amazonaws.com\"},\"Effect\":\"Allow\"}]}" + } + }, + "aws_iam_role_policy": { + "testinflightbase64_Handler_IamRolePolicy_031C1061": { + "//": { + "metadata": { + "path": "root/Default/Default/test:inflight base64/Handler/IamRolePolicy", + "uniqueId": "testinflightbase64_Handler_IamRolePolicy_031C1061" + } + }, + "policy": "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Effect\":\"Allow\",\"Action\":\"none:null\",\"Resource\":\"*\"}]}", + "role": "${aws_iam_role.testinflightbase64_Handler_IamRole_49F68A60.name}" + } + }, + "aws_iam_role_policy_attachment": { + "testinflightbase64_Handler_IamRolePolicyAttachment_FA451656": { + "//": { + "metadata": { + "path": "root/Default/Default/test:inflight base64/Handler/IamRolePolicyAttachment", + "uniqueId": "testinflightbase64_Handler_IamRolePolicyAttachment_FA451656" + } + }, + "policy_arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "role": "${aws_iam_role.testinflightbase64_Handler_IamRole_49F68A60.name}" + } + }, + "aws_lambda_function": { + "testinflightbase64_Handler_31E9772F": { + "//": { + "metadata": { + "path": "root/Default/Default/test:inflight base64/Handler/Default", + "uniqueId": "testinflightbase64_Handler_31E9772F" + } + }, + "environment": { + "variables": { + "WING_FUNCTION_NAME": "Handler-c853d8cf", + "WING_TARGET": "tf-aws" + } + }, + "function_name": "Handler-c853d8cf", + "handler": "index.handler", + "publish": true, + "role": "${aws_iam_role.testinflightbase64_Handler_IamRole_49F68A60.arn}", + "runtime": "nodejs18.x", + "s3_bucket": "${aws_s3_bucket.Code.bucket}", + "s3_key": "${aws_s3_object.testinflightbase64_Handler_S3Object_C9A792F2.key}", + "timeout": 30, + "vpc_config": { + "security_group_ids": [], + "subnet_ids": [] + } + } + }, + "aws_s3_bucket": { + "Code": { + "//": { + "metadata": { + "path": "root/Default/Code", + "uniqueId": "Code" + } + }, + "bucket_prefix": "code-c84a50b1-" + } + }, + "aws_s3_object": { + "testinflightbase64_Handler_S3Object_C9A792F2": { + "//": { + "metadata": { + "path": "root/Default/Default/test:inflight base64/Handler/S3Object", + "uniqueId": "testinflightbase64_Handler_S3Object_C9A792F2" + } + }, + "bucket": "${aws_s3_bucket.Code.bucket}", + "key": "", + "source": "" + } + } + } +} +``` + +## preflight.js +```js +const $stdlib = require('@winglang/sdk'); +const $outdir = process.env.WING_SYNTH_DIR ?? "."; +const std = $stdlib.std; +const $wing_is_test = process.env.WING_IS_TEST === "true"; +const util = require('@winglang/sdk').util; +class $Root extends $stdlib.std.Resource { + constructor(scope, id) { + super(scope, id); + class $Closure1 extends $stdlib.std.Resource { + constructor(scope, id, ) { + super(scope, id); + this.display.hidden = true; + this._addInflightOps("handle", "$inflight_init"); + } + static _toInflightType(context) { + return $stdlib.core.NodeJsCode.fromInline(` + require("./inflight.$Closure1.js")({ + $util_Util: ${context._lift(util.Util)}, + }) + `); + } + _toInflight() { + return $stdlib.core.NodeJsCode.fromInline(` + (await (async () => { + const $Closure1Client = ${$Closure1._toInflightType(this).text}; + const client = new $Closure1Client({ + }); + if (client.$inflight_init) { await client.$inflight_init(); } + return client; + })()) + `); + } + } + const string = "https://www.winglang.io/docs"; + const base64Encode = (util.Util.base64Encode(string)); + const base64urlEncode = (util.Util.base64Encode(string,true)); + const base64Decode = (util.Util.base64Decode("aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw==")); + const base64urlDecode = (util.Util.base64Decode("aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw",true)); + {((cond) => {if (!cond) throw new Error("assertion failed: base64Encode == \"aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw==\"")})((base64Encode === "aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw=="))}; + {((cond) => {if (!cond) throw new Error("assertion failed: base64urlEncode == \"aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw\"")})((base64urlEncode === "aHR0cHM6Ly93d3cud2luZ2xhbmcuaW8vZG9jcw"))}; + {((cond) => {if (!cond) throw new Error("assertion failed: base64Decode == string")})((base64Decode === string))}; + {((cond) => {if (!cond) throw new Error("assertion failed: base64urlDecode == string")})((base64urlDecode === string))}; + this.node.root.new("@winglang/sdk.std.Test",std.Test,this,"test:inflight base64",new $Closure1(this,"$Closure1")); + } +} +const $App = $stdlib.core.App.for(process.env.WING_TARGET); +new $App({ outdir: $outdir, name: "base64", rootConstruct: $Root, plugins: $plugins, isTestEnvironment: $wing_is_test }).synth(); + +``` + diff --git a/tools/hangar/__snapshots__/test_corpus/sdk_tests/util/base64.w_test_sim.md b/tools/hangar/__snapshots__/test_corpus/sdk_tests/util/base64.w_test_sim.md new file mode 100644 index 00000000000..bd9c04529a4 --- /dev/null +++ b/tools/hangar/__snapshots__/test_corpus/sdk_tests/util/base64.w_test_sim.md @@ -0,0 +1,12 @@ +# [base64.w](../../../../../../examples/tests/sdk_tests/util/base64.w) | test | sim + +## stdout.log +```log +pass ─ base64.wsim » root/env0/test:inflight base64 + + +Tests 1 passed (1) +Test Files 1 passed (1) +Duration +``` +