From 02c653080395af3490c21a9a76ed004bf8486018 Mon Sep 17 00:00:00 2001 From: Thomas van Dam Date: Fri, 30 Aug 2024 23:17:09 +0200 Subject: [PATCH] fix(as-sdk): correctly serialise request bodies Closes: #72 --- .../as-sdk-integration-tests/assembly/http.ts | 27 ++++++++++ .../assembly/index.ts | 4 +- .../as-sdk-integration-tests/src/http.test.ts | 31 ++++++++++- libs/as-sdk/assembly/http.ts | 54 ++++++++----------- libs/as-sdk/assembly/json-utils.ts | 12 +++-- libs/as-sdk/assembly/proxy-http.ts | 37 +++++++------ 6 files changed, 111 insertions(+), 54 deletions(-) diff --git a/libs/as-sdk-integration-tests/assembly/http.ts b/libs/as-sdk-integration-tests/assembly/http.ts index 76e293e..1f305b0 100644 --- a/libs/as-sdk-integration-tests/assembly/http.ts +++ b/libs/as-sdk-integration-tests/assembly/http.ts @@ -35,3 +35,30 @@ export class TestHttpSuccess extends OracleProgram { Process.error(Bytes.fromString('Something went wrong..'), 20); } } + +export class TestPostHttpSuccess extends OracleProgram { + execution(): void { + const headers = new Map(); + headers.set('content-type', 'application/json'); + + const response = httpFetch('https://jsonplaceholder.typicode.com/posts', { + body: Bytes.fromString( + `{"title":"Test SDK","body":"Don't forget to test some integrations."}` + ), + method: 'POST', + headers, + }); + const fulfilled = response.fulfilled; + const rejected = response.rejected; + + if (fulfilled !== null) { + Process.success(fulfilled.bytes); + } + + if (rejected !== null) { + Process.error(rejected.bytes); + } + + Process.error(Bytes.fromString('Something went wrong..'), 20); + } +} diff --git a/libs/as-sdk-integration-tests/assembly/index.ts b/libs/as-sdk-integration-tests/assembly/index.ts index 6c13ced..682642b 100644 --- a/libs/as-sdk-integration-tests/assembly/index.ts +++ b/libs/as-sdk-integration-tests/assembly/index.ts @@ -1,5 +1,5 @@ import { Process, Bytes } from '../../as-sdk/assembly/index'; -import { TestHttpRejection, TestHttpSuccess } from './http'; +import { TestHttpRejection, TestHttpSuccess, TestPostHttpSuccess} from './http'; import { testProxyHttpFetch } from './proxy-http'; import { TestTallyVmReveals, TestTallyVmRevealsFiltered } from './tally'; import { TestTallyVmHttp, TestTallyVmMode } from './vm-tests'; @@ -10,6 +10,8 @@ if (args === 'testHttpRejection') { new TestHttpRejection().run(); } else if (args === 'testHttpSuccess') { new TestHttpSuccess().run(); +} else if (args === 'testPostHttpSuccess') { + new TestPostHttpSuccess().run(); } else if (args === 'testTallyVmMode') { new TestTallyVmMode().run(); } else if (args === 'testTallyVmHttp') { diff --git a/libs/as-sdk-integration-tests/src/http.test.ts b/libs/as-sdk-integration-tests/src/http.test.ts index 23395c9..8ef8497 100644 --- a/libs/as-sdk-integration-tests/src/http.test.ts +++ b/libs/as-sdk-integration-tests/src/http.test.ts @@ -48,7 +48,10 @@ describe('Http', () => { const wasmBinary = await readFile( 'dist/libs/as-sdk-integration-tests/debug.wasm' ); - const result = await executeDrWasm(wasmBinary, Buffer.from('testHttpSuccess')); + const result = await executeDrWasm( + wasmBinary, + Buffer.from('testHttpSuccess') + ); expect(result.exitCode).toBe(0); expect(result.result).toEqual( @@ -67,6 +70,32 @@ describe('Http', () => { ); }); + // Possibly flakey as it relies on internet connectivity and an external service + it.only('Test SDK HTTP POST Success', async () => { + const wasmBinary = await readFile( + 'dist/libs/as-sdk-integration-tests/debug.wasm' + ); + const result = await executeDrWasm( + wasmBinary, + Buffer.from('testPostHttpSuccess') + ); + + expect(result.exitCode).toBe(0); + expect(result.result).toEqual( + new TextEncoder().encode( + JSON.stringify( + { + title: 'Test SDK', + body: "Don't forget to test some integrations.", + id: 101, + }, + undefined, + 2 + ) + ) + ); + }); + it('should exit when an invalid WASM binary is given', async () => { const result = await executeDrWasm( Buffer.from(new Uint8Array([0, 97, 115, 109])), diff --git a/libs/as-sdk/assembly/http.ts b/libs/as-sdk/assembly/http.ts index 5b8eb47..1e85364 100644 --- a/libs/as-sdk/assembly/http.ts +++ b/libs/as-sdk/assembly/http.ts @@ -1,11 +1,12 @@ import { JSON } from 'json-as/assembly'; import { call_result_write, http_fetch } from './bindings/seda_v1'; -import { jsonArrToUint8Array } from './json-utils'; +import { jsonArrToUint8Array, bytesToJsonArray } from './json-utils'; import { PromiseStatus, FromBuffer } from './promise'; import { Bytes } from './bytes'; +import { Console } from './console'; @json -export class InnerHttpResponse { +export class SerializableHttpResponse { bytes!: u8[]; content_length!: i64; status!: i64; @@ -13,16 +14,6 @@ export class InnerHttpResponse { headers!: Map; } -@json -export class HttpResponseDisplay { - type: string = "HttpResponseDisplay"; - bytes!: Bytes; - contentLength!: i64; - url!: string; - status!: i64; - headers!: Map; -} - /** * Response of an httpFetch call */ @@ -49,7 +40,7 @@ export class HttpResponse implements FromBuffer { */ public headers: Map = new Map(); - static fromInner(value: InnerHttpResponse): HttpResponse { + static fromSerializable(value: SerializableHttpResponse): HttpResponse { const response = new HttpResponse(); if (value.bytes) { @@ -86,21 +77,9 @@ export class HttpResponse implements FromBuffer { } fromBuffer(buffer: Uint8Array): HttpResponse { - const value = JSON.parse(String.UTF8.decode(buffer.buffer)); + const value = JSON.parse(String.UTF8.decode(buffer.buffer)); - return HttpResponse.fromInner(value); - } - - toString(): string { - const response = new HttpResponseDisplay(); - - response.bytes = this.bytes; - response.contentLength = this.contentLength; - response.headers = this.headers; - response.status = this.status; - response.url = this.url; - - return JSON.stringify(response); + return HttpResponse.fromSerializable(value); } } @@ -142,13 +121,24 @@ export class HttpFetchOptions { } @json -export class HttpFetch { +class SerializableHttpFetchOptions { + method!: string; + headers!: Map; + body: u8[] = []; +} + +@json +export class HttpFetchAction { url: string; - options: HttpFetchOptions; + options: SerializableHttpFetchOptions; - constructor(url: string, options: HttpFetchOptions = new HttpFetchOptions()) { + constructor(url: string, options: HttpFetchOptions) { this.url = url; - this.options = options; + this.options = new SerializableHttpFetchOptions(); + + this.options.method = options.method; + this.options.headers = options.headers; + this.options.body = bytesToJsonArray(options.body) } } @@ -175,7 +165,7 @@ export function httpFetch( url: string, options: HttpFetchOptions = new HttpFetchOptions() ): PromiseStatus { - const action = new HttpFetch(url, options); + const action = new HttpFetchAction(url, options); const actionStr = JSON.stringify(action); const buffer = String.UTF8.encode(actionStr); diff --git a/libs/as-sdk/assembly/json-utils.ts b/libs/as-sdk/assembly/json-utils.ts index 6e1c23f..03e2c89 100644 --- a/libs/as-sdk/assembly/json-utils.ts +++ b/libs/as-sdk/assembly/json-utils.ts @@ -1,3 +1,5 @@ +import { Bytes } from "./bytes"; + export function jsonArrToUint8Array(array: u8[]): Uint8Array { const result = new Uint8Array(array.length); result.set(array); @@ -5,11 +7,15 @@ export function jsonArrToUint8Array(array: u8[]): Uint8Array { return result; } -export function uint8arrayToJsonArray(input: Uint8Array): u8[] { +export function bytesToJsonArray(input: Bytes | null): u8[] { const result: u8[] = []; - for (let i = 0; i < input.length; i++) { - result.push(input[i]); + if (input === null) { + return result; + } + + for (let i = 0; i < input.value.length; i++) { + result.push(input.value[i]); } return result; diff --git a/libs/as-sdk/assembly/proxy-http.ts b/libs/as-sdk/assembly/proxy-http.ts index 149dec7..7204f12 100644 --- a/libs/as-sdk/assembly/proxy-http.ts +++ b/libs/as-sdk/assembly/proxy-http.ts @@ -1,26 +1,29 @@ import { JSON } from 'json-as/assembly'; -import { HttpFetchOptions, HttpFetch, HttpResponse } from "./http"; +import { HttpFetchOptions, HttpFetchAction, HttpResponse } from './http'; import { call_result_write, proxy_http_fetch } from './bindings/seda_v1'; import { PromiseStatus } from './promise'; -export function proxyHttpFetch(url: string, options: HttpFetchOptions = new HttpFetchOptions()): PromiseStatus { - const action = new HttpFetch(url, options); - const actionStr = JSON.stringify(action); +export function proxyHttpFetch( + url: string, + options: HttpFetchOptions = new HttpFetchOptions() +): PromiseStatus { + const action = new HttpFetchAction(url, options); + const actionStr = JSON.stringify(action); - const buffer = String.UTF8.encode(actionStr); - const utf8ptr = changetype(buffer); + const buffer = String.UTF8.encode(actionStr); + const utf8ptr = changetype(buffer); - const responseLength = proxy_http_fetch(utf8ptr, buffer.byteLength); - const responseBuffer = new ArrayBuffer(responseLength); - const responseBufferPtr = changetype(responseBuffer); + const responseLength = proxy_http_fetch(utf8ptr, buffer.byteLength); + const responseBuffer = new ArrayBuffer(responseLength); + const responseBufferPtr = changetype(responseBuffer); - call_result_write(responseBufferPtr, responseLength); + call_result_write(responseBufferPtr, responseLength); - const response = String.UTF8.decode(responseBuffer); + const response = String.UTF8.decode(responseBuffer); - return PromiseStatus.fromStr( - response, - new HttpResponse(), - new HttpResponse(), - ); -} \ No newline at end of file + return PromiseStatus.fromStr( + response, + new HttpResponse(), + new HttpResponse() + ); +}