From 75ebc83d76ac3da2f8b065adea514165c576d86f Mon Sep 17 00:00:00 2001 From: Sebastian Korfmann Date: Thu, 7 Sep 2023 21:22:18 +0200 Subject: [PATCH 1/6] Use wing native cors implementation from https://github.com/winglang/wing/pull/2904 --- examples/static-website/main.w | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/examples/static-website/main.w b/examples/static-website/main.w index ba5777e..88e8b4b 100644 --- a/examples/static-website/main.w +++ b/examples/static-website/main.w @@ -6,22 +6,17 @@ let website = new cloud.Website( path: "./static", ); -let api = new cloud.Api(); +let api = new cloud.Api({ + cors: true, + corsOptions: { + allowHeaders: ["*"], + allowMethods: [http.HttpMethod.POST], + }, +}); website.addJson("config.json", { api: api.url }); let counter = new cloud.Counter() as "website-counter"; -let corsHandler = inflight(req) => { - return { - headers: { - "Access-Control-Allow-Headers" => "*", - "Access-Control-Allow-Origin" => "*", - "Access-Control-Allow-Methods" => "OPTIONS,POST", - }, - status: 204 - }; -}; -api.options("/hello-static", corsHandler); api.post("/hello-static", inflight (request) => { return { status: 200, @@ -46,3 +41,18 @@ test "renders the index page" { test "api returns the correct response" { invokeAndAssert(http.post("${api.url}/hello-static"), "Hello 0"); } + +test "api handles cors" { + let response = http.fetch("${api.url}/hello-static", { + method: http.HttpMethod.OPTIONS, + headers: { + "Origin" => "https://example.com", + "hx-target" => "hello", + }, + }); + assert(response.status == 204); + log("headers: ${Json.stringify(response.headers)}"); + assert(response.headers.get("access-control-allow-headers") == "*"); + assert(response.headers.get("access-control-allow-origin") == "*"); + assert(response.headers.get("access-control-allow-methods") == "POST"); +} \ No newline at end of file From 0855c3cf9470ff365ee60bc6ab64d92ed5c43e43 Mon Sep 17 00:00:00 2001 From: Sebastian Korfmann Date: Thu, 7 Sep 2023 22:10:27 +0200 Subject: [PATCH 2/6] Needs to be static due to recent changes in wing --- examples/todo-app/main.w | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/todo-app/main.w b/examples/todo-app/main.w index 68d3ddb..591cb24 100644 --- a/examples/todo-app/main.w +++ b/examples/todo-app/main.w @@ -168,7 +168,7 @@ class TaskApi { api: cloud.Api; taskStorage: ITaskStorage; - extern "./tasklist_helper.js" inflight createRegex(s: str): IMyRegExp; + extern "./tasklist_helper.js" static inflight createRegex(s: str): IMyRegExp; init() { this.api = new cloud.Api(); @@ -285,7 +285,7 @@ class TaskApi { this.api.get("/tasks", inflight (req): cloud.ApiResponse => { let search = req.query.get("search"); - let results = this.taskStorage.find(this.createRegex(search)); + let results = this.taskStorage.find(TaskApi.createRegex(search)); return cloud.ApiResponse { headers: getAPICORSHeadersMap, status: 200, From 0a6ae87c7f92006f72ae19453f1da14f6abf5cbd Mon Sep 17 00:00:00 2001 From: Sebastian Korfmann Date: Thu, 7 Sep 2023 22:10:58 +0200 Subject: [PATCH 3/6] Use default wing cors implementation This is not as granular as the original implementation, but probably good enough until there's path based cors handling in wing. See https://github.com/winglang/wing/pull/2904 --- examples/todo-app/main.w | 131 +++++++++++++-------------------------- 1 file changed, 43 insertions(+), 88 deletions(-) diff --git a/examples/todo-app/main.w b/examples/todo-app/main.w index 591cb24..17584e8 100644 --- a/examples/todo-app/main.w +++ b/examples/todo-app/main.w @@ -68,37 +68,6 @@ let convertTaskArrayToJson = inflight (taskArray: Array): Json => { return jsonArray; }; -// Constants - bolierplate code to enable CORS - see https://github.com/winglang/wing/issues/2289 -let optionsTasksRouteAPICORSHeadersMap = { - "Access-Control-Allow-Headers" => "Content-Type", - "Access-Control-Allow-Origin" => "*", - "Access-Control-Allow-Methods" => "OPTIONS,POST,GET" -}; -let optionsTasksIdRouteAPICORSHeadersMap = { - "Access-Control-Allow-Headers" => "Content-Type", - "Access-Control-Allow-Origin" => "*", - "Access-Control-Allow-Methods" => "OPTIONS,GET,PUT,DELETE" -}; -let postAPICORSHeadersMap = { - "Access-Control-Allow-Headers" => "Content-Type", - "Access-Control-Allow-Origin" => "*", - "Access-Control-Allow-Methods" => "OPTIONS,POST" -}; -let putAPICORSHeadersMap = { - "Access-Control-Allow-Headers" => "Content-Type", - "Access-Control-Allow-Origin" => "*", - "Access-Control-Allow-Methods" => "PUT" -}; -let getAPICORSHeadersMap = { - "Access-Control-Allow-Headers" => "Content-Type", - "Access-Control-Allow-Origin" => "*", - "Access-Control-Allow-Methods" => "OPTIONS,GET" -}; -let deleteAPICORSHeadersMap = { - "Access-Control-Allow-Headers" => "Content-Type", - "Access-Control-Allow-Origin" => "*", - "Access-Control-Allow-Methods" => "OPTIONS,DELETE" -}; /******************************************************************** * } end of boilerplate code ********************************************************************/ @@ -125,7 +94,7 @@ class TaskStorage impl ITaskStorage { status: "PENDING" }; this._add(id, taskJson); - log("adding task ${id} with data: ${taskJson}"); + log("adding task ${id} with data: ${taskJson}"); return id; } @@ -149,8 +118,8 @@ class TaskStorage impl ITaskStorage { } } - inflight find(r: IMyRegExp): Array { - let result = MutArray[]; + inflight find(r: IMyRegExp): Array { + let result = MutArray[]; let ids = this.db.smembers("tasks"); for id in ids { if let taskJsonStr = this.db.get(id) { @@ -170,28 +139,15 @@ class TaskApi { extern "./tasklist_helper.js" static inflight createRegex(s: str): IMyRegExp; - init() { - this.api = new cloud.Api(); - this.taskStorage = new TaskStorage(); - - this.api.options("/tasks", inflight(req): cloud.ApiResponse => { - return cloud.ApiResponse { - headers: optionsTasksRouteAPICORSHeadersMap, - status: 204 - }; - }); - this.api.options("/tasks/{id}", inflight(req): cloud.ApiResponse => { - return cloud.ApiResponse { - headers: optionsTasksIdRouteAPICORSHeadersMap, - status: 204 - }; - }); - + init(storage: ITaskStorage) { + this.api = new cloud.Api(cors: true); + this.taskStorage = storage; + // API endpoints this.api.post("/tasks", inflight (req): cloud.ApiResponse => { if let body = req.body { let var description = Json.parse(body).get("description").asStr(); - // Easter Egg - if you add a task with the single word "random" as the description, + // Easter Egg - if you add a task with the single word "random" as the description, // the system will fetch a random task from the internet if description == "random" { let response = http.get("https://www.boredapi.com/api/activity"); @@ -199,21 +155,19 @@ class TaskApi { let body = Json.parse(responseBody); description = str.fromJson(body.get("activity")); } - } + } let id = this.taskStorage.add(description); - return cloud.ApiResponse { - headers: postAPICORSHeadersMap, - status:201, + return cloud.ApiResponse { + status:201, body: id }; } else { - return cloud.ApiResponse { - headers: postAPICORSHeadersMap, + return cloud.ApiResponse { status: 400, }; } }); - + this.api.put("/tasks/{id}", inflight (req): cloud.ApiResponse => { if let body = req.body { let id = req.vars.get("id"); @@ -224,22 +178,19 @@ class TaskApi { } try { if let taskJson = this.taskStorage.get(id) { - return cloud.ApiResponse { - headers: putAPICORSHeadersMap, - status:200, + return cloud.ApiResponse { + status:200, body: "${Json taskJson}" }; } } catch { - return cloud.ApiResponse { - headers: putAPICORSHeadersMap, - status: 400 + return cloud.ApiResponse { + status: 400 }; } } else { - return cloud.ApiResponse { - headers: putAPICORSHeadersMap, - status: 400 + return cloud.ApiResponse { + status: 400 }; } }); @@ -248,37 +199,32 @@ class TaskApi { let id = req.vars.get("id"); try { if let taskJson = this.taskStorage.get(id) { - return cloud.ApiResponse { - headers: getAPICORSHeadersMap, - status:200, + return cloud.ApiResponse { + status:200, body: "${Json taskJson}" }; } else { - return cloud.ApiResponse { - headers: getAPICORSHeadersMap, - status:404, + return cloud.ApiResponse { + status:404, }; } } catch { - return cloud.ApiResponse { - headers: getAPICORSHeadersMap, - status: 400 + return cloud.ApiResponse { + status: 400 }; } }); - + this.api.delete("/tasks/{id}", inflight (req): cloud.ApiResponse => { let id = req.vars.get("id"); try { this.taskStorage.remove(id); - return cloud.ApiResponse { - headers: deleteAPICORSHeadersMap, + return cloud.ApiResponse { status: 204 }; } catch { - return cloud.ApiResponse { - headers: deleteAPICORSHeadersMap, - status: 400 + return cloud.ApiResponse { + status: 400 }; } }); @@ -286,13 +232,22 @@ class TaskApi { this.api.get("/tasks", inflight (req): cloud.ApiResponse => { let search = req.query.get("search"); let results = this.taskStorage.find(TaskApi.createRegex(search)); - return cloud.ApiResponse { - headers: getAPICORSHeadersMap, - status: 200, - body: "${convertTaskArrayToJson(results)}" + return cloud.ApiResponse { + status: 200, + body: "${convertTaskArrayToJson(results)}" }; }); } } -let taskApi = new TaskApi(); +let storage = new TaskStorage(); +let taskApi = new TaskApi(storage); + +test "list tasks" { + storage.add("task 1"); + let url = taskApi.api.url; + let response = http.get("${url}/tasks"); + assert(response.status == 200); + assert(response.body == Json.stringify(Json{"0":{"id":"0","description":"task 1","status":"PENDING"}})); + assert(response.headers.get("access-control-allow-origin") == "*"); +} From 2fe392a655dd7d2844cdfc0c602d8a7345cef14a Mon Sep 17 00:00:00 2001 From: Sebastian Korfmann Date: Sat, 9 Sep 2023 14:07:15 +0200 Subject: [PATCH 4/6] Adapt test to changes --- examples/todo-app/main.w | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/todo-app/main.w b/examples/todo-app/main.w index 45e08c3..35fe7f6 100644 --- a/examples/todo-app/main.w +++ b/examples/todo-app/main.w @@ -246,7 +246,8 @@ test "list tasks" { storage.add("task 1"); let url = taskApi.api.url; let response = http.get("${url}/tasks"); + log("response: ${Json.stringify(response.body)}"); assert(response.status == 200); - assert(response.body == Json.stringify(Json{"0":{"id":"0","description":"task 1","status":"PENDING"}})); + assert(response.body == Json.stringify(Json[{"id":"0","description":"task 1","status":"PENDING"}])); assert(response.headers.get("access-control-allow-origin") == "*"); } From 2515f4182e6b20e01663f8a7044c250801c21973 Mon Sep 17 00:00:00 2001 From: Sebastian Korfmann Date: Sat, 9 Sep 2023 14:49:02 +0200 Subject: [PATCH 5/6] Increase timeout - see https://github.com/winglang/wing/issues/4125 --- examples/hello-wing/main.w | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/hello-wing/main.w b/examples/hello-wing/main.w index a6f5fff..4a1948c 100644 --- a/examples/hello-wing/main.w +++ b/examples/hello-wing/main.w @@ -6,7 +6,7 @@ let queue = new cloud.Queue(); queue.setConsumer(inflight (message) => { bucket.put("wing.txt", "Hello, ${message}"); -}, timeout: 3s); +}, timeout: 30s); test "Hello, world!" { queue.push("world!"); From f5d59a8200f318b46c20725dffc6d163d23a5bd4 Mon Sep 17 00:00:00 2001 From: Sebastian Korfmann Date: Sat, 9 Sep 2023 16:26:19 +0200 Subject: [PATCH 6/6] Redis test fails in eu-east-1 - see https://github.com/winglang/wing/issues/4126 --- examples/todo-app/skip.ci.aws | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 examples/todo-app/skip.ci.aws diff --git a/examples/todo-app/skip.ci.aws b/examples/todo-app/skip.ci.aws new file mode 100644 index 0000000..e69de29