Skip to content

Commit

Permalink
feat(sdk)!: change cloud.Function event type from str to Json (#…
Browse files Browse the repository at this point in the history
…6219)

Closes #6216

BREAKING CHANGE: The input and return types for `cloud.Function` handlers has been changed from `str?` to `Json?` to align with the event formats used by major cloud providers like AWS, GCP, and Azure.

This is a breaking change for any existing code that assumes the input event is a `str`.

To migrate:
- Update `cloud.Function` handlers to expect a `Json?` value as input instead of a `str?`
- Remove any JSON parsing logic since the input will already be parsed into a `Json` value. To convert a `x: Json?` to `str`, you can write  `x?.asStr()!`.
- Update any tests or invocations of `cloud.Function` to pass in `Json` values instead of `str`

## Checklist

- [x] Title matches [Winglang's style guide](https://www.winglang.io/contributing/start-here/pull_requests#how-are-pull-request-titles-formatted)
- [x] Description explains motivation and solution
- [ ] Tests added (always)
- [ ] Docs updated (only required for features)
- [ ] Added `pr/e2e-full` label if this feature requires end-to-end testing

*By submitting this pull request, I confirm that my contribution is made under the terms of the [Wing Cloud Contribution License](https://github.com/winglang/wing/blob/main/CONTRIBUTION_LICENSE.md)*.
  • Loading branch information
garysassano authored Jul 17, 2024
1 parent a14c454 commit 98de4ad
Show file tree
Hide file tree
Showing 58 changed files with 374 additions and 521 deletions.
4 changes: 2 additions & 2 deletions apps/wing-console/console/server/src/router/function.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { z } from "zod";

import { createProcedure, createRouter } from "../utils/createRouter.js";
import type { IFunctionClient } from "../wingsdk.js";
import type { IFunctionClient, Json } from "../wingsdk.js";

export type ResponseEnvelope =
| {
success: true;
response: string | undefined;
response: Json | undefined;
}
| {
success: false;
Expand Down
8 changes: 4 additions & 4 deletions docs/api/04-standard-library/aws/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -347,14 +347,14 @@ new aws.FunctionRef(functionArn: str);
##### `invoke` <a name="invoke" id="@winglang/sdk.cloud.IFunctionClient.invoke"></a>

```wing
inflight invoke(payload?: str): str?
inflight invoke(payload?: Json): Json?
```

Invokes the function with a payload and waits for the result.

###### `payload`<sup>Optional</sup> <a name="payload" id="@winglang/sdk.cloud.IFunctionClient.invoke.parameter.payload"></a>

- *Type:* str
- *Type:* <a href="#@winglang/sdk.std.Json">Json</a>

payload to pass to the function.

Expand All @@ -365,14 +365,14 @@ If not defined, an empty string will be passed.
##### `invokeAsync` <a name="invokeAsync" id="@winglang/sdk.cloud.IFunctionClient.invokeAsync"></a>

```wing
inflight invokeAsync(payload?: str): void
inflight invokeAsync(payload?: Json): void
```

Kicks off the execution of the function with a payload and returns immediately while the function is running.

###### `payload`<sup>Optional</sup> <a name="payload" id="@winglang/sdk.cloud.IFunctionClient.invokeAsync.parameter.payload"></a>

- *Type:* str
- *Type:* <a href="#@winglang/sdk.std.Json">Json</a>

payload to pass to the function.

Expand Down
16 changes: 8 additions & 8 deletions docs/api/04-standard-library/cloud/function.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ bring cloud;
bring util;

// defining a cloud.Function resource
let countWords = new cloud.Function(inflight (s: str?): str => {
return "{s?.split(" ")?.length ?? 0}";
let countWords = new cloud.Function(inflight (payload: Json?): Json => {
return "{payload?.tryAsStr()?.split(" ")?.length ?? 0}";
}) as "countWords";

let longTask = new cloud.Function(inflight () => {
Expand Down Expand Up @@ -282,14 +282,14 @@ Add an environment variable to the function.
##### `invoke` <a name="invoke" id="@winglang/sdk.cloud.IFunctionClient.invoke"></a>
```wing
inflight invoke(payload?: str): str?
inflight invoke(payload?: Json): Json?
```
Invokes the function with a payload and waits for the result.
###### `payload`<sup>Optional</sup> <a name="payload" id="@winglang/sdk.cloud.IFunctionClient.invoke.parameter.payload"></a>
- *Type:* str
- *Type:* <a href="#@winglang/sdk.std.Json">Json</a>
payload to pass to the function.
Expand All @@ -300,14 +300,14 @@ If not defined, an empty string will be passed.
##### `invokeAsync` <a name="invokeAsync" id="@winglang/sdk.cloud.IFunctionClient.invokeAsync"></a>
```wing
inflight invokeAsync(payload?: str): void
inflight invokeAsync(payload?: Json): void
```
Kicks off the execution of the function with a payload and returns immediately while the function is running.
###### `payload`<sup>Optional</sup> <a name="payload" id="@winglang/sdk.cloud.IFunctionClient.invokeAsync.parameter.payload"></a>
- *Type:* str
- *Type:* <a href="#@winglang/sdk.std.Json">Json</a>
payload to pass to the function.
Expand Down Expand Up @@ -529,14 +529,14 @@ Inflight client for `IFunctionHandler`.
##### `handle` <a name="handle" id="@winglang/sdk.cloud.IFunctionHandlerClient.handle"></a>
```wing
inflight handle(event?: str): str?
inflight handle(event?: Json): Json?
```
Entrypoint function that will be called when the cloud function is invoked.
###### `event`<sup>Optional</sup> <a name="event" id="@winglang/sdk.cloud.IFunctionHandlerClient.handle.parameter.event"></a>
- *Type:* str
- *Type:* <a href="#@winglang/sdk.std.Json">Json</a>
---
Expand Down
4 changes: 2 additions & 2 deletions docs/api/04-standard-library/cloud/topic.md
Original file line number Diff line number Diff line change
Expand Up @@ -493,12 +493,12 @@ Inflight client for `ITopicOnMessageHandler`.
##### `handle` <a name="handle" id="@winglang/sdk.cloud.ITopicOnMessageHandlerClient.handle"></a>

```wing
inflight handle(event: str): void
inflight handle(message: str): void
```

Function that will be called when a message is received from the topic.

###### `event`<sup>Required</sup> <a name="event" id="@winglang/sdk.cloud.ITopicOnMessageHandlerClient.handle.parameter.event"></a>
###### `message`<sup>Required</sup> <a name="message" id="@winglang/sdk.cloud.ITopicOnMessageHandlerClient.handle.parameter.message"></a>

- *Type:* str

Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions examples/tests/invalid/cloud_function_expects_inflight.test.w
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
bring cloud;

new cloud.Function((name: str): str => {
return "Hello {name}";
new cloud.Function((name: Json?): Json? => {
return "Hello {name?.tryAsStr() ?? "world"}!";
});
// ^ Expected type to be "inflight (any): any", but got "preflight (str): str" instead

Expand Down
2 changes: 1 addition & 1 deletion examples/tests/invalid/struct_expansion.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ let bucket1 = new cloud.Bucket(bublic: false, public: true);
let bucket2 = new cloud.Bucket(2, public: true);
//^^^^^^^^^^^^^^^ Expected between 0 and 1 arguments but got 2 when instantiating "Bucket"

let handler = inflight (event: str) => {
let handler = inflight (event: Json?) => {
bucket1.put(file: "file.txt", "data");
//^^^^^^^^^^^^^^^^ Named arguments must be after positional arguments
};
Expand Down
4 changes: 2 additions & 2 deletions examples/tests/invalid/super_call.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class ExtendedClass extends BaseClass {
return "extended inflight m1";
}
get_func(): cloud.Function {
let inflight_closure = inflight (s:str): str => {
let inflight_closure = inflight (s: Json?): Json? => {
return "this: {this.m1()}, super: {super.m1()}";
//^^ `super` calls inside inflight closures not supported yet, see: https://github.com/winglang/wing/issues/3474
};
Expand All @@ -59,4 +59,4 @@ class ExtendedClass extends BaseClass {
// let y = new ExtendedClass().get_func();
// test "inflight closure accesses super" {
// assert(y.invoke("") == "this: extended inflight m1, super: base inflight m1");
// }
// }
2 changes: 1 addition & 1 deletion examples/tests/invalid/unresolved_state.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ bring util;
let state = new sim.State();

let fn = new cloud.Function(
inflight (): str => {
inflight (): Json => {
return util.env("FOO_VALUE");
},
env: {
Expand Down
41 changes: 19 additions & 22 deletions examples/tests/sdk_tests/function/aws-function.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ bring util;

let target = util.env("WING_TARGET");

let lambda = new cloud.Function(inflight () => {}) as "aws-wing-function";

let getFunctionInfo = (f: cloud.Function): Map<str>? => {
if let lambda = aws.Function.from(f) {
return {
Expand All @@ -17,11 +15,7 @@ let getFunctionInfo = (f: cloud.Function): Map<str>? => {
return nil;
};

let functionInfo = getFunctionInfo(lambda);



let fn = new cloud.Function(inflight (msg: str?) => {
let fn = new cloud.Function(inflight (msg: Json?) => {
if msg == "error" {
throw "fake error";
}
Expand All @@ -39,36 +33,39 @@ let fn = new cloud.Function(inflight (msg: str?) => {
}

return msg;
}) as "FunctionAccessingContext";
}) as "aws-wing-function";

let fnInfo = getFunctionInfo(fn);

test "AWS Function" {
// "validates the AWS Function"
if let lambda = functionInfo {
if target == "tf-aws" {
assert(lambda.get("functionArn").contains("arn:aws:lambda:"));
assert(lambda.get("functionArn").contains(":function:"));
assert(lambda.get("functionArn").contains("aws-wing-function"));
assert(lambda.get("functionName").contains("aws-wing-function"));
} else { // If it's not a 'tf-aws' target, it's an 'awscdk'
assert(lambda.get("functionArn").contains("arn:aws:lambda:"));
assert(lambda.get("functionArn").contains(":function:"));
assert(lambda.get("functionArn").contains("awswingfunction"));
assert(lambda.get("functionName").contains("awswingfunction"));
if let info = fnInfo {
if target == "tf-aws" {
assert(info.get("functionArn").contains("arn:aws:lambda:"));
assert(info.get("functionArn").contains(":function:"));
assert(info.get("functionArn").contains("aws-wing-function"));
assert(info.get("functionName").contains("aws-wing-function"));
} elif target == "awscdk" {
assert(info.get("functionArn").contains("arn:aws:lambda:"));
assert(info.get("functionArn").contains(":function:"));
assert(info.get("functionArn").contains("awswingfunction"));
assert(info.get("functionName").contains("awswingfunction"));
} else {
expect.fail("Unexpected target " + target);
}
} else {
// If the test is not on AWS, it should not fail, so I am returning true.
assert(true);
}


// "can access lambda context"
let result = fn.invoke("hello");
expect.equal(result, "hello");

let result2 = fn.invoke("hello2");
expect.equal(result2, "hello2");

let result3 = fn.invoke();
expect.equal(result3, nil);

let var msg = "";
try {
fn.invoke("error");
Expand Down
2 changes: 1 addition & 1 deletion examples/tests/sdk_tests/function/env.test.w
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
bring cloud;
bring util;

let f1 = new cloud.Function(inflight (): str => {
let f1 = new cloud.Function(inflight () => {
assert(util.env("FOO1") == "bar");
assert(util.env("FOO2") == "baz");
return "ok";
Expand Down
6 changes: 2 additions & 4 deletions examples/tests/sdk_tests/function/invoke.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@ bring expect;
let payload = "hello";
log("log preflight");

let f = new cloud.Function(inflight (input): str => {
let f = new cloud.Function(inflight (input) => {
log("log inside function\ncontains 2 lines");
let target = util.tryEnv("WING_TARGET");
assert(target != nil); // make sure WING_TARGET is defined in all environments

return "{input ?? "nil"}-response";
return "{input?.tryAsStr() ?? "nil"}-response";
});



let f2 = new cloud.Function(inflight (e) => {
expect.equal(e, nil);
log("no event, no return!");
Expand Down
20 changes: 9 additions & 11 deletions examples/tests/valid/doubler.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ class Doubler {
new(func: cloud.IFunctionHandler) {
this.func = func;
}
inflight invoke(message: str): str {
inflight invoke(message: Json): Json {
let res1 = this.func.handle(message) ?? "";
let res2 = this.func.handle(res1) ?? "";
return res2;
}
}

let fn = new Doubler(inflight (m: str?): str => {
let fn = new Doubler(inflight (m: Json?): Json => {
return "Hello {m ?? "nil"}!";
});

Expand All @@ -23,23 +23,21 @@ let fn = new Doubler(inflight (m: str?): str => {
// check that we can capture an inflight function and call it inflight

class Doubler2 {
// TODO: make into a static method - see https://github.com/winglang/wing/issues/2583
pub makeFunc(handler: inflight (num): num): cloud.Function {
return new cloud.Function(inflight (x: str?): str => {
let xStr = num.fromStr(x ?? "NaN");
pub static makeFunc(handler: inflight (num): num): cloud.Function {
return new cloud.Function(inflight (x: Json?): Json => {
let xStr = num.fromJson(x ?? "NaN");
let y = handler(xStr);
let z = handler(y);
return Json.stringify(Json z);
return z;
});
}
}

let doubler2 = new Doubler2();
let f = doubler2.makeFunc(inflight (x: num): num => {
let f = Doubler2.makeFunc(inflight (x: num): num => {
return x * 2;
});

test "f(2) == 8" {
let result = f.invoke("2");
assert(result == "8");
let result = f.invoke(2);
assert(result == 8);
}
2 changes: 1 addition & 1 deletion examples/tests/valid/inflight_ts/.example2.inflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,5 @@ export interface IInflight$Inflight extends IHostedLiftable$Inflight {
/** A resource with an inflight "handle" method that can be used to create a `cloud.Function`. */
export interface IFunctionHandler$Inflight extends IInflight$Inflight {
/** Entrypoint function that will be called when the cloud function is invoked. */
readonly handle: (event?: (string) | undefined) => Promise<string | void>;
readonly handle: (event?: (Readonly<any>) | undefined) => Promise<Readonly<any> | void>;
}
4 changes: 2 additions & 2 deletions examples/tests/valid/inflights_calling_inflights.test.w
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ let storeInBucket = inflight (event: str, file: str) => {
globalBucket.put(file, event);
};

let handler1 = inflight (event: str?) => {
if let event = event {
let handler1 = inflight (event: Json?) => {
if let event = event?.tryAsStr() {
storeInBucket(event, "file1");
}
};
Expand Down
13 changes: 0 additions & 13 deletions examples/tests/valid/resource_as_inflight_literal.test.w

This file was deleted.

6 changes: 3 additions & 3 deletions examples/ts-fixture/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { inflight, lift, main, cloud } from "@wingcloud/framework";
import { inflight, lift, main, cloud, asJson } from "@wingcloud/framework";
import * as winglib from "@winglibs/testfixture";
import * as assert from "node:assert";
import { deepStrictEqual } from "node:assert";
Expand All @@ -15,7 +15,7 @@ main((app, test) => {
assert.strictEqual("", "");
await bucket.put("hi", "stuff");
console.log("hi from function");
return "hi";
return asJson("hi");
})
);

Expand All @@ -42,7 +42,7 @@ main((app, test) => {

console.log("hi from test");
await inf();
await f.invoke("me");
await f.invoke(asJson("me"));
await store.set("wing");
})
);
Expand Down
1 change: 1 addition & 0 deletions libs/@wingcloud/framework/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export { Construct } from "@winglang/sdk/lib/core/types";
// typescript workflow primitives
export { inflight, lift } from "@winglang/sdk/lib/core";
export { main } from "./main";
export { asJson } from "./json";

// used internally by wing compiler
export * as internal from "./internal";
Loading

0 comments on commit 98de4ad

Please sign in to comment.