From 2a98d139caa39c75b6167c1f071863d21c1e170b Mon Sep 17 00:00:00 2001 From: Dzming Li Date: Fri, 10 Jul 2026 17:44:51 +0800 Subject: [PATCH] refactor(io): replace Data with byte-oriented APIs --- examples/http_file_server/server_wbtest.mbt | 2 +- examples/line_processing/main.mbt | 7 +- examples/tcp_ping_pong/main.mbt | 4 +- examples/websocket_echo_server/main.mbt | 4 +- .../websocket_echo_server/server_wbtest.mbt | 4 +- src/fs/README.mbt.md | 18 +- src/fs/access_test.mbt | 2 +- src/fs/create_test.mbt | 26 +-- src/fs/cursor_test.mbt | 8 +- src/fs/file.mbt | 6 +- src/fs/lock_test.mbt | 4 +- src/fs/mkdir_test.mbt | 6 +- src/fs/named_pipe_test.mbt | 12 +- src/fs/pkg.generated.mbti | 4 +- src/fs/random_access_test.mbt | 10 +- src/fs/read_all_test.mbt | 2 +- src/fs/rename_test.mbt | 22 +- src/fs/text_file_test.mbt | 4 +- src/fs/timestamp_test.mbt | 19 +- src/fs/tmpdir_test.mbt | 2 +- src/fs/watch_test.mbt | 47 ++--- src/gzip/gzip_test.mbt | 20 +- src/http/README.mbt.md | 12 +- src/http/client.js.mbt | 6 +- src/http/client.mbt | 6 +- src/http/client_test.mbt | 16 +- src/http/cookie.mbt | 16 +- src/http/parser_wbtest.mbt | 194 +++++++++--------- src/http/pkg.generated.mbti | 16 +- src/http/proxy_test.mbt | 16 +- src/http/request.mbt | 18 +- src/http/request_test.mbt | 6 +- src/http/send.mbt | 6 +- src/http/send_wbtest.mbt | 34 +-- src/http/server.mbt | 8 +- .../event_loop/missing_close_test.mbt | 2 +- .../gzip_internal/gzip_internal_test.mbt | 8 +- src/io/README.mbt.md | 124 +++++------ src/io/buffered_writer.mbt | 2 + src/io/buffered_writer_test.mbt | 11 +- src/io/data.mbt | 97 --------- src/io/memory_reader_test.mbt | 6 +- src/io/pkg.generated.mbti | 18 +- src/io/read_all_test.mbt | 17 +- src/io/read_some_test.mbt | 2 +- src/io/reader.mbt | 19 +- src/io/writer.mbt | 35 +++- src/io/writer_test.mbt | 54 ++++- src/js_async/readable_stream_test.mbt | 12 +- src/pipe/read_exactly_test.mbt | 6 +- src/pipe/reader_closed_test.mbt | 2 +- src/process/README.mbt.md | 42 ++-- src/process/basic_test.mbt | 7 +- src/process/cancel_test.mbt | 8 +- src/process/cwd_test.mbt | 3 +- src/process/env_test.mbt | 4 +- src/process/helper_test.mbt | 14 +- src/process/pkg.generated.mbti | 8 +- src/process/process.mbt | 8 +- src/process/redirect_test.mbt | 8 +- src/process/spawn_in_group_test.mbt | 4 +- src/raw_fd/raw_fd_test.mbt | 6 +- src/socket/dual_stack_test.mbt | 12 +- src/socket/getsockname_test.mbt | 4 +- src/socket/read_exactly_test.mbt | 6 +- src/socket/reuse_addr_test.mbt | 4 +- src/stdio/moon.pkg | 1 + src/stdio/stdio_test.mbt | 15 +- src/tls/openssl.mbt | 2 +- src/tls/schannel.mbt | 4 +- src/tls/shutdown_test.mbt | 6 +- src/tls/tls.wasm.mbt | 4 +- src/tls/tls_test.mbt | 6 +- src/websocket/README.mbt.md | 2 +- src/websocket/bad_client_test.mbt | 4 +- src/websocket/conn.mbt | 2 +- src/websocket/control_test.mbt | 34 +-- src/websocket/external_client_test.mbt | 6 +- src/websocket/server.mbt | 2 +- src/websocket/utf8_validation_test.mbt | 4 +- src/websocket/websocket_test.mbt | 20 +- test_programs/lock_file/main.mbt | 4 +- test_programs/setup_sanitizer_check/main.mbt | 4 +- test_programs/setup_sanitizer_check/moon.pkg | 1 + 84 files changed, 623 insertions(+), 638 deletions(-) delete mode 100644 src/io/data.mbt diff --git a/examples/http_file_server/server_wbtest.mbt b/examples/http_file_server/server_wbtest.mbt index 6fd72854..04cb7573 100644 --- a/examples/http_file_server/server_wbtest.mbt +++ b/examples/http_file_server/server_wbtest.mbt @@ -32,7 +32,7 @@ async fn client( let response = conn.get(path) output_response(response, logger) match handle_response { - None => logger <+ "\{conn.read_all().text()}" + None => logger <+ "\{conn.read_all_text()}" Some(f) => f(conn) } } diff --git a/examples/line_processing/main.mbt b/examples/line_processing/main.mbt index 4572c7d4..7904cf35 100644 --- a/examples/line_processing/main.mbt +++ b/examples/line_processing/main.mbt @@ -39,9 +39,12 @@ async fn process_lines( } if keep { if show_name { - output..write(name).write(":") + output..write_text(name).write_text(":") } - output..write(line_prefix(line_no))..write(line).write("\n") + output + ..write_text(line_prefix(line_no)) + ..write_text(line) + .write_text("\n") } line_no += 1 } diff --git a/examples/tcp_ping_pong/main.mbt b/examples/tcp_ping_pong/main.mbt index 572146a4..54ce670c 100644 --- a/examples/tcp_ping_pong/main.mbt +++ b/examples/tcp_ping_pong/main.mbt @@ -26,7 +26,7 @@ async fn server_main(server : @socket.TcpServer, println : Printer) -> Unit { @async.sleep(10) println("server received: \{@utf8.decode(msg)}") let reply = "pong" - conn.write(reply) + conn.write_text(reply) println("server sent: \{reply}") if msg == b"exit" { println("server initiate terminate") @@ -58,7 +58,7 @@ async fn client( // with server side "received connection" message @async.sleep(10) println("client \{id}: connection established") - conn.write(msg) + conn.write_text(msg) println("client \{id} sent: \{msg}") if conn.read_some() is Some(msg) { @async.sleep(10) diff --git a/examples/websocket_echo_server/main.mbt b/examples/websocket_echo_server/main.mbt index ba74c386..1141ce65 100644 --- a/examples/websocket_echo_server/main.mbt +++ b/examples/websocket_echo_server/main.mbt @@ -50,12 +50,12 @@ async fn start_echo_server(port? : Int = 9001) -> Unit { let msg = ws.recv() match msg.kind { Text => { - let text = msg.read_all().text() + let text = msg.read_all_text() println("Received text \{text.char_length()} chars") ws.send_text(text) } Binary => { - let data = msg.read_all().binary() + let data = msg.read_all() println("Received binary data (\{data.length()} bytes)") ws.send_binary(data) } diff --git a/examples/websocket_echo_server/server_wbtest.mbt b/examples/websocket_echo_server/server_wbtest.mbt index a69a1b8e..d2a75038 100644 --- a/examples/websocket_echo_server/server_wbtest.mbt +++ b/examples/websocket_echo_server/server_wbtest.mbt @@ -21,11 +21,11 @@ async test "test server" { client.send_binary(b"abcd") let msg = client.recv() debug_inspect(msg.kind, content="Binary") - inspect(msg.read_all().text(), content="abcd") + inspect(msg.read_all_text(), content="abcd") client.send_text("abcd") let msg = client.recv() debug_inspect(msg.kind, content="Text") - inspect(msg.read_all().text(), content="abcd") + inspect(msg.read_all_text(), content="abcd") client.send_close() } } diff --git a/src/fs/README.mbt.md b/src/fs/README.mbt.md index 2d0e14c2..2ab6fd63 100644 --- a/src/fs/README.mbt.md +++ b/src/fs/README.mbt.md @@ -36,7 +36,7 @@ async test "open file for reading" { @fs.write_file(test_file, b"Hello, MoonBit!", create_mode=CreateOrTruncate) let file = @fs.open(test_file, mode=ReadOnly) defer file.close() - let content = file.read_all().text() + let content = file.read_all_text() @fs.remove(test_file) inspect(content, content="Hello, MoonBit!") } @@ -62,7 +62,7 @@ async test "open with append mode" { let file = @fs.open(test_file, mode=WriteOnly, append=true) file.write(b"Second line\n") file.close() - let content = @fs.read_file(test_file).text() + let content = @utf8.decode(@fs.read_file(test_file)) @fs.remove(test_file) inspect(content, content="First line\nSecond line\n") } @@ -101,7 +101,7 @@ async test "read_file - read entire file" { @fs.write_file(test_file, b"Hello, MoonBit!", create_mode=CreateOrTruncate) let content = @fs.read_file(test_file) @fs.remove(test_file) - inspect(content.text(), content="Hello, MoonBit!") + inspect(@utf8.decode(content), content="Hello, MoonBit!") } ///| @@ -127,7 +127,7 @@ async test "read_all from file" { let data = file.read_all() file.close() @fs.remove(test_file) - inspect(data.text(), content="Complete content") + inspect(@utf8.decode(data), content="Complete content") } ///| @@ -156,7 +156,7 @@ Write data to files using various methods: async test "write_file - write entire file" { let test_file = "_build/test_write.txt" @fs.write_file(test_file, b"File content", create_mode=CreateOrTruncate) - let content = @fs.read_file(test_file).text() + let content = @utf8.decode(@fs.read_file(test_file)) @fs.remove(test_file) inspect(content, content="File content") } @@ -172,7 +172,7 @@ async test "write with sync modes" { sync=Data, create_mode=CreateOrTruncate, ) - let content = @fs.read_file(test_file).text() + let content = @utf8.decode(@fs.read_file(test_file)) @fs.remove(test_file) inspect(content, content="Synced data") } @@ -185,7 +185,7 @@ async test "write using File methods" { file.write(b"Line 1\n") file.write(b"Line 2\n") file.close() - let content = @fs.read_file(test_file).text() + let content = @utf8.decode(@fs.read_file(test_file)) @fs.remove(test_file) inspect(content, content="Line 1\nLine 2\n") } @@ -241,12 +241,12 @@ async test "write at specific position" { { let file = @fs.open(test_file, mode=WriteOnly, create_mode=CreateOrTruncate) defer file.close() - file.write("abcdef") + file.write_text("abcdef") file.write_at(b"CD", position=2) } // read 3 bytes at position 5 - inspect(@fs.read_file(test_file).text(), content="abCDef") + inspect(@utf8.decode(@fs.read_file(test_file)), content="abCDef") @fs.remove(test_file) } diff --git a/src/fs/access_test.mbt b/src/fs/access_test.mbt index 8f5664bc..1c048297 100644 --- a/src/fs/access_test.mbt +++ b/src/fs/access_test.mbt @@ -57,7 +57,7 @@ async test "chmod" { } let path = "_build/chmod_test" @async.with_task_group() <| group => { - @fs.write_file(path, "abcd", create_mode=CreateNew, permission=0o444) + @fs.write_file(path, b"abcd", create_mode=CreateNew, permission=0o444) group.add_defer(() => @fs.remove(path)) inspect(@fs.can_read(path), content="true") inspect(@fs.can_write(path), content="false") diff --git a/src/fs/create_test.mbt b/src/fs/create_test.mbt index ca296236..5c2aab1a 100644 --- a/src/fs/create_test.mbt +++ b/src/fs/create_test.mbt @@ -23,7 +23,7 @@ async test "basic create" { let result = { let r = @fs.open(path, mode=ReadOnly) defer r.close() - r.read_all().text() + r.read_all_text() } @fs.remove(path) inspect( @@ -55,7 +55,7 @@ async test "create or append" { let result = { let r = @fs.open(path, mode=ReadOnly) defer r.close() - r.read_all().text() + r.read_all_text() } @fs.remove(path) inspect(result, content="abcdefgh") @@ -65,7 +65,7 @@ async test "create or append" { async test "wrapper test" { let path = "_build/fs_wrapper_test" @fs.write_file(path, b"abcd", create_mode=CreateOrTruncate) - let result = @fs.read_file(path).text() + let result = @utf8.decode(@fs.read_file(path)) @fs.remove(path) inspect(result, content="abcd") } @@ -83,9 +83,9 @@ async test "create_mode test" { let file = @fs.create(path, allow_existing=false) defer file.close() group.add_defer(() => @fs.remove(path)) - file.write("abcd") + file.write_text("abcd") } - inspect(@fs.read_file(path).text(), content="abcd") + inspect(@utf8.decode(@fs.read_file(path)), content="abcd") @test_util.assert_raise_async(() => @fs.create(path, allow_existing=false)) @test_util.assert_raise_async <| () => { @fs.open(path, mode=WriteOnly, create_mode=CreateNew) @@ -93,26 +93,26 @@ async test "create_mode test" { { let file = @fs.open(path, mode=WriteOnly, create_mode=OpenExisting) defer file.close() - file.write("AB") + file.write_text("AB") } - inspect(@fs.read_file(path).text(), content="ABcd") + inspect(@utf8.decode(@fs.read_file(path)), content="ABcd") { let file = @fs.open(path, mode=WriteOnly, create_mode=OpenOrCreate) defer file.close() - file.write("XY") + file.write_text("XY") } - inspect(@fs.read_file(path).text(), content="XYcd") + inspect(@utf8.decode(@fs.read_file(path)), content="XYcd") { let file = @fs.open(path, mode=WriteOnly, create_mode=CreateOrTruncate) defer file.close() - file.write("12") + file.write_text("12") } - inspect(@fs.read_file(path).text(), content="12") + inspect(@utf8.decode(@fs.read_file(path)), content="12") { let file = @fs.open(path, mode=WriteOnly, create_mode=TruncateExisting) defer file.close() - file.write("34") + file.write_text("34") } - inspect(@fs.read_file(path).text(), content="34") + inspect(@utf8.decode(@fs.read_file(path)), content="34") } } diff --git a/src/fs/cursor_test.mbt b/src/fs/cursor_test.mbt index 5434172f..7d8b54e4 100644 --- a/src/fs/cursor_test.mbt +++ b/src/fs/cursor_test.mbt @@ -15,7 +15,7 @@ ///| async test "sequential read/write have separated cursor" { let path = "_build/read_write_cursor_test" - @fs.write_file(path, "abcd", create_mode=CreateOrTruncate, sync=Full) + @fs.write_file(path, b"abcd", create_mode=CreateOrTruncate, sync=Full) let file = @fs.open(path, mode=ReadWrite) defer file.close() file.write(b"AB") @@ -26,13 +26,13 @@ async test "sequential read/write have separated cursor" { ///| async test "append mode only affect writing" { let path = "_build/append_cursor_test" - @fs.write_file(path, "abcd", create_mode=CreateOrTruncate, sync=Full) + @fs.write_file(path, b"abcd", create_mode=CreateOrTruncate, sync=Full) let file = @fs.open(path, append=true, mode=ReadWrite) defer file.close() // the read cursor should not be affected by `append` json_inspect(file.read_exactly(2), content="ab") // writing should append to EOF automatically - file.write("ef") + file.write_text("ef") // read cursor is unaffected by append write json_inspect(file.read_exactly(2), content="cd") json_inspect(file.read_exactly(2), content="ef") @@ -41,7 +41,7 @@ async test "append mode only affect writing" { ///| async test "append mode file does not support random access write" { let path = "_build/append_random_access_test" - @fs.write_file(path, "abcd", create_mode=CreateOrTruncate, sync=Full) + @fs.write_file(path, b"abcd", create_mode=CreateOrTruncate, sync=Full) let file = @fs.open(path, append=true, mode=ReadWrite) defer file.close() // random access read is not affected diff --git a/src/fs/file.mbt b/src/fs/file.mbt index f034ddc3..caa014cc 100644 --- a/src/fs/file.mbt +++ b/src/fs/file.mbt @@ -472,7 +472,7 @@ pub async fn File::ctime(self : File) -> (Int64, Int) { pub async fn read_file( path : StringView, sync_timestamp? : Bool = false, -) -> &@io.Data { +) -> Bytes { let file = open(path, mode=ReadOnly) defer file.close() let result = file.read_all() @@ -491,7 +491,7 @@ pub async fn read_file( #label_migration(truncate, fill=false, msg="the option `truncate` is deprecated, use `create_mode` instead") pub async fn write_file( path : StringView, - content : &@io.Data, + content : BytesView, create_mode? : CreateMode, permission? : Int, sync? : SyncMode = Data, @@ -523,5 +523,5 @@ pub async fn write_file( permission~, ) defer file.close() - file.write(content.binary()) + file.write(content) } diff --git a/src/fs/lock_test.mbt b/src/fs/lock_test.mbt index c8f257c1..a9b5c413 100644 --- a/src/fs/lock_test.mbt +++ b/src/fs/lock_test.mbt @@ -329,7 +329,7 @@ async test "flock advisory" { let log = [] let file_name = "_build/flock_advisory" @async.with_task_group() <| group => { - @fs.write_file(file_name, create_mode=CreateOrTruncate, "abcd") + @fs.write_file(file_name, create_mode=CreateOrTruncate, b"abcd") let file = @fs.open(file_name, mode=ReadWrite) defer file.close() group.add_defer(() => @fs.remove(file_name)) @@ -342,7 +342,7 @@ async test "flock advisory" { ) @async.sleep(250) log.push("attempting to read") - log.push("read from file: \{file.read_all().text()}") + log.push("read from file: \{file.read_all_text()}") } json_inspect(log, content=[ "child: acquired lock", "attempting to read", "read from file: abcd", "child: releasing lock", diff --git a/src/fs/mkdir_test.mbt b/src/fs/mkdir_test.mbt index e18191f9..e6344379 100644 --- a/src/fs/mkdir_test.mbt +++ b/src/fs/mkdir_test.mbt @@ -16,7 +16,7 @@ async test "mkdir" { let path = "_build/mkdir_test" @fs.mkdir(path) - @fs.write_file("\{path}/file", "", create_mode=CreateOrTruncate) + @fs.write_file("\{path}/file", b"", create_mode=CreateOrTruncate) let result = @fs.readdir(path, sort=true) @fs.remove("\{path}/file") @fs.rmdir(path) @@ -28,9 +28,9 @@ async test "rmdir recursive" { guard @env.current_dir() is Some(cwd) let path = "_build/rmdir_test" @fs.mkdir(path, permission=0o755) - @fs.write_file("\{path}/file", "", create_mode=CreateOrTruncate) + @fs.write_file("\{path}/file", b"", create_mode=CreateOrTruncate) @fs.mkdir("\{path}/inner") - @fs.write_file("\{path}/inner/file", "", create_mode=CreateOrTruncate) + @fs.write_file("\{path}/inner/file", b"", create_mode=CreateOrTruncate) @fs.symlink("\{path}/symlink", target="\{cwd}/_build") @test_util.assert_raise_async(() => @fs.rmdir(path)) @fs.rmdir(path, recursive=true) diff --git a/src/fs/named_pipe_test.mbt b/src/fs/named_pipe_test.mbt index 944d2ef2..9da5d95c 100644 --- a/src/fs/named_pipe_test.mbt +++ b/src/fs/named_pipe_test.mbt @@ -68,17 +68,17 @@ async test "named fifo" { defer r.close() inspect(r.read_until("\n").unwrap_or(""), content="abcd") debug_inspect( - @async.with_timeout_opt(250, () => r.read_all().text()), + @async.with_timeout_opt(250, () => r.read_all_text()), content="None", ) - inspect(r.read_all().text(), content="efgh") + inspect(r.read_all_text(), content="efgh") } group.spawn_bg() <| () => { let w = @fs.open(path, mode=WriteOnly) defer w.close() - w.write("abcd\n") + w.write_text("abcd\n") @async.sleep(500) - w.write("efgh") + w.write_text("efgh") } } } @@ -101,10 +101,10 @@ async test "named pipe" { defer r.close() inspect(r.read_until("\n").unwrap_or(""), content="abcd") debug_inspect( - @async.with_timeout_opt(250, () => r.read_all().text()), + @async.with_timeout_opt(250, () => r.read_all_text()), content="None", ) - inspect(r.read_all().text(), content="efgh") + inspect(r.read_all_text(), content="efgh") } group.spawn_bg() <| () => { // wait for the client to connect diff --git a/src/fs/pkg.generated.mbti b/src/fs/pkg.generated.mbti index a074e1d9..f2549a26 100644 --- a/src/fs/pkg.generated.mbti +++ b/src/fs/pkg.generated.mbti @@ -35,7 +35,7 @@ pub async fn open(StringView, mode~ : Mode, sync? : SyncMode, append? : Bool, cr pub async fn opendir(StringView) -> Directory -pub async fn read_file(StringView, sync_timestamp? : Bool) -> &@io.Data +pub async fn read_file(StringView, sync_timestamp? : Bool) -> Bytes pub async fn readdir(StringView, include_hidden? : Bool, include_special? : Bool, sort? : Bool) -> Array[String] @@ -55,7 +55,7 @@ pub async fn walk(StringView, async (String, Array[String]) -> Unit, exclude? : #label_migration(create, fill=false, msg="the option `create` is deprecated, use `create_mode` and `permission` instead") #label_migration(truncate, fill=false, msg="the option `truncate` is deprecated, use `create_mode` instead") -pub async fn write_file(StringView, &@io.Data, create_mode? : CreateMode, permission? : Int, sync? : SyncMode, append? : Bool, create? : Int, truncate? : Bool) -> Unit +pub async fn write_file(StringView, BytesView, create_mode? : CreateMode, permission? : Int, sync? : SyncMode, append? : Bool, create? : Int, truncate? : Bool) -> Unit // Errors diff --git a/src/fs/random_access_test.mbt b/src/fs/random_access_test.mbt index 6c063896..d93722d3 100644 --- a/src/fs/random_access_test.mbt +++ b/src/fs/random_access_test.mbt @@ -16,22 +16,22 @@ async test "write_at" { let path = "_build/write_at_test" // initialize - @fs.write_file(path, "abcdef", create_mode=CreateOrTruncate, sync=Full) + @fs.write_file(path, b"abcdef", create_mode=CreateOrTruncate, sync=Full) // first read - inspect(@fs.read_file(path).text(), content="abcdef") + inspect(@utf8.decode(@fs.read_file(path)), content="abcdef") // update content { let w = @fs.open(path, mode=WriteOnly) defer w.close() w.write_at(b"56", position=w.size() - 2) w.write_at(b"34", position=2) - w.write("12") // still write from the start + w.write_text("12") // still write from the start } // read content { let r = @fs.open(path, mode=ReadOnly) defer r.close() - inspect(r.read_all().text(), content="123456") + inspect(r.read_all_text(), content="123456") } @fs.remove(path) } @@ -39,7 +39,7 @@ async test "write_at" { ///| async test "read_at" { let path = "_build/read_at_test" - @fs.write_file(path, "abcdef", create_mode=CreateOrTruncate, sync=Full) + @fs.write_file(path, b"abcdef", create_mode=CreateOrTruncate, sync=Full) { let r = @fs.open(path, mode=ReadOnly) defer r.close() diff --git a/src/fs/read_all_test.mbt b/src/fs/read_all_test.mbt index 36ccb1ec..9347fa6f 100644 --- a/src/fs/read_all_test.mbt +++ b/src/fs/read_all_test.mbt @@ -18,7 +18,7 @@ async test "read_all basic" { let r = @fs.open(path, mode=ReadOnly) defer r.close() inspect( - r.read_all().text(), + r.read_all_text(), content=( #| #| Apache License diff --git a/src/fs/rename_test.mbt b/src/fs/rename_test.mbt index 98b0b484..497b10b6 100644 --- a/src/fs/rename_test.mbt +++ b/src/fs/rename_test.mbt @@ -23,12 +23,12 @@ async test "rename basic" { sync=Full, ) defer file.close() - file.write("abcd") - inspect(@fs.read_file(old_path).text(), content="abcd") + file.write_text("abcd") + inspect(@utf8.decode(@fs.read_file(old_path)), content="abcd") @fs.rename(old_path, new_path) - inspect(@fs.read_file(new_path).text(), content="abcd") + inspect(@utf8.decode(@fs.read_file(new_path)), content="abcd") file.write_at(b"ABCD", position=0) - inspect(@fs.read_file(new_path).text(), content="ABCD") + inspect(@utf8.decode(@fs.read_file(new_path)), content="ABCD") inspect(@fs.exists(old_path), content="false") @fs.remove(new_path) } @@ -37,7 +37,7 @@ async test "rename basic" { async test "rename replace" { let old_path = "_build/rename_replace_test_old" let new_path = "_build/rename_replace_test_new" - @fs.write_file(new_path, "xyzw", create_mode=CreateOrTruncate) + @fs.write_file(new_path, b"xyzw", create_mode=CreateOrTruncate) let old_file = @fs.open( old_path, mode=WriteOnly, @@ -47,17 +47,17 @@ async test "rename replace" { defer old_file.close() let new_file = @fs.open(new_path, mode=ReadOnly, create_mode=OpenExisting) defer new_file.close() - old_file.write("abcd") - inspect(@fs.read_file(old_path).text(), content="abcd") - inspect(@fs.read_file(new_path).text(), content="xyzw") + old_file.write_text("abcd") + inspect(@utf8.decode(@fs.read_file(old_path)), content="abcd") + inspect(@utf8.decode(@fs.read_file(new_path)), content="xyzw") @test_util.assert_raise_async <| () => { @fs.rename(old_path, new_path, replace=false) } @fs.rename(old_path, new_path, replace=true) - inspect(@fs.read_file(new_path).text(), content="abcd") - inspect(new_file.read_all().text(), content="xyzw") + inspect(@utf8.decode(@fs.read_file(new_path)), content="abcd") + inspect(new_file.read_all_text(), content="xyzw") old_file.write_at(b"ABCD", position=0) - inspect(@fs.read_file(new_path).text(), content="ABCD") + inspect(@utf8.decode(@fs.read_file(new_path)), content="ABCD") inspect(@fs.exists(old_path), content="false") @fs.remove(new_path) } diff --git a/src/fs/text_file_test.mbt b/src/fs/text_file_test.mbt index 1effca48..7ea08c2e 100644 --- a/src/fs/text_file_test.mbt +++ b/src/fs/text_file_test.mbt @@ -17,7 +17,7 @@ async test "text file" { @async.with_task_group() <| root => { let path = "_build/basic_text_file_test" root.add_defer(() => @fs.remove(path)) - @fs.write_file(path, "abcd", create_mode=CreateOrTruncate) - inspect(@fs.read_file(path).text(), content="abcd") + @fs.write_file(path, b"abcd", create_mode=CreateOrTruncate) + inspect(@utf8.decode(@fs.read_file(path)), content="abcd") } } diff --git a/src/fs/timestamp_test.mbt b/src/fs/timestamp_test.mbt index c9aad280..955336b0 100644 --- a/src/fs/timestamp_test.mbt +++ b/src/fs/timestamp_test.mbt @@ -29,19 +29,22 @@ async test "timestamp for path" { } let path = "/tmp/timestamp_test" @async.with_task_group() <| group => { - @fs.write_file(path, "abcd", create_mode=CreateOrTruncate, sync=Full) + @fs.write_file(path, b"abcd", create_mode=CreateOrTruncate, sync=Full) group.add_defer(() => @fs.remove(path)) let mtime_1 = @fs.mtime(path) let ctime_1 = @fs.ctime(path) @async.sleep(600) - @fs.write_file(path, "1234", sync=Full) + @fs.write_file(path, b"1234", sync=Full) let atime_2 = @fs.atime(path) let mtime_2 = @fs.mtime(path) let ctime_2 = @fs.ctime(path) assert_true(diff_timestamp(mtime_2, mtime_1) > 500_000_000) assert_true(diff_timestamp(ctime_2, ctime_1) > 500_000_000) @async.sleep(600) - inspect(@fs.read_file(path, sync_timestamp=true).text(), content="1234") + inspect( + @utf8.decode(@fs.read_file(path, sync_timestamp=true)), + content="1234", + ) let atime_3 = @fs.atime(path) let mtime_3 = @fs.mtime(path) let ctime_3 = @fs.ctime(path) @@ -63,12 +66,12 @@ async test "timestamp for path" { async test "mtime for path" { let path = "_build/timestamp_test" @async.with_task_group() <| group => { - @fs.write_file(path, "abcd", create_mode=CreateOrTruncate, sync=Full) + @fs.write_file(path, b"abcd", create_mode=CreateOrTruncate, sync=Full) group.add_defer(() => @fs.remove(path)) let mtime_1 = @fs.mtime(path) let ctime_1 = @fs.ctime(path) @async.sleep(600) - @fs.write_file(path, "1234", sync=Full) + @fs.write_file(path, b"1234", sync=Full) let mtime_2 = @fs.mtime(path) let ctime_2 = @fs.ctime(path) assert_true(diff_timestamp(mtime_2, mtime_1) > 500_000_000) @@ -86,7 +89,7 @@ async test "timestamp for opened file" { } let path = "/tmp/opened_file_timestamp_test" @async.with_task_group() <| group => { - @fs.write_file(path, "abcd", create_mode=CreateOrTruncate, sync=Full) + @fs.write_file(path, b"abcd", create_mode=CreateOrTruncate, sync=Full) group.add_defer(() => @fs.remove(path)) let file = @fs.open(path, mode=ReadWrite, sync=Full) defer file.close() @@ -100,7 +103,7 @@ async test "timestamp for opened file" { assert_true(diff_timestamp(mtime_2, mtime_1) > 500_000_000) assert_true(diff_timestamp(ctime_2, ctime_1) > 500_000_000) @async.sleep(600) - inspect(file.read_all().text(), content="1234") + inspect(file.read_all_text(), content="1234") file.sync() let atime_3 = file.atime() let mtime_3 = file.mtime() @@ -123,7 +126,7 @@ async test "timestamp for opened file" { async test "mtime for opened file" { let path = "_build/opened_file_timestamp_test" @async.with_task_group() <| group => { - @fs.write_file(path, "abcd", create_mode=CreateOrTruncate, sync=Full) + @fs.write_file(path, b"abcd", create_mode=CreateOrTruncate, sync=Full) group.add_defer(() => @fs.remove(path)) let file = @fs.open(path, mode=ReadWrite, sync=Full) defer file.close() diff --git a/src/fs/tmpdir_test.mbt b/src/fs/tmpdir_test.mbt index 11904ffd..428e200f 100644 --- a/src/fs/tmpdir_test.mbt +++ b/src/fs/tmpdir_test.mbt @@ -28,7 +28,7 @@ async test "tmpdir" { assert_true(@fs.exists(t2)) @fs.write_file( "\{t1}/file.txt", - "abcd", + b"abcd", create_mode=CreateNew, permission=0o600, ) diff --git a/src/fs/watch_test.mbt b/src/fs/watch_test.mbt index f0c73a02..4ebc1e7b 100644 --- a/src/fs/watch_test.mbt +++ b/src/fs/watch_test.mbt @@ -21,7 +21,8 @@ enum TestFile { ///| async fn TestFile::instantiate(self : TestFile, path : String) -> Unit { match self { - File(content) => @fs.write_file(path, content, create_mode=CreateOrTruncate) + File(content) => + @fs.write_file(path, @utf8.encode(content), create_mode=CreateOrTruncate) Dir(files) => { @fs.mkdir(path) for name, file in files { @@ -57,14 +58,14 @@ async fn watch_basic_test( }) // 1. modify content of existing file at root log.push("writing to root file") - @fs.write_file("\{path}/root_file", "ABCD", create_mode=OpenExisting) + @fs.write_file("\{path}/root_file", b"ABCD", create_mode=OpenExisting) @async.sleep(150) // 2. modify content of existing file at inner directory log.push("writing to inner file") @fs.write_file( "\{path}/inner_dir/inner_file", - "EFGH", + b"EFGH", create_mode=OpenExisting, ) @async.sleep(150) @@ -78,7 +79,7 @@ async fn watch_basic_test( // 4. modify content of newly created file at root directory log.push("writing to newly created root file") - file.write("ABCD") + file.write_text("ABCD") } @async.sleep(150) @@ -91,7 +92,7 @@ async fn watch_basic_test( // 6. modify content of newly created file at inner directory log.push("writing to newly created inner file") - file.write("EFGH") + file.write_text("EFGH") } @async.sleep(150) @@ -124,7 +125,7 @@ async fn watch_basic_test( // 12. modify content of newly created file at newly created inner directory log.push("writing to newly created inner file") - file.write("EFGH") + file.write_text("EFGH") } @async.sleep(150) }) @@ -238,7 +239,7 @@ async fn watch_rename_within_test( log.push("writing to inner_dir/inner_file2") @fs.write_file( "\{path}/inner_dir/inner_file2", - "efgh", + b"efgh", create_mode=OpenExisting, ) @async.sleep(150) @@ -252,7 +253,7 @@ async fn watch_rename_within_test( log.push("writing to inner_dir2/inner_file2") @fs.write_file( "\{path}/inner_dir2/inner_file2", - "EFGH", + b"EFGH", create_mode=OpenExisting, ) @async.sleep(150) @@ -264,7 +265,7 @@ async fn watch_rename_within_test( // 6. modify file moved across directory but still within watched tree log.push("writing to root_file2") - @fs.write_file("\{path}/root_file2", "EFGH", create_mode=OpenExisting) + @fs.write_file("\{path}/root_file2", b"EFGH", create_mode=OpenExisting) @async.sleep(150) // 7. rename file replaces existing file @@ -357,7 +358,7 @@ async fn watch_rename_inout_test( } }) // 1. move foreign file inside watched tree - @fs.write_file("\{base_path}/foreign_file", "1234", create_mode=CreateNew) + @fs.write_file("\{base_path}/foreign_file", b"1234", create_mode=CreateNew) log.push("moving foreign file inside watched tree") @fs.rename("\{base_path}/foreign_file", "\{watched_path}/moved_in_file") @async.sleep(150) @@ -366,7 +367,7 @@ async fn watch_rename_inout_test( log.push("writing to moved_in_file") @fs.write_file( "\{watched_path}/moved_in_file", - "5678", + b"5678", create_mode=OpenExisting, ) @async.sleep(150) @@ -382,7 +383,7 @@ async fn watch_rename_inout_test( log.push("writing to moved_in_dir/foreign_file") @fs.write_file( "\{watched_path}/moved_in_dir/foreign_file", - "XYZW", + b"XYZW", create_mode=OpenExisting, ) @async.sleep(150) @@ -396,7 +397,7 @@ async fn watch_rename_inout_test( log.push("writing to moved_out_file") @fs.write_file( "\{base_path}/moved_out_file", - "1234", + b"1234", create_mode=OpenExisting, ) @async.sleep(150) @@ -415,7 +416,7 @@ async fn watch_rename_inout_test( log.push("writing to moved_out_dir/foreign_file") @fs.write_file( "\{base_path}/moved_out_dir/foreign_file", - "xyzw", + b"xyzw", create_mode=OpenExisting, ) @async.sleep(150) @@ -427,7 +428,7 @@ async fn watch_rename_inout_test( // 11. moved in file overwrites existing file log.push("renaming foreign file to replace root_file") - @fs.write_file("\{base_path}/foreign_file", "1234", create_mode=CreateNew) + @fs.write_file("\{base_path}/foreign_file", b"1234", create_mode=CreateNew) @fs.rename("\{base_path}/foreign_file", "\{watched_path}/root_file") @async.sleep(150) @@ -559,11 +560,11 @@ async fn watch_vertical_swap_test( let events = @async.with_timeout(1000, () => watcher.wait()) log.push("watcher received change: \{to_repr(events)}") log.push("modifying previous outer file") - @fs.write_file("\{path}/outer/inner/file", "ABCD") + @fs.write_file("\{path}/outer/inner/file", b"ABCD") let events = @async.with_timeout(1000, () => watcher.wait()) log.push("watcher received change: \{to_repr(events)}") log.push("modifying previous inner file") - @fs.write_file("\{path}/outer/file", "EFGH") + @fs.write_file("\{path}/outer/file", b"EFGH") let events = @async.with_timeout(1000, () => watcher.wait()) log.push("watcher received change: \{to_repr(events)}") }) @@ -639,12 +640,12 @@ async test "watch ignored path" { // 1. modify file in ignored path log.push("writing to ignored/file") - @fs.write_file("\{path}/ignored/file", "ABCD", create_mode=OpenExisting) + @fs.write_file("\{path}/ignored/file", b"ABCD", create_mode=OpenExisting) @async.sleep(150) // 2. modify file in normal path log.push("writing to normal/file") - @fs.write_file("\{path}/normal/file", "EFGH", create_mode=OpenExisting) + @fs.write_file("\{path}/normal/file", b"EFGH", create_mode=OpenExisting) @async.sleep(150) // 3. create file in ignored directory @@ -656,7 +657,7 @@ async test "watch ignored path" { // 4. modify content of newly created file at ignored directory log.push("writing to newly created file in ignored directory") - file.write("ABCD") + file.write_text("ABCD") } @async.sleep(150) @@ -669,7 +670,7 @@ async test "watch ignored path" { // 6. modify content of newly created file at normal directory log.push("writing to newly created file in normal directory") - file.write("EFGH") + file.write_text("EFGH") } @async.sleep(150) @@ -707,7 +708,7 @@ async test "watch ignored path" { // 13. create new file in re-created ignored path log.push("writing to newly created file in ignored directory") - file.write("ABCD") + file.write_text("ABCD") } @async.sleep(150) @@ -725,7 +726,7 @@ async test "watch ignored path" { // 16. create new file in re-created normal path log.push("writing to newly created file in normal directory") - file.write("EFGH") + file.write_text("EFGH") } @async.sleep(150) }) diff --git a/src/gzip/gzip_test.mbt b/src/gzip/gzip_test.mbt index 5f550372..975cfb40 100644 --- a/src/gzip/gzip_test.mbt +++ b/src/gzip/gzip_test.mbt @@ -26,8 +26,8 @@ async fn node_gzip(mode : String, input : Bytes) -> Bytes { } let (code, stdout, stderr) = runner.wait() assert_eq(code, 0) - assert_eq(stderr.binary().length(), 0) - stdout.binary() + assert_eq(stderr.length(), 0) + stdout } } @@ -41,7 +41,7 @@ async fn encode_with_moonbit(input : Bytes) -> Bytes { @gzip.Encoder(w)..write(input).end() } defer r.close() - r.read_all().binary() + r.read_all() } } @@ -55,7 +55,7 @@ async fn decode_with_moonbit(input : Bytes) -> Bytes { w.write(input) } defer r.close() - @gzip.Decoder(r).read_all().binary() + @gzip.Decoder(r).read_all() } } @@ -82,14 +82,14 @@ async test "encode decode roundtrip" { operating_system=b'\x03', ) for chunk in source_chunks { - encoder.write(chunk) + encoder.write_text(chunk) @async.sleep(10) } encoder.end() } let decoder = @gzip.Decoder(compressed_r) - inspect(decoder.read_all().text(), content="hello gzip world") + inspect(decoder.read_all_text(), content="hello gzip world") compressed_r.close() } } @@ -107,7 +107,7 @@ async test "encoder uses valid blocks across large writes" { } let decoder = @gzip.Decoder(compressed_r) - inspect(decoder.read_all().binary() == payload, content="true") + inspect(decoder.read_all() == payload, content="true") compressed_r.close() } } @@ -123,11 +123,11 @@ async test "encoder emits output when flushed" { let encoder = @gzip.Encoder(w) log.push("encoder ready") log.push("write plain ab") - encoder.write("ab") + encoder.write_text("ab") encoder.flush() @async.sleep(60) log.push("write plain cd") - encoder.write("cd") + encoder.write_text("cd") encoder.flush() @async.sleep(60) log.push("end") @@ -234,7 +234,7 @@ async test "decoder supports reads smaller than writer chunks" { ///| #cfg(target="native") async test "node compatibility on source text file" { - let input = @fs.read_file("src/internal/gzip_internal/decoder.mbt").binary() + let input = @fs.read_file("src/internal/gzip_internal/decoder.mbt") assert_node_compatibility(input) } diff --git a/src/http/README.mbt.md b/src/http/README.mbt.md index 221a3ef4..b5564333 100644 --- a/src/http/README.mbt.md +++ b/src/http/README.mbt.md @@ -10,12 +10,12 @@ Simple HTTP request can be made in just one line: async test { let (response, body) = @http.get("https://www.moonbitlang.com") inspect(response.code, content="200") - assert_true(body.text().has_prefix("")) + assert_true(@utf8.decode(body).has_prefix("")) } ``` -You can use use `body.text()` to get a `String` (decoded via UTF8) -or `body.json()` for a `Json` from the response body. +The response body is returned as raw `Bytes`. Decode it with `@utf8.decode` +or parse the decoded text with `@json.parse` when needed. ## Generic HTTP client Sometimes, the simple one-time `@http.get` etc. is insufficient, @@ -48,7 +48,7 @@ async test { let response = client..request(Get, "/").end_request() inspect(response.code, content="200") let body = client.read_all() - assert_true(body.text().has_prefix("")) + assert_true(@utf8.decode(body).has_prefix("")) } ``` @@ -81,7 +81,9 @@ Here's an example server that returns 404 to every request: #cfg(target="native") pub async fn server(listen_addr : @socket.Addr) -> Unit { @http.Server(listen_addr).run_forever((request, _body, conn) => { - conn..send_response(404, "NotFound").write("`\{request.path}` not found") + conn + ..send_response(404, "NotFound") + .write_text("`\{request.path}` not found") }) } ``` diff --git a/src/http/client.js.mbt b/src/http/client.js.mbt index 5d5ad3d6..ae95d0ea 100644 --- a/src/http/client.js.mbt +++ b/src/http/client.js.mbt @@ -272,7 +272,7 @@ pub async fn Client::get( self : Client, path : String, extra_headers? : Map[String, String] = {}, - body? : &@io.Data, + body? : BytesView, ) -> Response { self.request(Get, path, extra_headers~) if body is Some(body) { @@ -286,7 +286,7 @@ pub async fn Client::get( pub async fn Client::put( self : Client, path : String, - body : &@io.Data, + body : BytesView, extra_headers? : Map[String, String] = {}, ) -> Response { self..request(Put, path, extra_headers~)..write(body).end_request() @@ -297,7 +297,7 @@ pub async fn Client::put( pub async fn Client::post( self : Client, path : String, - body : &@io.Data, + body : BytesView, extra_headers? : Map[String, String] = {}, ) -> Response { self..request(Post, path, extra_headers~)..write(body).end_request() diff --git a/src/http/client.mbt b/src/http/client.mbt index d318f90b..df9f6d24 100644 --- a/src/http/client.mbt +++ b/src/http/client.mbt @@ -271,7 +271,7 @@ pub async fn Client::get( self : Client, path : String, extra_headers? : Map[String, String] = {}, - body? : &@io.Data, + body? : BytesView, ) -> Response { self.request(Get, path, extra_headers~) if body is Some(body) { @@ -285,7 +285,7 @@ pub async fn Client::get( pub async fn Client::put( self : Client, path : String, - body : &@io.Data, + body : BytesView, extra_headers? : Map[String, String] = {}, ) -> Response { self..request(Put, path, extra_headers~)..write(body).end_request() @@ -296,7 +296,7 @@ pub async fn Client::put( pub async fn Client::post( self : Client, path : String, - body : &@io.Data, + body : BytesView, extra_headers? : Map[String, String] = {}, ) -> Response { self..request(Post, path, extra_headers~)..write(body).end_request() diff --git a/src/http/client_test.mbt b/src/http/client_test.mbt index e871cb03..ab7ac818 100644 --- a/src/http/client_test.mbt +++ b/src/http/client_test.mbt @@ -26,10 +26,10 @@ async fn test_server(group : @async.TaskGroup[Unit], log : &Logger) -> Int { } conn.send_response(200, "OK") log.write_string("server: sending \{meth} response part 1\n") - conn..write("\{meth} response part 1\n").flush() + conn..write_text("\{meth} response part 1\n").flush() @async.sleep(100) log.write_string("server: sending \{meth} response part 2\n") - conn.write("\{meth} response part 2\n") + conn.write_text("\{meth} response part 2\n") }) } server.addr.port() @@ -114,10 +114,10 @@ async test "request streaming" { defer client.close() @async.sleep(100) log <+ "client: sending PUT data part 1\n" - client..write("PUT data part 1\n").flush() + client..write_text("PUT data part 1\n").flush() @async.sleep(100) log <+ "client: sending PUT data part 2\n" - client.write("PUT data part 2\n") + client.write_text("PUT data part 2\n") let response = client.end_request() inspect(response.code, content="200") fetch_response(client) @@ -128,10 +128,10 @@ async test "request streaming" { defer client.close() @async.sleep(100) log <+ "client: sending POST data part 1\n" - client..write("POST data part 1\n").flush() + client..write_text("POST data part 1\n").flush() @async.sleep(100) log <+ "client: sending POST data part 2\n" - client.write("POST data part 2\n") + client.write_text("POST data part 2\n") let response = client.end_request() inspect(response.code, content="200") fetch_response(client) @@ -180,8 +180,8 @@ async test "multiple request" { defer client.close() let response1 = client.get("/") inspect(response1.code, content="200") - assert_true(client.read_all().text().has_prefix("")) + assert_true(client.read_all_text().has_prefix("")) let response2 = client.get("/download") inspect(response2.code, content="200") - assert_true(client.read_all().text().has_prefix("")) + assert_true(client.read_all_text().has_prefix("")) } diff --git a/src/http/cookie.mbt b/src/http/cookie.mbt index 9a4993c5..692436a5 100644 --- a/src/http/cookie.mbt +++ b/src/http/cookie.mbt @@ -107,26 +107,26 @@ fn Cookie::parse(line : StringView) -> Cookie raise { ///| #cfg(any(target="native", target="wasm")) async fn[W : @io.Writer] Cookie::write_to(cookie : Cookie, w : W) -> Unit { - w..write(cookie.name)..write("=").write(cookie.value) + w..write_text(cookie.name)..write_text("=").write_text(cookie.value) if cookie.path is Some(path) { - w..write("; Path=").write(path) + w..write_text("; Path=").write_text(path) } if cookie.expires_raw is Some(expires_raw) { - w..write("; Expires=").write(expires_raw) + w..write_text("; Expires=").write_text(expires_raw) } if cookie.max_age is Some(max_age) { - w..write("; Max-Age=").write(max_age.to_string()) + w..write_text("; Max-Age=").write_text(max_age.to_string()) } if cookie.domain is Some(domain) { - w..write("; Domain=").write(domain) + w..write_text("; Domain=").write_text(domain) } if cookie.secure { - w.write("; Secure") + w.write_text("; Secure") } if cookie.http_only { - w.write("; HttpOnly") + w.write_text("; HttpOnly") } for ext in cookie.extensions { - w..write("; ").write(ext) + w..write_text("; ").write_text(ext) } } diff --git a/src/http/parser_wbtest.mbt b/src/http/parser_wbtest.mbt index f2cb8b71..92579685 100644 --- a/src/http/parser_wbtest.mbt +++ b/src/http/parser_wbtest.mbt @@ -39,15 +39,15 @@ async test "read_request basic" { root.spawn_bg() <| () => { defer w.close() w - ..write("GET / HTTP/1.1\r\n") - ..write("Host: x.y.org\r\n") - .write("User-Agent: MoonBit\r\n\r\n") + ..write_text("GET / HTTP/1.1\r\n") + ..write_text("Host: x.y.org\r\n") + .write_text("User-Agent: MoonBit\r\n\r\n") } defer r.close() let input = Reader::new(r) let request = input.read_request() log_request(request, log) - log.write_string(input.read_all().text()) + log.write_string(input.read_all_text()) } inspect( log.to_string(), @@ -68,17 +68,17 @@ async test "read_request fixed body" { root.spawn_bg() <| () => { defer w.close() w - ..write("GET / HTTP/1.1\r\n") - ..write("Host: x.y.org\r\n") - ..write("User-Agent: MoonBit\r\n") - ..write("Content-Length: 7\r\n\r\n") - .write("message") + ..write_text("GET / HTTP/1.1\r\n") + ..write_text("Host: x.y.org\r\n") + ..write_text("User-Agent: MoonBit\r\n") + ..write_text("Content-Length: 7\r\n\r\n") + .write_text("message") } defer r.close() let input = Reader::new(r) let request = input.read_request() log_request(request, log) - log.write_string(input.read_all().text()) + log.write_string(input.read_all_text()) } inspect( log.to_string(), @@ -99,17 +99,17 @@ async test "read_request fixed body early EOF" { root.spawn_bg() <| () => { defer w.close() w - ..write("GET / HTTP/1.1\r\n") - ..write("Host: x.y.org\r\n") - ..write("User-Agent: MoonBit\r\n") - ..write("Content-Length: 8\r\n\r\n") - .write("message") + ..write_text("GET / HTTP/1.1\r\n") + ..write_text("Host: x.y.org\r\n") + ..write_text("User-Agent: MoonBit\r\n") + ..write_text("Content-Length: 8\r\n\r\n") + .write_text("message") } defer r.close() let input = Reader::new(r) let _request = input.read_request() debug_inspect( - @test_util.expect_error_async(() => input.read_all().binary()), + @test_util.expect_error_async(() => input.read_all()), content=( #|ReaderClosed ), @@ -125,21 +125,21 @@ async test "read_request chunked" { root.spawn_bg() <| () => { defer w.close() w - ..write("GET / HTTP/1.1\r\n") - ..write("Host: x.y.org\r\n") - ..write("User-Agent: MoonBit\r\n") - ..write("Transfer-Encoding: chunked\r\n\r\n") - ..write("4\r\n") - ..write("abcd\r\n") - ..write("10\r\n") - ..write("abcdefghijklmnop\r\n") - .write("0\r\n\r\n") + ..write_text("GET / HTTP/1.1\r\n") + ..write_text("Host: x.y.org\r\n") + ..write_text("User-Agent: MoonBit\r\n") + ..write_text("Transfer-Encoding: chunked\r\n\r\n") + ..write_text("4\r\n") + ..write_text("abcd\r\n") + ..write_text("10\r\n") + ..write_text("abcdefghijklmnop\r\n") + .write_text("0\r\n\r\n") } defer r.close() let input = Reader::new(r) let request = input.read_request() log_request(request, log) - log.write_string(input.read_all().text()) + log.write_string(input.read_all_text()) } inspect( log.to_string(), @@ -161,20 +161,20 @@ async test "read_request stream" { root.spawn_bg() <| () => { defer w.close() w - ..write("GET / HTTP/1.1\r\n") - ..write("Host: x.y.org\r\n") - ..write("User-Agent: MoonBit\r\n") - .write("Transfer-Encoding: chunked\r\n\r\n") + ..write_text("GET / HTTP/1.1\r\n") + ..write_text("Host: x.y.org\r\n") + ..write_text("User-Agent: MoonBit\r\n") + .write_text("Transfer-Encoding: chunked\r\n\r\n") @async.sleep(50) log <+ "writing data...\n" - w.write("4\r\nabcd\r\n") + w.write_text("4\r\nabcd\r\n") @async.sleep(50) log <+ "writing data...\n" - w.write("4\r\nefgh\r\n") + w.write_text("4\r\nefgh\r\n") @async.sleep(50) log <+ "writing data...\n" - w.write("4\r\nijkl\r\n") - w.write("0\r\n\r\n") + w.write_text("4\r\nijkl\r\n") + w.write_text("0\r\n\r\n") } defer r.close() let input = Reader::new(r) @@ -211,36 +211,36 @@ async test "multiple request" { root.spawn_bg() <| () => { defer w.close() w - ..write("GET / HTTP/1.1\r\n") - ..write("Host: x.y.org\r\n") - ..write("User-Agent: MoonBit\r\n") - .write("Transfer-Encoding: chunked\r\n\r\n") + ..write_text("GET / HTTP/1.1\r\n") + ..write_text("Host: x.y.org\r\n") + ..write_text("User-Agent: MoonBit\r\n") + .write_text("Transfer-Encoding: chunked\r\n\r\n") @async.sleep(50) log <+ "writing data...\n" - w.write("4\r\nabcd\r\n") + w.write_text("4\r\nabcd\r\n") @async.sleep(50) log <+ "writing data...\n" - w.write("4\r\nefgh\r\n") + w.write_text("4\r\nefgh\r\n") @async.sleep(50) log <+ "writing data...\n" - w.write("4\r\nijkl\r\n") - w.write("0\r\n\r\n") + w.write_text("4\r\nijkl\r\n") + w.write_text("0\r\n\r\n") @async.sleep(50) w - ..write("GET / HTTP/1.1\r\n") - ..write("Host: z.w.org\r\n") - ..write("User-Agent: MoonBit\r\n") - ..write("Content-Length: 8\r\n\r\n") - .write("message2") + ..write_text("GET / HTTP/1.1\r\n") + ..write_text("Host: z.w.org\r\n") + ..write_text("User-Agent: MoonBit\r\n") + ..write_text("Content-Length: 8\r\n\r\n") + .write_text("message2") } defer r.close() let input = Reader::new(r) let request = input.read_request() log_request(request, log) - log..write_string(input.read_all().text()).write_string("\n\n=========\n\n") + log..write_string(input.read_all_text()).write_string("\n\n=========\n\n") let request = input.read_request() log_request(request, log) - log.write_string(input.read_all().text()) + log.write_string(input.read_all_text()) } inspect( log.to_string(), @@ -273,9 +273,9 @@ async test "read_response basic" { root.spawn_bg() <| () => { defer w.close() w - ..write("HTTP/1.1 200 OK\r\n") - ..write("Content-Length: 7\r\n\r\n") - .write("message") + ..write_text("HTTP/1.1 200 OK\r\n") + ..write_text("Content-Length: 7\r\n\r\n") + .write_text("message") } defer r.close() let input = Reader::new(r) @@ -284,7 +284,7 @@ async test "read_response basic" { auto_decompress=true, ) log_response(response, log) - log.write_string(input.read_all().text()) + log.write_string(input.read_all_text()) } inspect( log.to_string(), @@ -304,12 +304,12 @@ async test "read_response passthrough fallback (no length headers)" { root.spawn_bg() <| () => { defer w.close() w - ..write("HTTP/1.1 200 OK\r\n") - ..write("Connection: close\r\n") - ..write("Content-Type: text/plain\r\n\r\n") + ..write_text("HTTP/1.1 200 OK\r\n") + ..write_text("Connection: close\r\n") + ..write_text("Content-Type: text/plain\r\n\r\n") // No Content-Length or Transfer-Encoding // Body ends when connection closes - .write("Hello, World!") + .write_text("Hello, World!") } defer r.close() let input = Reader::new(r) @@ -320,7 +320,7 @@ async test "read_response passthrough fallback (no length headers)" { log_response(response, log) // Read body via PassThrough fallback (reads until EOF/connection close) - log.write_string(input.read_all().text()) + log.write_string(input.read_all_text()) } inspect( log.to_string(), @@ -341,9 +341,9 @@ async test "read_response 204 No Content (no body)" { root.spawn_bg() <| () => { defer w.close() w - ..write("HTTP/1.1 204 No Content\r\n") - ..write("Connection: keep-alive\r\n") - .write("Date: Mon, 15 Feb 2026 00:00:00 GMT\r\n\r\n") + ..write_text("HTTP/1.1 204 No Content\r\n") + ..write_text("Connection: keep-alive\r\n") + .write_text("Date: Mon, 15 Feb 2026 00:00:00 GMT\r\n\r\n") // 204 responses MUST NOT contain a message body // Connection remains open for next request } @@ -355,7 +355,7 @@ async test "read_response 204 No Content (no body)" { ) log_response(response, log) // Body should be Empty, not PassThrough - let body_text = input.read_all().text() + let body_text = input.read_all_text() log <+ "body: " log.write_string(body_text) } @@ -378,9 +378,9 @@ async test "read_response 205 Reset Content (no body)" { root.spawn_bg() <| () => { defer w.close() w - ..write("HTTP/1.1 205 Reset Content\r\n") - ..write("Connection: keep-alive\r\n") - .write("Date: Mon, 15 Feb 2026 00:00:00 GMT\r\n\r\n") + ..write_text("HTTP/1.1 205 Reset Content\r\n") + ..write_text("Connection: keep-alive\r\n") + .write_text("Date: Mon, 15 Feb 2026 00:00:00 GMT\r\n\r\n") // 205 responses MUST NOT contain a message body // Connection remains open for next request } @@ -392,7 +392,7 @@ async test "read_response 205 Reset Content (no body)" { ) log_response(response, log) // Body should be Empty, not WaitConnectionClose - let body_text = input.read_all().text() + let body_text = input.read_all_text() log <+ "body: " log.write_string(body_text) } @@ -415,10 +415,10 @@ async test "read_response 304 Not Modified (no body)" { root.spawn_bg() <| () => { defer w.close() w - ..write("HTTP/1.1 304 Not Modified\r\n") - ..write("Connection: keep-alive\r\n") - ..write("ETag: \"abc123\"\r\n") - .write("Cache-Control: max-age=3600\r\n\r\n") + ..write_text("HTTP/1.1 304 Not Modified\r\n") + ..write_text("Connection: keep-alive\r\n") + ..write_text("ETag: \"abc123\"\r\n") + .write_text("Cache-Control: max-age=3600\r\n\r\n") // 304 responses MUST NOT contain a message body } defer r.close() @@ -428,7 +428,7 @@ async test "read_response 304 Not Modified (no body)" { auto_decompress=true, ) log_response(response, log) - let body_text = input.read_all().text() + let body_text = input.read_all_text() log <+ "body: " log.write_string(body_text) } @@ -452,8 +452,8 @@ async test "read_response 100 Continue (no body)" { root.spawn_bg() <| () => { defer w.close() w - ..write("HTTP/1.1 100 Continue\r\n") - .write("\r\n") + ..write_text("HTTP/1.1 100 Continue\r\n") + .write_text("\r\n") // 1xx responses MUST NOT contain a message body } defer r.close() @@ -463,7 +463,7 @@ async test "read_response 100 Continue (no body)" { auto_decompress=true, ) log_response(response, log) - let body_text = input.read_all().text() + let body_text = input.read_all_text() log <+ "body: " log.write_string(body_text) } @@ -484,8 +484,8 @@ async test "read_response CONNECT 200 (no body, tunnel mode)" { root.spawn_bg() <| () => { defer w.close() w - ..write("HTTP/1.1 200 Connection Established\r\n") - .write("\r\n") + ..write_text("HTTP/1.1 200 Connection Established\r\n") + .write_text("\r\n") // Successful CONNECT responses switch to tunnel mode // Connection remains open for tunneled traffic } @@ -497,7 +497,7 @@ async test "read_response CONNECT 200 (no body, tunnel mode)" { ) log_response(response, log) // Body should be Empty, not WaitConnectionClose - let body_text = input.read_all().text() + let body_text = input.read_all_text() log <+ "body: " log.write_string(body_text) } @@ -518,10 +518,10 @@ async test "read_response HEAD 200 (no body)" { root.spawn_bg() <| () => { defer w.close() w - ..write("HTTP/1.1 200 OK\r\n") - ..write("Content-Length: 1234\r\n") - ..write("Content-Type: text/html\r\n") - .write("\r\n") + ..write_text("HTTP/1.1 200 OK\r\n") + ..write_text("Content-Length: 1234\r\n") + ..write_text("Content-Type: text/html\r\n") + .write_text("\r\n") // HEAD responses never have a body, even with Content-Length // Connection remains open for next request } @@ -533,7 +533,7 @@ async test "read_response HEAD 200 (no body)" { ) log_response(response, log) // Body should be Empty, not Fixed(1234) - let body_text = input.read_all().text() + let body_text = input.read_all_text() log <+ "body: " log.write_string(body_text) } @@ -758,23 +758,23 @@ async test "gzip split between chunks" { let (r, w) = @io.pipe() group.spawn_bg() <| () => { defer w.close() - @gzip_stream.Encoder(w)..write("A simple message to compress").end() + @gzip_stream.Encoder(w)..write_text("A simple message to compress").end() } defer r.close() - r.read_all().binary() + r.read_all() }) let (r, w) = @io.pipe() @async.with_task_group() <| group => { group.spawn_bg() <| () => { defer w.close() w - ..write("HTTP/1.1 200 OK\r\n") - ..write("Transfer-Encoding: Chunked\r\n") - .write("Content-Encoding: gzip\r\n\r\n") + ..write_text("HTTP/1.1 200 OK\r\n") + ..write_text("Transfer-Encoding: Chunked\r\n") + .write_text("Content-Encoding: gzip\r\n\r\n") for i in 0.. { defer w.close() w - ..write("HTTP/1.1 200 OK\r\n") - ..write("Set-Cookie: Cookie1=value1\r\n") - ..write( + ..write_text("HTTP/1.1 200 OK\r\n") + ..write_text("Set-Cookie: Cookie1=value1\r\n") + ..write_text( "Set-Cookie: Cookie2=value2; Path=/path/to/xxx; Max-Age=420000; Secure\r\n", ) - ..write( + ..write_text( "Set-Cookie: Cookie3=value3; Expires=Sun, 06 Nov 1994 08:49:37 GMT; Domain=example.moonbitlang.com; HttpOnly\r\n", ) - ..write("Set-Cookie: Cookie4=value four; ExtNoArg; ExtWithArg=xxx\r\n") - .write("Content-Length: 0\r\n\r\n") + ..write_text( + "Set-Cookie: Cookie4=value four; ExtNoArg; ExtWithArg=xxx\r\n", + ) + .write_text("Content-Length: 0\r\n\r\n") } defer r.close() let input = Reader::new(r) diff --git a/src/http/pkg.generated.mbti b/src/http/pkg.generated.mbti index 7b83454c..61cf9d85 100644 --- a/src/http/pkg.generated.mbti +++ b/src/http/pkg.generated.mbti @@ -10,16 +10,16 @@ import { // Values #label_migration(body, fill=false, msg="`GET` request should not contain a body") -pub async fn get(String, headers? : Map[String, String], body? : &@io.Data, proxy? : Client) -> (Response, &@io.Data) +pub async fn get(String, headers? : Map[String, String], body? : BytesView, proxy? : Client) -> (Response, Bytes) #label_migration(body, fill=false, msg="`GET` request should not contain a body") -pub async fn get_stream(String, headers? : Map[String, String], body? : &@io.Data, proxy? : Client) -> (Response, Client) +pub async fn get_stream(String, headers? : Map[String, String], body? : BytesView, proxy? : Client) -> (Response, Client) -pub async fn post(String, &@io.Data, headers? : Map[String, String], proxy? : Client) -> (Response, &@io.Data) +pub async fn post(String, BytesView, headers? : Map[String, String], proxy? : Client) -> (Response, Bytes) pub async fn post_stream(String, headers? : Map[String, String], proxy? : Client) -> Client -pub async fn put(String, &@io.Data, headers? : Map[String, String], proxy? : Client) -> (Response, &@io.Data) +pub async fn put(String, BytesView, headers? : Map[String, String], proxy? : Client) -> (Response, Bytes) pub async fn put_stream(String, headers? : Map[String, String], proxy? : Client) -> Client @@ -57,9 +57,9 @@ pub async fn Client::end_request(Self) -> Response pub async fn Client::enter_passthrough_mode(Self) -> Unit pub async fn Client::flush(Self) -> Unit #label_migration(body, fill=false, msg="`GET` request should not contain a body") -pub async fn Client::get(Self, String, extra_headers? : Map[String, String], body? : &@io.Data) -> Response -pub async fn Client::post(Self, String, &@io.Data, extra_headers? : Map[String, String]) -> Response -pub async fn Client::put(Self, String, &@io.Data, extra_headers? : Map[String, String]) -> Response +pub async fn Client::get(Self, String, extra_headers? : Map[String, String], body? : BytesView) -> Response +pub async fn Client::post(Self, String, BytesView, extra_headers? : Map[String, String]) -> Response +pub async fn Client::put(Self, String, BytesView, extra_headers? : Map[String, String]) -> Response pub async fn Client::request(Self, RequestMethod, StringView, extra_headers? : Map[String, String]) -> Unit pub async fn Client::skip_response_body(Self) -> Unit pub impl @io.Reader for Client @@ -141,7 +141,7 @@ pub async fn ServerConnection::read_request(Self) -> Request pub async fn ServerConnection::send_response(Self, Int, String, extra_headers? : Map[String, String], cookies? : Array[Cookie]) -> Unit pub async fn ServerConnection::skip_request_body(Self) -> Unit pub async fn ServerConnection::write_string(Self, String) -> Unit -pub async fn ServerConnection::write_string_interpolation(Self, &@io.Data) -> Unit +pub async fn ServerConnection::write_string_interpolation(Self, &Show) -> Unit pub impl @io.Reader for ServerConnection pub impl @io.Writer for ServerConnection diff --git a/src/http/proxy_test.mbt b/src/http/proxy_test.mbt index cbff46c6..16c07db0 100644 --- a/src/http/proxy_test.mbt +++ b/src/http/proxy_test.mbt @@ -40,7 +40,7 @@ async test "passthrough mode" { client.enter_passthrough_mode() for packet in ["abcd", "efgh", "ijkl"] { log.push("client sending: \{packet}") - client.write(packet) + client.write_text(packet) @async.sleep(200) } } @@ -64,13 +64,13 @@ async test "passthrough mode remaining data" { debug_inspect(request.meth, content="Get") inspect(request.path, content="/") conn.enter_passthrough_mode() - inspect(conn.read_all().text(), content="remaining data") + inspect(conn.read_all_text(), content="remaining data") } let client = @socket.Tcp::connect( @socket.Addr::parse("127.0.0.1:\{server_addr.port()}"), ) defer client.close() - client.write("GET / HTTP/1.1\r\n\r\nremaining data") + client.write_text("GET / HTTP/1.1\r\n\r\nremaining data") } } @@ -124,7 +124,7 @@ async test "proxied https request" { "https://www.moonbitlang.com", proxy=@http.Client("http://localhost:\{port}"), ) - assert_true(result.text().has_prefix("")) + assert_true(@utf8.decode(result).has_prefix("")) } json_inspect(log, content=[ "proxy server: connected to www.moonbitlang.com:443 successfully", @@ -146,7 +146,7 @@ async test "proxied http request" { "http://www.example.org", proxy=@http.Client("http://localhost:\{port}"), ) - assert_true(result.text().has_prefix("")) + assert_true(@utf8.decode(result).has_prefix("")) } json_inspect(log, content=[ "proxy server: connected to www.example.org:80 successfully", @@ -171,7 +171,7 @@ async test "proxied misc request" { let conn = @http.ServerConnection::new(conn) for ;; { let request = conn.read_request() - let body = conn.read_all().text() + let body = conn.read_all_text() log.push( "target server: received request \{@debug.to_string(request.meth)} \{request.path} \{body}", ) @@ -184,8 +184,8 @@ async test "proxied misc request" { } let _ = @http.get("http://localhost:\{port}", proxy=proxy()) - let _ = @http.put("http://localhost:\{port}", "abcd", proxy=proxy()) - let _ = @http.post("http://localhost:\{port}", "efgh", proxy=proxy()) + let _ = @http.put("http://localhost:\{port}", b"abcd", proxy=proxy()) + let _ = @http.post("http://localhost:\{port}", b"efgh", proxy=proxy()) } for i in 0.. (Response, &@io.Data) { +) -> (Response, Bytes) { let (protocol, host, path) = resolve_url(uri) let client = Client::connect(host, headers~, protocol~, proxy?) defer client.close() @@ -100,9 +100,9 @@ async fn perform_request( pub async fn get( uri : String, headers? : Map[String, String] = {}, - body? : &@io.Data = b"", + body? : BytesView = b"", proxy? : Client, -) -> (Response, &@io.Data) { +) -> (Response, Bytes) { perform_request(uri, Get, headers, body, proxy?) } @@ -110,10 +110,10 @@ pub async fn get( /// Similar to `get`, but performs a `PUT` request instead. pub async fn put( uri : String, - content : &@io.Data, + content : BytesView, headers? : Map[String, String] = {}, proxy? : Client, -) -> (Response, &@io.Data) { +) -> (Response, Bytes) { perform_request(uri, Put, headers, content, proxy?) } @@ -121,10 +121,10 @@ pub async fn put( /// Similar to `get`, but performs a `POST` request instead. pub async fn post( uri : String, - content : &@io.Data, + content : BytesView, headers? : Map[String, String] = {}, proxy? : Client, -) -> (Response, &@io.Data) { +) -> (Response, Bytes) { perform_request(uri, Post, headers, content, proxy?) } @@ -142,7 +142,7 @@ pub async fn post( pub async fn get_stream( uri : String, headers? : Map[String, String] = {}, - body? : &@io.Data = b"", + body? : BytesView = b"", proxy? : Client, ) -> (Response, Client) { let (protocol, host, path) = resolve_url(uri) diff --git a/src/http/request_test.mbt b/src/http/request_test.mbt index cfdb3f86..01473c90 100644 --- a/src/http/request_test.mbt +++ b/src/http/request_test.mbt @@ -15,13 +15,13 @@ ///| async test "https request" { let (_, result) = @http.get("https://www.moonbitlang.com") - assert_true(result.text().has_prefix("")) + assert_true(@utf8.decode(result).has_prefix("")) } ///| async test "http request" { let (_, result) = @http.get("http://www.example.org") - assert_true(result.text().has_prefix("")) + assert_true(@utf8.decode(result).has_prefix("")) } ///| @@ -65,7 +65,7 @@ async test "manual Accept-Encoding" { let (_, result2) = @http.get("http://\{test_server.addr}", headers={ "Accept-Encoding": "gzip", }) - assert_not_eq(result1.binary().length(), result2.binary().length()) + assert_not_eq(result1.length(), result2.length()) json_inspect(log, content=[ "server received: Some(\"gzip,identity\")", "server received: Some(\"gzip\")", ]) diff --git a/src/http/send.mbt b/src/http/send.mbt index ff1d4a19..2fb3b071 100644 --- a/src/http/send.mbt +++ b/src/http/send.mbt @@ -277,7 +277,7 @@ async fn Sender::send_headers( } else if forbidden_headers.contains(k_lower) { continue } - self..write(k)..write(b": ")..write(v).write(b"\r\n") + self..write_text(k)..write(b": ")..write_text(v).write(b"\r\n") } content_length } @@ -304,7 +304,7 @@ async fn Sender::send_request( Patch => self.write(b"PATCH ") } let content_length = self - ..write(path) + ..write_text(path) ..write(b" HTTP/1.1\r\n") ..write(self.headers) .send_headers(extra_headers) @@ -342,7 +342,7 @@ async fn Sender::send_response( ..write(b"HTTP/1.1 ") ..write(encode_int(code, base=10)) ..write(b" ") - ..write(reason) + ..write_text(reason) ..write(b"\r\n") ..write(self.headers) .send_headers(extra_headers) diff --git a/src/http/send_wbtest.mbt b/src/http/send_wbtest.mbt index 5739f4a4..2b758b06 100644 --- a/src/http/send_wbtest.mbt +++ b/src/http/send_wbtest.mbt @@ -27,7 +27,7 @@ async test "send_request basic" { } defer r.close() inspect( - r.read_all().binary() |> @bytes_util.ascii_to_string, + r.read_all() |> @bytes_util.ascii_to_string, content=( #|GET / HTTP/1.1\r #|Host: x.y.org\r @@ -57,7 +57,7 @@ async test "send_request stream" { } defer r.close() inspect( - r.read_all().binary() |> @bytes_util.ascii_to_string, + r.read_all() |> @bytes_util.ascii_to_string, content=( #|GET / HTTP/1.1\r #|Host: x.y.org\r @@ -310,16 +310,16 @@ async test "send_request stream2" { "Host": "x.y.org", "User-Agent": "MoonBit", }) - sender.write("abcd") + sender.write_text("abcd") @async.sleep(50) - sender.write("efgh") + sender.write_text("efgh") @async.sleep(50) - sender.write("ijkl") + sender.write_text("ijkl") sender.end_body() } defer r.close() inspect( - r.read_all().binary() |> @bytes_util.ascii_to_string, + r.read_all() |> @bytes_util.ascii_to_string, content=( #|GET / HTTP/1.1\r #|Host: x.y.org\r @@ -357,7 +357,7 @@ async test "send invalid header" { } defer r.close() inspect( - r.read_all().binary() |> @bytes_util.ascii_to_string, + r.read_all() |> @bytes_util.ascii_to_string, content=( #|GET / HTTP/1.1\r #|Host: x.y.org\r @@ -388,14 +388,14 @@ async test "send fixed length" { "Content-Length": total_size.to_string(), }) sender - ..write(header) + ..write_text(header) ..write_reader(license_file) - ..write(trailer) + ..write_text(trailer) .end_body() } defer r.close() inspect( - r.read_all().binary() |> @bytes_util.ascii_to_string, + r.read_all() |> @bytes_util.ascii_to_string, content=( #|GET / HTTP/1.1\r #|Content-Length: 11397\r @@ -717,7 +717,7 @@ async test "send_response cookies" { } defer r.close() inspect( - r.read_all().binary() |> @bytes_util.ascii_to_string, + r.read_all() |> @bytes_util.ascii_to_string, content=( #|HTTP/1.1 200 OK\r #|Set-Cookie: Cookie1=value1\r @@ -740,7 +740,7 @@ async test "send_request empty body" { defer w.close() let sender = Sender::new(w) let _ = sender.send_request(Get, "/", extra_headers={}) - sender..write("").end_body() + sender..write_text("").end_body() let _ = sender.send_request(Head, "/", extra_headers={}) sender.end_body() let _ = sender.send_request(Post, "/", extra_headers={}) @@ -748,7 +748,7 @@ async test "send_request empty body" { } defer r.close() inspect( - r.read_all().binary() |> @bytes_util.ascii_to_string, + r.read_all() |> @bytes_util.ascii_to_string, content=( #|GET / HTTP/1.1\r #|Accept-Encoding: gzip,identity\r @@ -776,17 +776,17 @@ async test "send_response empty body" { let _ = sender.send_response(200, "OK", cookies=[], request_method=Get, extra_headers={}) sender.end_body() let _ = sender.send_response(200, "OK", cookies=[], request_method=Head, extra_headers={}) - sender..write("").end_body() + sender..write_text("").end_body() let _ = sender.send_response(102, "OK", cookies=[], request_method=Get, extra_headers={}) sender.end_body() let _ = sender.send_response(204, "OK", cookies=[], request_method=Get, extra_headers={}) - sender..write("").end_body() + sender..write_text("").end_body() let _ = sender.send_response(304, "OK", cookies=[], request_method=Get, extra_headers={}) sender.end_body() } defer r.close() inspect( - r.read_all().binary() |> @bytes_util.ascii_to_string, + r.read_all() |> @bytes_util.ascii_to_string, content=( #|HTTP/1.1 200 OK\r #|Content-Length: 0\r @@ -820,7 +820,7 @@ async test "sender persistent header" { } defer r.close() inspect( - r.read_all().binary() |> @bytes_util.ascii_to_string, + r.read_all() |> @bytes_util.ascii_to_string, content=( #|GET / HTTP/1.1\r #|Host: x.y.org\r diff --git a/src/http/server.mbt b/src/http/server.mbt index 60b8b34d..6087ca43 100644 --- a/src/http/server.mbt +++ b/src/http/server.mbt @@ -117,11 +117,13 @@ pub impl @io.Writer for ServerConnection with fn write_once( } ///| +/// Write the display representation of a value used in string interpolation. +/// Use `write` for raw bytes and `write_json` for JSON wire data. pub async fn ServerConnection::write_string_interpolation( self : ServerConnection, - data : &@io.Data, + data : &Show, ) -> Unit { - self.write(data) + self.write_text(data.to_string()) } ///| @@ -131,7 +133,7 @@ pub async fn ServerConnection::write_string( self : ServerConnection, s : String, ) -> Unit { - @io.Writer::write(self, s) + self.write_text(s) } ///| diff --git a/src/internal/event_loop/missing_close_test.mbt b/src/internal/event_loop/missing_close_test.mbt index 45625c3f..963e3782 100644 --- a/src/internal/event_loop/missing_close_test.mbt +++ b/src/internal/event_loop/missing_close_test.mbt @@ -26,7 +26,7 @@ test "missing_close" { } root.spawn_bg() <| () => { let data = "abcd" - w.write(data) + @io.Writer::write_text(w, data) log.write_string("written \{data}\n") } } diff --git a/src/internal/gzip_internal/gzip_internal_test.mbt b/src/internal/gzip_internal/gzip_internal_test.mbt index cbba25b9..9daa1a37 100644 --- a/src/internal/gzip_internal/gzip_internal_test.mbt +++ b/src/internal/gzip_internal/gzip_internal_test.mbt @@ -322,7 +322,7 @@ test "gzip encoder hash table survives history wrap" { ///| #cfg(target="native") async test "gzip encode min input source file" { - let data = @fs.read_file("src/internal/gzip_internal/decoder.mbt").binary() + let data = @fs.read_file("src/internal/gzip_internal/decoder.mbt") let compressed = encode(data, input_step=Min) let data2 = decode(compressed, expected_len=data.length()) assert_eq(data, data2) @@ -331,7 +331,7 @@ async test "gzip encode min input source file" { ///| #cfg(target="native") async test "gzip encode min output source file" { - let data = @fs.read_file("src/internal/gzip_internal/decoder.mbt").binary() + let data = @fs.read_file("src/internal/gzip_internal/decoder.mbt") let compressed = encode(data, output_step=Min) let data2 = decode(compressed, expected_len=data.length()) assert_eq(data, data2) @@ -340,7 +340,7 @@ async test "gzip encode min output source file" { ///| #cfg(target="native") async test "gzip decode min input source file" { - let data = @fs.read_file("src/internal/gzip_internal/decoder.mbt").binary() + let data = @fs.read_file("src/internal/gzip_internal/decoder.mbt") let compressed = encode(data) let data2 = decode(compressed, expected_len=data.length(), input_step=Min) assert_eq(data, data2) @@ -349,7 +349,7 @@ async test "gzip decode min input source file" { ///| #cfg(target="native") async test "gzip decode min output source file" { - let data = @fs.read_file("src/internal/gzip_internal/decoder.mbt").binary() + let data = @fs.read_file("src/internal/gzip_internal/decoder.mbt") let compressed = encode(data) let data2 = decode(compressed, expected_len=data.length(), output_step=Min) assert_eq(data, data2) diff --git a/src/io/README.mbt.md b/src/io/README.mbt.md index 81047841..4632273b 100644 --- a/src/io/README.mbt.md +++ b/src/io/README.mbt.md @@ -8,7 +8,7 @@ Asynchronous I/O abstraction for MoonBit. This package provides fundamental abst - [Core Traits](#core-traits) - [Reader Trait](#reader-trait) - [Writer Trait](#writer-trait) - - [Data Trait](#data-trait) + - [Byte and Text Data](#byte-and-text-data) - [wrap in-memory data into reader](#wrap-in-memory-data-into-reader) - [Buffered I/O](#buffered-io) - [BufferedReader](#bufferedreader) @@ -42,7 +42,7 @@ async test "quick start pipeline" { // Read everything that arrives and print it as text. defer reader.close() - let message = reader.read_all().text() + let message = reader.read_all_text() inspect(message, content="Hello, MoonBit!\n") }) } @@ -52,18 +52,18 @@ This pattern generalises to sockets, files, and any type that implements the `Re ## Core Traits -### Data Trait +### Byte and Text Data -`Data` is a lightweight trait for types that can be read/write via IO endpoints. -On the sender side, `Data` is implemented by `Bytes`, `BytesView`, `String`, `StringView` and `Json`. -If an API accepts `&Data` as input (for example `Writer::write`), any value of the above types can be passed directly. -On the receiver side, the trait object type `&Data` provides helper methods `.binary()`, `.text()` and `.json()`, -which converts receive data to raw binary/UTF-8 encoded string/UTF-8 encoded JSON string in respect. -This allows convenient conversion of received data to various formats. +I/O streams fundamentally transfer bytes. `Writer::write` accepts a `BytesView`, +which can represent an entire `Bytes` value or a zero-copy slice of one. +`Reader::read_all` returns the raw content as `Bytes`. + +Text and JSON conversions are explicit: use `write_text`/`read_all_text` for +UTF-8 text and `write_json`/`read_all_json` for JSON. ```moonbit check ///| -async test "data as binary" { +async test "raw bytes" { @async.with_task_group(root => { let (r, w) = @io.pipe() defer r.close() @@ -72,30 +72,25 @@ async test "data as binary" { // Send raw binary bytes. w.write(b"binary data") }) - let data = r.read_all() - let binary = data.binary() - inspect(@utf8.decode(binary), content="binary data") + assert_eq(r.read_all(), b"binary data") }) } ///| -async test "data as text" { +async test "UTF-8 text" { @async.with_task_group(root => { let (r, w) = @io.pipe() defer r.close() root.spawn_bg(() => { defer w.close() - // Send UTF-8 encoded string - w.write("Hello, MoonBit!") + w.write_text("Hello, MoonBit!") }) - let data = r.read_all() - // Decoding happens lazily when `.text()` is invoked. - inspect(data.text(), content="Hello, MoonBit!") + inspect(r.read_all_text(), content="Hello, MoonBit!") }) } ///| -async test "data as json" { +async test "JSON" { @async.with_task_group(root => { let (r, w) = @io.pipe() defer r.close() @@ -103,12 +98,9 @@ async test "data as json" { defer w.close() // send UTF-8 encoded JSON string let data : Json = { "name": "John", "age": 30 } - w.write(data) + w.write_json(data) }) - let data = r.read_all() - let json = data.json() - // `json_inspect` asserts that the parsed JSON matches the expected structure. - json_inspect(json, content={ "name": "John", "age": 30 }) + json_inspect(r.read_all_json(), content={ "name": "John", "age": 30 }) }) } ``` @@ -120,7 +112,8 @@ The `Reader` trait exposes three complementary levels of granularity: - `read` performs a best-effort read into an existing buffer and is ideal for incremental protocols. A zero return value indicates EOF. - `read_exactly` loops until it fills the requested number of bytes or raises `ReaderClosed`, which is helpful when parsing fixed-width frames. - `read_some` read the next chunk of data once any new data is available -- `read_all` drains the stream into memory, returning a `&Data` handle for convenient conversion to text, JSON, or binary. +- `read_all` drains the stream into memory and returns the raw `Bytes`. +- `read_all_text` and `read_all_json` drain the stream and decode UTF-8 text or JSON explicitly. - `read_until` read UTF-8 encoded string from the input until a separator is reached Each example below shows how a reader pairs with `@io.pipe()` and includes inline comments that highlight the important steps. @@ -175,11 +168,11 @@ async test "read_some - read next chunk of data" { root.spawn_bg(() => { defer w.close() // the writer supplieds data in two chunks - w.write("abcd") + w.write_text("abcd") @async.sleep(200) - w.write("efgh") + w.write_text("efgh") @async.sleep(200) - w.write("ijkl") + w.write_text("ijkl") }) debug_inspect( r.read_some(), @@ -221,10 +214,9 @@ async test "read_all - read entire content" { defer w.close() w.write(b"Complete content") }) - // `read_all` accumulates everything into a `&Data` handle. + // `read_all` accumulates everything into raw bytes. let data = r.read_all() - // Convert to text on demand. - inspect(data.text(), content="Complete content") + inspect(@utf8.decode(data), content="Complete content") }) } @@ -238,7 +230,7 @@ async test "read_all large data" { // Large payloads can be streamed without precomputing the size. w.write(Bytes::make(4097, 0)) }) - inspect(r.read_all().binary().length(), content="4097") + inspect(r.read_all().length(), content="4097") }) } @@ -266,8 +258,8 @@ async test "read_until - read text from stream until a separator is found" { defer r.close() root.spawn_bg(() => { defer w.close() - w.write("abcd|") - w.write("defg") + w.write_text("abcd|") + w.write_text("defg") }) // read until the separator "|" is met. The separator will be consumed as well debug_inspect( @@ -299,7 +291,7 @@ async test "read_until - used to read file line by line" { lines.push(line) } inspect(lines.length(), content="202") - assert_eq(lines.join("\n") + "\n", @fs.read_file("LICENSE").text()) + assert_eq(lines.join("\n") + "\n", @utf8.decode(@fs.read_file("LICENSE"))) } ``` @@ -313,19 +305,19 @@ The reader reaches EOF when the callback returns normally. If the callback raise ///| async test "MemoryReader - generate reader content in memory" { let r = @io.MemoryReader() <| w => { - w.write("hello, ") + w.write_text("hello, ") @async.sleep(10) - w.write("MoonBit") + w.write_text("MoonBit") } defer r.close() - inspect(r.read_all().text(), content="hello, MoonBit") + inspect(r.read_all_text(), content="hello, MoonBit") } ///| async test "MemoryReader - stream generated chunks" { let r = @io.MemoryReader() <| w => { for part in ["ab", "cd", "ef"] { - w.write(part) + w.write_text(part) @async.sleep(10) } } @@ -356,8 +348,9 @@ async test "MemoryReader - stream generated chunks" { `Writer` focuses on pushing bytes downstream. The three key helpers build on top of the fundamental `write_once` contract: -- `write_once` performs a single best-effort write and returns how many bytes were accepted. Note that partial write is allowed for `write_once`: only a portion of data may be succesfully written. End users should not call `write_once` directly in general. -- `write` encodes and writes a `&Data` to the writer, is will keep calling `write_once` until all data are succesfully written. This is the most common helper users should use. +- `write_once` performs a single best-effort write and returns how many bytes were accepted. Partial writes are allowed, but a non-empty request must accept at least one byte and must never report more than the requested length. End users should not call `write_once` directly in general. +- `write` writes a `BytesView`, repeatedly calling `write_once` until all bytes are written. +- `write_text` UTF-8 encodes a string before writing it, while `write_json` serializes JSON as UTF-8. - `write_reader` streams from any `Reader`, making it perfect for relaying pipes, sockets, or files without copying into intermediate strings. The snippets below demonstrate each mode with progressive complexity. @@ -377,7 +370,7 @@ async test "write to writer" { }) // `read_all` collapses everything for verification. let data = r.read_all() - inspect(data.text(), content="Hello, World!") + inspect(@utf8.decode(data), content="Hello, World!") }) } @@ -394,7 +387,7 @@ async test "write_once - single write operation" { inspect(written, content="9") }) let content = r.read_all() - inspect(content.text(), content="Test data") + inspect(@utf8.decode(content), content="Test data") }) } @@ -410,7 +403,7 @@ async test "write large data" { }) root.spawn_bg(() => { defer r.close() - inspect(r.read_all().binary().length(), content="16384") + inspect(r.read_all().length(), content="16384") }) }) } @@ -469,9 +462,9 @@ async test "write string" { root.spawn_bg(() => { defer w.close() // Unicode data is transparently encoded via UTF-8. - w.write("abcd中文☺") + w.write_text("abcd中文☺") }) - inspect(r.read_all().text(), content="abcd中文☺") + inspect(r.read_all_text(), content="abcd中文☺") }) } ``` @@ -492,13 +485,13 @@ async test "BufferedWriter - basic buffering" { defer w.close() let w = @io.BufferedWriter::new(w, size=4) log <+ "2 bytes written\n" - w.write("ab") + w.write_text("ab") @async.sleep(20) log <+ "2 bytes written\n" - w.write("cd") + w.write_text("cd") @async.sleep(20) log <+ "2 bytes written\n" - w.write("ef") + w.write_text("ef") // Force the remaining bytes out of the buffer. w.flush() }) @@ -536,7 +529,7 @@ async test "BufferedWriter::new with custom size" { w.flush() }) let data = r.read_all() - inspect(data.text(), content="test") + inspect(@utf8.decode(data), content="test") }) } @@ -555,7 +548,7 @@ async test "BufferedWriter::flush - commit buffered data" { w.flush() }) let data = r.read_all() - inspect(data.text(), content="buffermore") + inspect(@utf8.decode(data), content="buffermore") }) } @@ -622,29 +615,19 @@ Efficient asynchronous I/O is mostly about balancing throughput and memory usage Trait for reading data from a source. Methods include: - `read(buffer, offset?, max_len?)` - Read data into buffer - `read_exactly(len)` - Read exact number of bytes -- `read_all()` - Read entire content +- `read_all()` - Read entire content as raw bytes +- `read_all_text()` - Read entire content as UTF-8 text +- `read_all_json()` - Read and parse UTF-8 encoded JSON ### Writer Trait for writing data to a destination. Methods include: - `write_once(data, offset~, len~)` - Single write operation -- `write(data)` - Write data (may require multiple operations) +- `write(data)` - Write a byte view (may require multiple operations) +- `write_text(text)` - UTF-8 encode and write text +- `write_json(value)` - Serialize and write JSON - `write_reader(reader)` - Copy data from reader to writer -### Data - -Trait for abstracting various data formats. Implementations: -- `Bytes` - Raw binary data -- `BytesView` - View of binary data -- `String` - UTF-8 encoded string -- `StringView` - View of string -- `Json` - JSON data - -Helper methods for received data: -- `binary()` - Extract raw binary data -- `text()` - Decode as UTF-8 string -- `json()` - Decode as JSON - ### BufferedReader A buffered reader that wraps around a normal reader. Methods include: @@ -673,6 +656,8 @@ A buffered writer with fixed-size buffer. Methods include: - `flush()` - Commit buffered data - `write_once(data, offset~, len~)` - Single write operation - `write(data)` - Write data +- `write_text(text)` - UTF-8 encode and write text +- `write_json(value)` - Serialize and write JSON - `write_reader(reader)` - Copy from reader ### Encoding @@ -689,7 +674,6 @@ Error raised when attempting to read from a closed reader. 1. **Only one reader/writer at the same time**. Attempting to read from/write to the same reader/writer from multiple tasks easily lead to race condition. When multiple readers/writers are needed, it is recommended to adapt the actor pattern by spawning a dedicated reader/writer task and use `@aqueue.Queue` to distribute data atomically. 2. **Use buffered I/O for performance**: Wrap readers and writers with `BufferedReader` and `BufferedWriter` when performing many small reads or writes. -3. **Pick the right data view and cache the result**: Prefer `.text()` for UTF-8, `.json()` for structured payloads, and `.binary()` when forwarding raw bytes. - `.text()` and `.json()` methods need to perform decoding and parsing, so users should cache the result of these conversion methods instead of calling them repeatedly +3. **Keep byte and text operations explicit**: Use `read_all()`/`write()` for raw bytes and the text or JSON helpers only when decoding or encoding is intended. For more examples and detailed usage, explore the test suites in this package—they double as executable documentation. diff --git a/src/io/buffered_writer.mbt b/src/io/buffered_writer.mbt index 1ef0c529..5e875883 100644 --- a/src/io/buffered_writer.mbt +++ b/src/io/buffered_writer.mbt @@ -30,10 +30,12 @@ struct BufferedWriter[W] { ///| /// Create a buffered writer with fixed buffer size `size`. +/// `size` must be positive. pub fn[W] BufferedWriter::new( writer : W, size? : Int = 1024, ) -> BufferedWriter[W] { + guard size > 0 else { abort("BufferedWriter capacity must be positive") } { writer, buf: FixedArray::make(size, 0), len: 0 } } diff --git a/src/io/buffered_writer_test.mbt b/src/io/buffered_writer_test.mbt index 51cbf255..08542125 100644 --- a/src/io/buffered_writer_test.mbt +++ b/src/io/buffered_writer_test.mbt @@ -12,6 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +///| +test "panic BufferedWriter::new with zero capacity" { + ignore(@io.BufferedWriter::new((), size=0)) +} + ///| async test "BufferedWriter" { let log = StringBuilder::new() @@ -21,13 +26,13 @@ async test "BufferedWriter" { defer w.close() let w = @io.BufferedWriter::new(w, size=4) log <+ "2 bytes written\n" - w.write("ab") + w.write_text("ab") @async.sleep(20) log <+ "2 bytes written\n" - w.write("cd") + w.write_text("cd") @async.sleep(20) log <+ "2 bytes written\n" - w.write("ef") + w.write_text("ef") w.flush() } root.spawn_bg() <| () => { diff --git a/src/io/data.mbt b/src/io/data.mbt deleted file mode 100644 index 6b30b5e2..00000000 --- a/src/io/data.mbt +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2025 International Digital Economy Academy -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -///| -/// `Data` is an abstraction over various kinds of data format -/// that can be read from/write to a reader/writer. -/// Currently, available formats of data include: -/// -/// - `Bytes` and `BytesView` for raw binary data -/// - `String` and `StringView`, for string (UTF8 encoded) -/// - `Json` for JSON string (UTF8 encoded) -trait Data { - fn to_bytesview(Self) -> BytesView = _ - fn to_bytes(Self) -> Bytes -} - -///| -impl Data with fn to_bytesview(self) { - self.to_bytes()[:] -} - -///| -pub impl Data for Bytes with fn to_bytes(self) { - self -} - -///| -pub impl Data for BytesView with fn to_bytes(self) { - self.to_owned() -} - -///| -pub impl Data for BytesView with fn to_bytesview(self) { - self -} - -///| -pub impl Data for String with fn to_bytes(self) { - @utf8.encode(self) -} - -///| -pub impl Data for StringView with fn to_bytes(self) { - @utf8.encode(self) -} - -///| -pub impl Data for Json with fn to_bytes(self) { - @utf8.encode(self.stringify()) -} - -// ============================================================================== -// internal node: the `Data` trait serves two purpose. -// When the user is sending data, we use `to_binary` to convert user data to binary. -// When the user is receiving data, raw data is always represented as raw binary, -// so `&Data` always internally hold a `Bytes`, -// and the user may use these helpers below to convert them to different format. -// -// Technically we should use two types for these two purposes, -// but that would mean two different names, instead of the simple `Data`. -// So currently we take this rather hacky approach for simplicify of API. -// ============================================================================== - -///| -/// Extract the raw binary data from `&Data` -pub fn &Data::binary(self : &Data) -> Bytes { - self.to_bytes() -} - -///| -/// Decode data as a UTF8 encoded string. -/// -/// Calling this method will cause string decoding, -/// so user should avoid calling `.text()` more than once. -pub fn &Data::text(self : &Data) -> String raise { - self.to_bytes() |> @utf8.decode -} - -///| -/// Decode data as a UTF8 encoded JSON string. -/// -/// Calling this method will cause string decoding and json parsing, -/// so user should avoid calling `.json()` more than once. -pub fn &Data::json(self : &Data) -> Json raise { - self.to_bytes() |> @utf8.decode |> @json.parse -} diff --git a/src/io/memory_reader_test.mbt b/src/io/memory_reader_test.mbt index 2ce29881..30438e9a 100644 --- a/src/io/memory_reader_test.mbt +++ b/src/io/memory_reader_test.mbt @@ -18,7 +18,7 @@ async test "MemoryReader basic" { let r = @io.MemoryReader() <| w => { for data in ["abcd", "1234", "xyzw"] { log.push("writing \{data}") - w.write(data) + w.write_text(data) @async.sleep(150) } } @@ -37,7 +37,7 @@ async test "MemoryReader cancel" { let log = [] let r = @io.MemoryReader() <| w => { for i = 0; ; i = i + 1 { - try w.write("#\{i}") catch { + try w.write_text("#\{i}") catch { err => { log.push("`write` fail with \{err}") raise err @@ -67,7 +67,7 @@ async test "MemoryReader failure" { raise Failure::Failure("failure") } defer r.close() - try r.read_all().text() catch { + try r.read_all_text() catch { err => inspect( err, diff --git a/src/io/pkg.generated.mbti b/src/io/pkg.generated.mbti index b3a64d75..f61e0cf0 100644 --- a/src/io/pkg.generated.mbti +++ b/src/io/pkg.generated.mbti @@ -47,16 +47,6 @@ pub fn ReaderBuffer::new() -> Self // Type aliases // Traits -trait Data -pub impl Data for String -pub impl Data for Bytes -pub impl Data for Json -pub impl Data for BytesView -pub impl Data for StringView -pub fn &Data::binary(Self) -> Bytes -pub fn &Data::json(Self) -> Json raise -pub fn &Data::text(Self) -> String raise - pub(open) trait Reader { fn _get_internal_buffer(Self) -> ReaderBuffer async fn _direct_read(Self, FixedArray[Byte], offset~ : Int, max_len~ : Int) -> Int @@ -64,13 +54,17 @@ pub(open) trait Reader { async fn drop(Self, Int) -> Int = _ async fn read_exactly(Self, Int) -> Bytes = _ async fn read_some(Self, max_len? : Int) -> Bytes? = _ - async fn read_all(Self) -> &Data = _ + async fn read_all(Self) -> Bytes = _ + async fn read_all_text(Self) -> String = _ + async fn read_all_json(Self) -> Json = _ async fn read_until(Self, StringView) -> String? = _ } pub(open) trait Writer { async fn write_once(Self, Bytes, offset~ : Int, len~ : Int) -> Int - async fn write(Self, &Data) -> Unit = _ + async fn write(Self, BytesView) -> Unit = _ + async fn write_text(Self, StringView) -> Unit = _ + async fn write_json(Self, Json) -> Unit = _ async fn write_reader(Self, &Reader) -> Unit = _ } diff --git a/src/io/read_all_test.mbt b/src/io/read_all_test.mbt index 3e2a56b9..3ad81c93 100644 --- a/src/io/read_all_test.mbt +++ b/src/io/read_all_test.mbt @@ -33,7 +33,7 @@ async test "read_all basic" { let (r, w) = @io.pipe() defer r.close() root.spawn_bg(() => writer(log, w)) - inspect(r.read_all().text(), content="abcdefghijkl") + inspect(r.read_all_text(), content="abcdefghijkl") } } @@ -46,6 +46,19 @@ async test "read_all large data" { defer w.close() w.write(Bytes::make(4097, 0)) } - inspect(r.read_all().binary().length(), content="4097") + inspect(r.read_all().length(), content="4097") + } +} + +///| +async test "read_all preserves arbitrary bytes" { + @async.with_task_group() <| root => { + let (r, w) = @io.pipe() + defer r.close() + root.spawn_bg() <| () => { + defer w.close() + w.write(b"\xff\x00\x80") + } + assert_eq(r.read_all(), b"\xff\x00\x80") } } diff --git a/src/io/read_some_test.mbt b/src/io/read_some_test.mbt index 8ab9055f..d96e301e 100644 --- a/src/io/read_some_test.mbt +++ b/src/io/read_some_test.mbt @@ -100,7 +100,7 @@ async test "read_some length limit 3" { defer r.close() root.spawn_bg() <| () => { defer w.close() - w.write("abcdefghijkl") + w.write_text("abcdefghijkl") } for max_len = 2; ; max_len = max_len * 2 { guard r.read_some(max_len~) is Some(chunk) else { break } diff --git a/src/io/reader.mbt b/src/io/reader.mbt index 595637da..90792ee8 100644 --- a/src/io/reader.mbt +++ b/src/io/reader.mbt @@ -23,7 +23,9 @@ pub(open) trait Reader { async fn drop(Self, Int) -> Int = _ async fn read_exactly(Self, len : Int) -> Bytes = _ async fn read_some(Self, max_len? : Int) -> Bytes? = _ - async fn read_all(Self) -> &Data = _ + async fn read_all(Self) -> Bytes = _ + async fn read_all_text(Self) -> String = _ + async fn read_all_json(Self) -> Json = _ async fn read_until(Self, StringView) -> String? = _ } @@ -141,8 +143,7 @@ impl Reader with fn read_some(self, max_len?) { ///| /// Read all remaining content from the reader. -/// The return value is a `&io.Data` object, which can be converted to different formats -/// using `.binary()`, `.text()` or `.json()`. +/// The return value contains the raw bytes read from the stream. impl Reader with fn read_all(self) { let buffer_list = [] let mut buffer = FixedArray::make(1024, b'0') @@ -165,6 +166,18 @@ impl Reader with fn read_all(self) { result.unsafe_reinterpret_as_bytes() } +///| +/// Read all remaining content and decode it as UTF-8 text. +impl Reader with fn read_all_text(self) { + @utf8.decode(self.read_all()) +} + +///| +/// Read all remaining content, decode it as UTF-8, and parse it as JSON. +impl Reader with fn read_all_json(self) { + self.read_all_text() |> @json.parse +} + ///| /// `reader.read_until(sep)` read UTF-8 encoded text from the input stream /// until `sep` or EOF is reached. diff --git a/src/io/writer.mbt b/src/io/writer.mbt index 92299c23..340ef5df 100644 --- a/src/io/writer.mbt +++ b/src/io/writer.mbt @@ -19,28 +19,43 @@ pub(all) enum Encoding { ///| pub(open) trait Writer { + /// `write_once` may accept fewer than `len` bytes, but it must make progress: + /// for a non-empty request, its return value must be in `1..=len`. async fn write_once(Self, Bytes, offset~ : Int, len~ : Int) -> Int - async fn write(Self, &Data) -> Unit = _ + async fn write(Self, BytesView) -> Unit = _ + async fn write_text(Self, StringView) -> Unit = _ + async fn write_json(Self, Json) -> Unit = _ async fn write_reader(Self, &Reader) -> Unit = _ } ///| impl Writer with fn write(self, data) { - let view = data.to_bytesview() - let start = view.start_offset() - let len = view.length() + let start = data.start_offset() + let len = data.length() guard len > 0 else { return } let end = start + len - let offset = self.write_once(view.data(), offset=start, len~) - if offset == end { - return - } - for offset = start + offset; offset < end; { - let progress = self.write_once(view.data(), offset~, len=end - offset) + for offset = start; offset < end; { + let remaining = end - offset + let progress = self.write_once(data.data(), offset~, len=remaining) + guard progress > 0 && progress <= remaining else { + abort("Writer::write_once returned an invalid byte count") + } continue offset + progress } } +///| +/// Encode `text` as UTF-8 and write all encoded bytes. +impl Writer with fn write_text(self, text) { + self.write(@utf8.encode(text)) +} + +///| +/// Serialize `value` as JSON and write its UTF-8 representation. +impl Writer with fn write_json(self, value) { + self.write_text(value.stringify()) +} + ///| impl Writer with fn write_reader(self, reader) { let buf = reader._get_internal_buffer() diff --git a/src/io/writer_test.mbt b/src/io/writer_test.mbt index 146c267c..c2d678f3 100644 --- a/src/io/writer_test.mbt +++ b/src/io/writer_test.mbt @@ -23,7 +23,7 @@ async test "write large data" { } root.spawn_bg() <| () => { defer r.close() - inspect(r.read_all().binary().length(), content="16384") + inspect(r.read_all().length(), content="16384") } } } @@ -104,14 +104,60 @@ async test "write cancel" { } ///| -async test "write_string" { +async test "write_text" { @async.with_task_group() <| root => { let (r, w) = @io.pipe() defer r.close() root.spawn_bg() <| () => { defer w.close() - w.write("abcd中文☺") + w.write_text("abcd中文☺") } - inspect(r.read_all().text(), content="abcd中文☺") + inspect(r.read_all_text(), content="abcd中文☺") + } +} + +///| +struct ShortWriter { + logger : &Logger +} + +///| +impl @io.Writer for ShortWriter with fn write_once(self, buf, offset~, len~) { + let written = @cmp.minimum(len, 2) + self.logger <+ + "\{offset}:\{len}:\{@utf8.decode(buf[offset:offset + written])}\n" + written +} + +///| +async test "write a non-zero-offset byte view through short writes" { + let log = StringBuilder::new() + let writer : ShortWriter = { logger: log } + let data = b"__payload__" + writer.write(data[2:2]) + writer.write(data[2:9]) + writer.write(data[9:9]) + inspect( + log.to_string(), + content=( + #|2:7:pa + #|4:5:yl + #|6:3:oa + #|8:1:d + #| + ), + ) +} + +///| +async test "write and read JSON explicitly" { + @async.with_task_group() <| root => { + let (r, w) = @io.pipe() + defer r.close() + root.spawn_bg() <| () => { + defer w.close() + w.write_json({ "answer": 42, "ok": true }) + } + json_inspect(r.read_all_json(), content={ "answer": 42, "ok": true }) } } diff --git a/src/js_async/readable_stream_test.mbt b/src/js_async/readable_stream_test.mbt index 123c0511..16d5bf08 100644 --- a/src/js_async/readable_stream_test.mbt +++ b/src/js_async/readable_stream_test.mbt @@ -25,7 +25,7 @@ async test "ReadableStream roundtrip" { group.spawn_bg() <| () => { defer w.close() async fn write(msg : String) { - w.write(msg) + w.write_text(msg) log.push("writer: \{msg} at tick #\{tick()}") } write("message1") @@ -57,7 +57,7 @@ async test "ReadableStream read cancelled" { defer w.close() defer r.close() json_inspect(@async.with_timeout_opt(300, () => r.read_some()), content=null) - w.write("message") + w.write_text("message") json_inspect(@async.with_timeout_opt(300, () => r.read_some()), content=[ ["message"], ]) @@ -70,10 +70,10 @@ async test "ReadableStream write cancelled" { let r = @js_async.ReadableStream::from_js(r) defer w.close() defer r.close() - w.write("message1") - w.write("message2") + w.write_text("message1") + w.write_text("message2") json_inspect( - @async.with_timeout_opt(300, () => w.write("message3")), + @async.with_timeout_opt(300, () => w.write_text("message3")), content=null, ) for _ in 0..<2 { @@ -81,7 +81,7 @@ async test "ReadableStream write cancelled" { } @async.with_task_group() <| group => { group.spawn_bg(() => msgs.push(r.read_some())) - json_inspect(@async.with_timeout_opt(300, () => w.write("message4")), content=[ + json_inspect(@async.with_timeout_opt(300, () => w.write_text("message4")), content=[ null, ]) } diff --git a/src/pipe/read_exactly_test.mbt b/src/pipe/read_exactly_test.mbt index 2dd00a48..20770398 100644 --- a/src/pipe/read_exactly_test.mbt +++ b/src/pipe/read_exactly_test.mbt @@ -35,10 +35,10 @@ async test "read_exactly" { root.spawn_bg() <| () => { defer w.close() log("first message sent") - w.write("abcdef") + w.write_text("abcdef") @async.sleep(100) log("second message sent") - w.write("ghijkl") + w.write_text("ghijkl") } } inspect( @@ -77,7 +77,7 @@ async test "read_exactly failure" { root.spawn_bg() <| () => { defer w.close() log("first message sent") - w.write("abcdef") + w.write_text("abcdef") } }) catch { err => log(err.to_string()) diff --git a/src/pipe/reader_closed_test.mbt b/src/pipe/reader_closed_test.mbt index 21df0090..2578dc3b 100644 --- a/src/pipe/reader_closed_test.mbt +++ b/src/pipe/reader_closed_test.mbt @@ -18,5 +18,5 @@ async test "reader closed" { defer w.close() r.close() @async.sleep(100) - @test_util.assert_raise_async(() => w.write("abcd")) + @test_util.assert_raise_async(() => w.write_text("abcd")) } diff --git a/src/process/README.mbt.md b/src/process/README.mbt.md index a583ca04..aec94aa3 100644 --- a/src/process/README.mbt.md +++ b/src/process/README.mbt.md @@ -14,7 +14,7 @@ Execute commands and collect their output: async test "simple command execution" { let (exit_code, output) = @process.collect_stdout("echo", ["Hello, World!"]) inspect(exit_code, content="0") - let text = output.text() + let text = @utf8.decode(output) inspect(text.has_prefix("Hello"), content="true") } @@ -63,7 +63,7 @@ Capture stdout from a process: async test "collect stdout" { let (code, output) = @process.collect_stdout("printf", ["test output"]) inspect(code, content="0") - inspect(output.text(), content="test output") + inspect(@utf8.decode(output), content="test output") } ///| @@ -73,7 +73,7 @@ async test "collect stdout with args" { "-c", "echo 'line 1'; echo 'line 2'", ]) inspect(code, content="0") - let text = output.text() + let text = @utf8.decode(output) inspect(text.contains("line 1"), content="true") inspect(text.contains("line 2"), content="true") } @@ -91,7 +91,7 @@ async test "collect stderr" { "-c", "printf 'error message' >&2", ]) inspect(code, content="0") - inspect(output.text(), content="error message") + inspect(@utf8.decode(output), content="error message") } ``` @@ -107,8 +107,8 @@ async test "collect both streams" { "-c", "printf 'out msg'; printf 'err msg' >&2", ]) inspect(code, content="0") - inspect(stdout.text(), content="out msg") - inspect(stderr.text(), content="err msg") + inspect(@utf8.decode(stdout), content="out msg") + inspect(@utf8.decode(stderr), content="err msg") } ``` @@ -124,7 +124,7 @@ async test "collect merged output" { "-c", "printf 'ab'; printf 'cd' >&2; printf 'ef'", ]) inspect(code, content="0") - inspect(output.text(), content="abcdef") + inspect(@utf8.decode(output), content="abcdef") } ``` @@ -142,7 +142,7 @@ async test "read from process with pipe" { let (reader, writer) = @process.read_from_process() defer reader.close() let _ = @process.spawn(group, "echo", ["Hello from process"], stdout=writer) - let output = reader.read_all().text() + let output = reader.read_all_text() inspect(output.has_prefix("Hello"), content="true") }) } @@ -172,7 +172,7 @@ async test "write to process with pipe" { }) group.spawn_bg(() => { defer we_read.close() - let output = we_read.read_all().text() + let output = we_read.read_all_text() inspect(output.contains("test input"), content="true") }) }) @@ -191,7 +191,7 @@ Use a file as process input: async test "redirect input from file" { @async.with_task_group(root => { let input_file = "_build/process_test_input.txt" - @fs.write_file(input_file, "file content", create_mode=CreateOrTruncate) + @fs.write_file(input_file, b"file content", create_mode=CreateOrTruncate) root.add_defer(() => @fs.remove(input_file)) let (code, output) = @process.collect_stdout( "cat", @@ -199,7 +199,7 @@ async test "redirect input from file" { stdin=@process.redirect_from_file(input_file), ) inspect(code, content="0") - inspect(output.text(), content="file content") + inspect(@utf8.decode(output), content="file content") }) } ``` @@ -224,7 +224,7 @@ async test "redirect output to file" { ), ) inspect(code, content="0") - let content = @fs.read_file(output_file).text() + let content = @utf8.decode(@fs.read_file(output_file)) inspect(content.has_prefix("test output"), content="true") }) } @@ -241,7 +241,7 @@ async test "file to file redirection" { @async.with_task_group(root => { let input_file = "_build/process_redirect_in.txt" let output_file = "_build/process_redirect_out.txt" - @fs.write_file(input_file, "redirect test", create_mode=CreateOrTruncate) + @fs.write_file(input_file, b"redirect test", create_mode=CreateOrTruncate) root.add_defer(() => @fs.remove(input_file)) root.add_defer(() => @fs.remove(output_file)) let _ = @process.run( @@ -253,7 +253,7 @@ async test "file to file redirection" { create_mode=CreateOrTruncate, ), ) - inspect(@fs.read_file(output_file).text(), content="redirect test") + inspect(@utf8.decode(@fs.read_file(output_file)), content="redirect test") }) } ``` @@ -272,7 +272,7 @@ async test "set environment variable" { "MY_VAR": "my_value", }) inspect(code, content="0") - inspect(output.text().trim(), content="my_value") + inspect(@utf8.decode(output).trim(), content="my_value") } ///| @@ -284,7 +284,7 @@ async test "multiple environment variables" { extra_env={ "VAR1": "first", "VAR2": "second" }, ) inspect(code, content="0") - inspect(output.text().trim(), content="first-second") + inspect(@utf8.decode(output).trim(), content="first-second") } ``` @@ -303,7 +303,7 @@ async test "isolated environment" { inherit_env=false, ) inspect(code, content="0") - let text = output.text() + let text = @utf8.decode(output) inspect(text.contains("ONLY_VAR=only_value"), content="true") // Parent environment variables won't be present inspect(text.contains("PATH="), content="false") @@ -322,7 +322,7 @@ Execute processes in a specific directory: async test "set working directory" { let (code, output) = @process.collect_stdout("pwd", [], cwd="src") inspect(code, content="0") - let text = output.text().trim() + let text = @utf8.decode(output).trim() // Check that the path ends with "src" (works across OSes) inspect(text.has_suffix("src"), content="true") } @@ -332,7 +332,7 @@ async test "set working directory" { async test "relative path in cwd" { let (code, output) = @process.collect_stdout("ls", [], cwd="src") inspect(code, content="0") - let text = output.text() + let text = @utf8.decode(output) // Should list contents of src directory let has_content = text.length() > 0 inspect(has_content, content="true") @@ -404,7 +404,7 @@ async test "merge stdout and stderr" { stdout=writer, stderr=writer, ) - let output = reader.read_all().text() + let output = reader.read_all_text() inspect(output.contains("to stdout"), content="true") inspect(output.contains("to stderr"), content="true") }) @@ -423,7 +423,7 @@ async test "multiple processes to one pipe" { let (reader, writer) = @pipe.pipe() root.spawn_bg(no_wait=true, () => { defer reader.close() - let output = reader.read_all().text() + let output = reader.read_all_text() inspect(output.contains("first"), content="true") inspect(output.contains("second"), content="true") }) diff --git a/src/process/basic_test.mbt b/src/process/basic_test.mbt index ce347e67..375ef04f 100644 --- a/src/process/basic_test.mbt +++ b/src/process/basic_test.mbt @@ -23,8 +23,7 @@ async test "basic_ls" { }) |> ignore let files = r - .read_all() - .text() + .read_all_text() .trim(char_set="\n\r ") .replace_all(old="\r\n", new="\n") .split("\n") @@ -49,7 +48,7 @@ async test "basic_cat" { } group.spawn_bg() <| () => { defer we_read.close() - inspect(we_read.read_all().text(), content="abcdefgh") + inspect(we_read.read_all_text(), content="abcdefgh") } } } @@ -58,7 +57,7 @@ async test "basic_cat" { #cfg(platform="windows") async test "execute .com file" { let (_, output) = @process.collect_stdout("chcp.com", []) - guard output.binary().chop_suffix(b"\r\n") is Some(output) && + guard output.chop_suffix(b"\r\n") is Some(output) && output.rev_find(b" ") is Some(index) && index + 1 < output.length() else { fail("invalid output format from chcp") diff --git a/src/process/cancel_test.mbt b/src/process/cancel_test.mbt index 572a5713..e351acc2 100644 --- a/src/process/cancel_test.mbt +++ b/src/process/cancel_test.mbt @@ -96,7 +96,7 @@ async test "cancel process hard" { }) root.spawn_bg() <| () => { defer r.close() - inspect(r.read_all().text(), content="") + inspect(r.read_all_text(), content="") } @async.sleep(500) let t0 = @env.now() @@ -128,7 +128,7 @@ async test "orphan process" { inspect(@utf8.decode(r.read_exactly(5)), content="first") task.cancel() log <+ "cancelling task running process\n" - inspect(r.read_all().text(), content="second") + inspect(r.read_all_text(), content="second") } inspect( log.to_string(), @@ -159,7 +159,7 @@ async test "kill children on hard cancel" { @async.TimeoutError => () err => raise err } - inspect(r.read_all().text().trim(), content="") + inspect(r.read_all_text().trim(), content="") } ///| @@ -180,5 +180,5 @@ async test "do not kill orphan children on hard cancel" { @async.TimeoutError => () err => raise err } - inspect(r.read_all().text().trim(), content="after sleep") + inspect(r.read_all_text().trim(), content="after sleep") } diff --git a/src/process/cwd_test.mbt b/src/process/cwd_test.mbt index ebb67877..59155fdf 100644 --- a/src/process/cwd_test.mbt +++ b/src/process/cwd_test.mbt @@ -28,8 +28,7 @@ async test "set cwd" { guard prev_cwd == new_cwd } let files = r - .read_all() - .text() + .read_all_text() .trim(char_set="\n\r ") .replace_all(old="\r\n", new="\n") .split("\n") diff --git a/src/process/env_test.mbt b/src/process/env_test.mbt index 16fe816b..17e6a440 100644 --- a/src/process/env_test.mbt +++ b/src/process/env_test.mbt @@ -26,7 +26,7 @@ async test "set_env" { stdout=w, extra_env={ "MAGIC_VAR": "MAGIC_VALUE" }, ) - inspect(r.read_all().text(), content="MAGIC_VALUE") + inspect(r.read_all_text(), content="MAGIC_VALUE") } } @@ -50,7 +50,7 @@ async test "set_env no inherit" { inherit_env=false, ) inspect( - r.read_all().text().replace_all(old="\r\n", new="\n"), + r.read_all_text().replace_all(old="\r\n", new="\n"), content=( #|MAGIC_VAR_1=MAGIC_VALUE_1 #|MAGIC_VAR_2=MAGIC_VALUE_2 diff --git a/src/process/helper_test.mbt b/src/process/helper_test.mbt index beb4b628..6740a8c1 100644 --- a/src/process/helper_test.mbt +++ b/src/process/helper_test.mbt @@ -20,7 +20,7 @@ async test "collect_stdout" { "\{write_stdout} abcd", ]) inspect(code, content="0") - inspect(output.text(), content="abcd") + inspect(@utf8.decode(output), content="abcd") } ///| @@ -30,7 +30,7 @@ async test "collect_error" { "\{write_stderr} abcd", ]) inspect(code, content="0") - inspect(output.text(), content="abcd") + inspect(@utf8.decode(output), content="abcd") } ///| @@ -40,8 +40,8 @@ async test "collect_output" { "\{write_stdout} ab; \{write_stderr} ab; \{write_stdout} cd; \{write_stderr} cd", ]) inspect(code, content="0") - inspect(stdout.text(), content="abcd") - inspect(stderr.text(), content="abcd") + inspect(@utf8.decode(stdout), content="abcd") + inspect(@utf8.decode(stderr), content="abcd") } ///| @@ -51,7 +51,7 @@ async test "collect_output_merged" { "\{write_stdout} ab; \{write_stderr} ab; \{write_stdout} cd; \{write_stderr} cd", ]) inspect(code, content="0") - inspect(output.text(), content="ababcdcd") + inspect(@utf8.decode(output), content="ababcdcd") } ///| @@ -72,7 +72,7 @@ async test "collect_output blocked" { stdin=cat_read, ) inspect(code, content="0") - inspect(stdout.text(), content="") - inspect(stderr.binary().length(), content="1048576") + inspect(@utf8.decode(stdout), content="") + inspect(stderr.length(), content="1048576") } } diff --git a/src/process/pkg.generated.mbti b/src/process/pkg.generated.mbti index c88450aa..3b98c762 100644 --- a/src/process/pkg.generated.mbti +++ b/src/process/pkg.generated.mbti @@ -11,13 +11,13 @@ import { } // Values -pub async fn collect_output(StringView, ArrayView[String], extra_env? : Map[String, String], inherit_env? : Bool, stdin? : &ProcessInput, cwd? : StringView, no_console_window? : Bool) -> (Int, &@io.Data, &@io.Data) +pub async fn collect_output(StringView, ArrayView[String], extra_env? : Map[String, String], inherit_env? : Bool, stdin? : &ProcessInput, cwd? : StringView, no_console_window? : Bool) -> (Int, Bytes, Bytes) -pub async fn collect_output_merged(StringView, Array[String], extra_env? : Map[String, String], inherit_env? : Bool, stdin? : &ProcessInput, cwd? : StringView, no_console_window? : Bool) -> (Int, &@io.Data) +pub async fn collect_output_merged(StringView, Array[String], extra_env? : Map[String, String], inherit_env? : Bool, stdin? : &ProcessInput, cwd? : StringView, no_console_window? : Bool) -> (Int, Bytes) -pub async fn collect_stderr(StringView, ArrayView[String], extra_env? : Map[String, String], inherit_env? : Bool, stdin? : &ProcessInput, stdout? : &ProcessOutput, cwd? : StringView, no_console_window? : Bool) -> (Int, &@io.Data) +pub async fn collect_stderr(StringView, ArrayView[String], extra_env? : Map[String, String], inherit_env? : Bool, stdin? : &ProcessInput, stdout? : &ProcessOutput, cwd? : StringView, no_console_window? : Bool) -> (Int, Bytes) -pub async fn collect_stdout(StringView, ArrayView[String], extra_env? : Map[String, String], inherit_env? : Bool, stdin? : &ProcessInput, stderr? : &ProcessOutput, cwd? : StringView, no_console_window? : Bool) -> (Int, &@io.Data) +pub async fn collect_stdout(StringView, ArrayView[String], extra_env? : Map[String, String], inherit_env? : Bool, stdin? : &ProcessInput, stderr? : &ProcessOutput, cwd? : StringView, no_console_window? : Bool) -> (Int, Bytes) pub fn graceful_cancel(timeout~ : Int, signal? : @signal.Signal) -> CancellationHandler diff --git a/src/process/process.mbt b/src/process/process.mbt index 4d04c345..c49c5b8f 100644 --- a/src/process/process.mbt +++ b/src/process/process.mbt @@ -260,7 +260,7 @@ pub async fn collect_stdout( stderr? : &ProcessOutput, cwd? : StringView, no_console_window? : Bool, -) -> (Int, &@io.Data) { +) -> (Int, Bytes) { let (r, w) = read_from_process() @async.with_task_group() <| group => { defer r.close() @@ -296,7 +296,7 @@ pub async fn collect_stderr( stdout? : &ProcessOutput, cwd? : StringView, no_console_window? : Bool, -) -> (Int, &@io.Data) { +) -> (Int, Bytes) { let (r, w) = read_from_process() @async.with_task_group() <| group => { defer r.close() @@ -332,7 +332,7 @@ pub async fn collect_output( stdin? : &ProcessInput, cwd? : StringView, no_console_window? : Bool, -) -> (Int, &@io.Data, &@io.Data) { +) -> (Int, Bytes, Bytes) { let (r_out, w_out) = read_from_process() let (r_err, w_err) = read_from_process() @async.with_task_group() <| group => { @@ -375,7 +375,7 @@ pub async fn collect_output_merged( stdin? : &ProcessInput, cwd? : StringView, no_console_window? : Bool, -) -> (Int, &@io.Data) { +) -> (Int, Bytes) { let (r, w) = read_from_process() @async.with_task_group() <| group => { defer r.close() diff --git a/src/process/redirect_test.mbt b/src/process/redirect_test.mbt index e92b535a..6770fc44 100644 --- a/src/process/redirect_test.mbt +++ b/src/process/redirect_test.mbt @@ -19,7 +19,7 @@ async test "merge multiple" { let (r, w) = @pipe.pipe() root.spawn_bg(no_wait=true) <| () => { defer r.close() - inspect(r.read_all().text(), content="abcdefgh") + inspect(r.read_all_text(), content="abcdefgh") } defer w.close() @async.with_task_group() <| group => { @@ -47,7 +47,7 @@ async test "redirect to file" { let output_file = "_build/process_redirect_test_out.txt" root.add_defer(() => @fs.remove(input_file)) root.add_defer(() => @fs.remove(output_file)) - @fs.write_file(input_file, "abcd", create_mode=CreateOrTruncate) + @fs.write_file(input_file, b"abcd", create_mode=CreateOrTruncate) let _ = @process.run( cat, [], @@ -57,7 +57,7 @@ async test "redirect to file" { create_mode=CreateOrTruncate, ), ) - inspect(@fs.read_file(output_file).text(), content="abcd") + inspect(@utf8.decode(@fs.read_file(output_file)), content="abcd") } } @@ -73,6 +73,6 @@ async test "merge stdout and stderr" { stdout=w, stderr=w, ) - inspect(r.read_all().text(), content="abcdefgh") + inspect(r.read_all_text(), content="abcdefgh") } } diff --git a/src/process/spawn_in_group_test.mbt b/src/process/spawn_in_group_test.mbt index ce0ac776..f06267e8 100644 --- a/src/process/spawn_in_group_test.mbt +++ b/src/process/spawn_in_group_test.mbt @@ -37,7 +37,7 @@ async test "spawn_in_group cancel" { let (r, w) = @process.read_from_process() outer.spawn_bg() <| () => { defer r.close() - inspect(r.read_all().text().trim(), content="received termination signal") + inspect(r.read_all_text().trim(), content="received termination signal") } @async.with_task_group() <| inner => { let _ = @process.spawn(inner, sleep, ["10000"], stdout=w, no_wait=true) @@ -82,7 +82,7 @@ async test "Process:cancel" { let (r, w) = @process.read_from_process() group.spawn_bg() <| () => { defer r.close() - inspect(r.read_all().text().trim(), content="received termination signal") + inspect(r.read_all_text().trim(), content="received termination signal") } let child = @process.spawn(group, sleep, ["10000"], stdout=w) t0 = @env.now() diff --git a/src/raw_fd/raw_fd_test.mbt b/src/raw_fd/raw_fd_test.mbt index 88dbbb1f..62504d74 100644 --- a/src/raw_fd/raw_fd_test.mbt +++ b/src/raw_fd/raw_fd_test.mbt @@ -91,7 +91,7 @@ async test "pipe as raw fd stream" { ///| async test "file as raw fd" { - let truth = @fs.read_file("LICENSE").binary() + let truth = @fs.read_file("LICENSE") let (io, _) = @event_loop.open( "LICENSE", 0, @@ -121,7 +121,7 @@ async test "file as raw fd" { ///| async test "file as raw fd stream" { - let truth = @fs.read_file("LICENSE").binary() + let truth = @fs.read_file("LICENSE") let (io, _) = @event_loop.open( "LICENSE", 0, @@ -136,7 +136,7 @@ async test "file as raw fd stream" { let file = @raw_fd.RawFdStream(fd) defer file.close() assert_true(@event_loop.kind_of_fd(fd, context="kind_of_fd") is Regular) - let actual = file.read_all().binary() + let actual = file.read_all() assert_eq(actual.length(), truth.length()) assert_eq(truth, actual) } diff --git a/src/socket/dual_stack_test.mbt b/src/socket/dual_stack_test.mbt index d35a9800..dfeca4dd 100644 --- a/src/socket/dual_stack_test.mbt +++ b/src/socket/dual_stack_test.mbt @@ -31,7 +31,7 @@ async test "Only_V4 server" { let addr_str = addr.to_string() guard addr_str.rev_find(":") is Some(ip_end) let ip = addr_str[:ip_end] - let msg = conn.read_all().text() + let msg = conn.read_all_text() log.write_string("server received \{msg.escape()} from \{ip}\n") }) } @@ -42,7 +42,7 @@ async test "Only_V4 server" { err => raise err } defer conn.close() - conn.write("message") + conn.write_text("message") } for protocol in all_protocol_preferences { @@ -88,7 +88,7 @@ async test "Only_V6 server" { let addr_str = addr.to_string() guard addr_str.rev_find(":") is Some(ip_end) let ip = addr_str[:ip_end] - let msg = conn.read_all().text() + let msg = conn.read_all_text() log.write_string("server received \{msg.escape()} from \{ip}\n") }) } @@ -99,7 +99,7 @@ async test "Only_V6 server" { err => raise err } defer conn.close() - conn.write("message") + conn.write_text("message") } for protocol in all_protocol_preferences { @@ -142,14 +142,14 @@ async test "dual stack server" { let addr_str = addr.to_string() guard addr_str.rev_find(":") is Some(ip_end) let ip = addr_str[:ip_end] - let msg = conn.read_all().text() + let msg = conn.read_all_text() log.write_string("server received \{msg.escape()} from \{ip}\n") }) } async fn client(protocol) { let conn = @socket.Tcp::connect_to_host("localhost", port~, protocol~) defer conn.close() - conn.write("message") + conn.write_text("message") } for protocol in all_protocol_preferences { diff --git a/src/socket/getsockname_test.mbt b/src/socket/getsockname_test.mbt index 8b64b224..70a05ce3 100644 --- a/src/socket/getsockname_test.mbt +++ b/src/socket/getsockname_test.mbt @@ -26,13 +26,13 @@ async test "getsockname TCP" { let (conn, peer_addr) = server.accept() defer conn.close() assert_eq(conn.addr, server_addr) - inspect(conn.read_all().text(), content="abcd") + inspect(conn.read_all_text(), content="abcd") peer_addr }) let client_addr = group.spawn(() => { let client = @socket.Tcp::connect(server_addr) defer client.close() - client.write("abcd") + client.write_text("abcd") client.addr }) assert_eq(client_addr_from_server.wait(), client_addr.wait()) diff --git a/src/socket/read_exactly_test.mbt b/src/socket/read_exactly_test.mbt index bb061cd4..055557d4 100644 --- a/src/socket/read_exactly_test.mbt +++ b/src/socket/read_exactly_test.mbt @@ -41,10 +41,10 @@ async test "read_exactly" { root.spawn_bg() <| () => { let conn = @socket.Tcp::connect(addr) defer conn.close() - conn.write("abcdef") + conn.write_text("abcdef") log("first message sent") @async.sleep(100) - conn.write("ghijkl") + conn.write_text("ghijkl") log("second message sent") } } @@ -93,7 +93,7 @@ async test "read_exactly failure" { root.spawn_bg() <| () => { let conn = @socket.Tcp::connect(addr) defer conn.close() - conn.write("abcdef") + conn.write_text("abcdef") log("first message sent") } }) catch { diff --git a/src/socket/reuse_addr_test.mbt b/src/socket/reuse_addr_test.mbt index d0ec7efc..0ca60d0b 100644 --- a/src/socket/reuse_addr_test.mbt +++ b/src/socket/reuse_addr_test.mbt @@ -26,14 +26,14 @@ async test "reuse addr" { port = server.addr.port() let (conn, _) = server.accept() defer conn.close() - conn.write("abcd") + conn.write_text("abcd") } } for _ in 0..<2 { @async.sleep(100) let conn = @socket.Tcp::connect(@socket.Addr::parse("127.0.0.1:\{port}")) defer conn.close() - inspect(conn.read_all().text(), content="abcd") + inspect(conn.read_all_text(), content="abcd") } } } diff --git a/src/stdio/moon.pkg b/src/stdio/moon.pkg index 8db3c672..0d789e79 100644 --- a/src/stdio/moon.pkg +++ b/src/stdio/moon.pkg @@ -5,6 +5,7 @@ import { } import { + "moonbitlang/core/encoding/utf8", "moonbitlang/async", "moonbitlang/async/fs", "moonbitlang/async/process", diff --git a/src/stdio/stdio_test.mbt b/src/stdio/stdio_test.mbt index 09a5d920..2a3f650f 100644 --- a/src/stdio/stdio_test.mbt +++ b/src/stdio/stdio_test.mbt @@ -39,7 +39,7 @@ async test "redirect_file" { stdout=@process.redirect_to_file(out_file, create_mode=CreateOrTruncate), ) inspect(code, content="0") - assert_eq(@fs.read_file(out_file).text(), @fs.read_file("moon.mod").text()) + assert_eq(@fs.read_file(out_file), @fs.read_file("moon.mod")) } ///| @@ -61,7 +61,7 @@ async test "redirect pipe" { } defer we_read.close() inspect( - we_read.read_all().text(), + we_read.read_all_text(), content=( #|abcd #|defg @@ -89,7 +89,7 @@ async test "stdio cancel" { } defer we_write.close() defer we_read.close() - we_write.write("abcd") + we_write.write_text("abcd") debug_inspect( we_read.read_some(), content=( @@ -97,10 +97,7 @@ async test "stdio cancel" { ), ) @async.sleep(750) - inspect( - we_read.read_all().text().trim(chars="\r\n"), - content="TimeoutError", - ) + inspect(we_read.read_all_text().trim(chars="\r\n"), content="TimeoutError") } } @@ -115,11 +112,11 @@ async test "stdout and stderr are the same" { } { defer we_write.close() - we_write.write("abcd") + we_write.write_text("abcd") } let (_, output) = cat_task.wait() inspect( - output.text(), + @utf8.decode(output), content=( #|abcd ), diff --git a/src/tls/openssl.mbt b/src/tls/openssl.mbt index bdba1561..172d5fac 100644 --- a/src/tls/openssl.mbt +++ b/src/tls/openssl.mbt @@ -355,7 +355,7 @@ async fn SSL_CTX::client_with_custom_root(root_cert : String) -> SSL_CTX { return ctx } try { - let pem = @fs.read_file(root_cert).text() + let pem = @utf8.decode(@fs.read_file(root_cert)) for cert in decode_pem_certificates(pem) { if ctx.add_root_certificate(cert) != 1 { raise TlsError(err_get_error()) diff --git a/src/tls/schannel.mbt b/src/tls/schannel.mbt index 3aed0e00..3b495d08 100644 --- a/src/tls/schannel.mbt +++ b/src/tls/schannel.mbt @@ -208,7 +208,7 @@ pub async fn[R : @io.Reader, W : @io.Writer] Tls::client_from_pair( } if trust is CustomPemFile(root_cert) { try { - let pem = @fs.read_file(root_cert).text() + let pem = @utf8.decode(@fs.read_file(root_cert)) for cert in decode_pem_certificates(pem) { if context.add_root_certificate(cert) is err && err != 0 { raise TlsError(@os_error.errno_to_string(err)) @@ -293,7 +293,7 @@ pub async fn[R : @io.Reader, W : @io.Writer] Tls::server_from_pair( pfx_file~ : String, ) -> Tls { let context = Schannel::new() - let pfx_content = @fs.read_file(pfx_file).binary() + let pfx_content = @fs.read_file(pfx_file) if context.init_server(pfx_content) is err && err != 0 { context.free() raise TlsError(@os_error.errno_to_string(err)) diff --git a/src/tls/shutdown_test.mbt b/src/tls/shutdown_test.mbt index 3f63adee..bc5cb733 100644 --- a/src/tls/shutdown_test.mbt +++ b/src/tls/shutdown_test.mbt @@ -24,8 +24,8 @@ async test "peer close connection" { defer conn.close() let server = test_server(conn, conn) defer server.close() - server.write("1234") - inspect(server.read_all().text(), content="abcd") + server.write_text("1234") + inspect(server.read_all_text(), content="abcd") } // client let conn = @socket.Tcp::connect(addr) @@ -41,7 +41,7 @@ async test "peer close connection" { // due to pending data in the recv buffer. // Read something before closing the connection solves the problem. inspect(@utf8.decode(client.read_exactly(4)), content="1234") - client.write("abcd") + client.write_text("abcd") // close the underlying connection directly } } diff --git a/src/tls/tls.wasm.mbt b/src/tls/tls.wasm.mbt index 9e15017a..eca59cb1 100644 --- a/src/tls/tls.wasm.mbt +++ b/src/tls/tls.wasm.mbt @@ -371,7 +371,7 @@ pub async fn[R : @io.Reader, W : @io.Writer] Tls::client_from_pair( let conn = TlsConnectionHandle::new().check_new() try { if trust is CustomPemFile(path) { - let pem = @fs.read_file(path).text() + let pem = @utf8.decode(@fs.read_file(path)) for cert in decode_pem_certificates(pem) { let status = conn.add_root_certificate(cert) if status != 0 { @@ -422,7 +422,7 @@ pub async fn[R : @io.Reader, W : @io.Writer] Tls::server_from_pair( "pfx_file is required for TLS servers on Windows hosts", ) } - let pfx_content = @fs.read_file(pfx_file).binary() + let pfx_content = @fs.read_file(pfx_file) conn.set_server_pfx(pfx_content) } else { let private_key_file = match private_key_file { diff --git a/src/tls/tls_test.mbt b/src/tls/tls_test.mbt index f87d10c5..3d944d47 100644 --- a/src/tls/tls_test.mbt +++ b/src/tls/tls_test.mbt @@ -84,7 +84,7 @@ async test "one way" { @async.sleep(50) client.write(b"ijkl") client.shutdown() - inspect(client.read_all().text(), content="") + inspect(client.read_all_text(), content="") } } inspect( @@ -137,7 +137,7 @@ async test "echo" { @async.sleep(50) } client.shutdown() - inspect(client.read_all().text(), content="") + inspect(client.read_all_text(), content="") } } inspect( @@ -299,7 +299,7 @@ async test "client custom root certificate" { defer client.close() client.write(b"ok") client.shutdown() - inspect(client.read_all().text(), content="") + inspect(client.read_all_text(), content="") } } } diff --git a/src/websocket/README.mbt.md b/src/websocket/README.mbt.md index 2f178f8f..869bee1d 100644 --- a/src/websocket/README.mbt.md +++ b/src/websocket/README.mbt.md @@ -63,7 +63,7 @@ async test "WebSocket client example" { defer ws.close() // convenient helper for sending small text message ws.send_text("abcd") - inspect(ws.recv().read_all().text(), content="abcd") + inspect(ws.recv().read_all_text(), content="abcd") // gracefully terminate the WebSocket tunnel ws.send_close() }) diff --git a/src/websocket/bad_client_test.mbt b/src/websocket/bad_client_test.mbt index 1f9e4885..601c9f49 100644 --- a/src/websocket/bad_client_test.mbt +++ b/src/websocket/bad_client_test.mbt @@ -23,7 +23,7 @@ async test "bad client" { let ws = @websocket.from_http_server(req, conn) defer ws.close() let result = @test_util.expect_error_async <| () => { - let result = ws.recv().read_all().text() + let result = ws.recv().read_all_text() ws.send_text(result) result } @@ -44,7 +44,7 @@ async test "bad client" { http_client.write(frame) } } - ignore(ws_client.recv().read_all().text()) + ignore(ws_client.recv().read_all_text()) } } diff --git a/src/websocket/conn.mbt b/src/websocket/conn.mbt index 4c27b15e..bcff9bb1 100644 --- a/src/websocket/conn.mbt +++ b/src/websocket/conn.mbt @@ -689,7 +689,7 @@ pub impl @io.Writer for Conn with fn write_once(self, buf, offset~, len~) { /// Convenient helper for sending a single text message. /// To send large message lazily, see `start_message`. pub async fn Conn::send_text(self : Conn, text : StringView) -> Unit { - self..start_message(Text)..write(text).end_message() + self..start_message(Text)..write_text(text).end_message() } ///| diff --git a/src/websocket/control_test.mbt b/src/websocket/control_test.mbt index 774c2de5..c0455531 100644 --- a/src/websocket/control_test.mbt +++ b/src/websocket/control_test.mbt @@ -24,7 +24,7 @@ async test "ping auto-reply" { let request = conn.read_request() let ws = @websocket.from_http_server(request, conn) defer ws.close() - inspect(ws.recv().read_all().text(), content="1234") + inspect(ws.recv().read_all_text(), content="1234") } let http_client = @http.Client("http://localhost:\{port}") let ws_client = @websocket.from_http_client(http_client, "/") @@ -49,7 +49,7 @@ async test "pong ignored" { let request = conn.read_request() let ws = @websocket.from_http_server(request, conn) defer ws.close() - inspect(ws.recv().read_all().text(), content="1234") + inspect(ws.recv().read_all_text(), content="1234") ws.send_text("1234") } let http_client = @http.Client("http://localhost:\{port}") @@ -58,7 +58,7 @@ async test "pong ignored" { let ping_frame : Bytes = [0x89, 0x04, ..b"abcd"] http_client.write(ping_frame) ws_client.send_text("1234") - inspect(ws_client.recv().read_all().text(), content="1234") + inspect(ws_client.recv().read_all_text(), content="1234") } } @@ -78,7 +78,7 @@ async test "ping auto-reply race-with-write" { // currently, auto-reply is only triggered when the user is actually reading. group.spawn_bg(no_wait=true, () => ignore(ws.recv().read_all())) ws.start_message(Text) - ws.write("1234") + ws.write_text("1234") @async.sleep(400) ws.end_message() } @@ -115,14 +115,14 @@ async test "close test" { // currently, auto-reply is only triggered when the user is actually reading. group.spawn_bg() <| () => { debug_inspect( - @test_util.expect_error_async(() => ws.recv().read_all().text()), + @test_util.expect_error_async(() => ws.recv().read_all_text()), content=( #|ConnectionClosed(Normal, Some("abcd")) ), ) } ws.start_message(Text) - ws.write("1234") + ws.write_text("1234") @async.sleep(400) ws.end_message() } @@ -154,7 +154,7 @@ async test "send ping" { let request = conn.read_request() let ws = @websocket.from_http_server(request, conn) defer ws.close() - inspect(ws.recv().read_all().text(), content="1234") + inspect(ws.recv().read_all_text(), content="1234") ws.send_text("abcd") } let ws = @websocket.connect("ws://localhost:\{port}") @@ -164,7 +164,7 @@ async test "send ping" { ws.ping() ws.send_text("1234") } - inspect(ws.recv().read_all().text(), content="abcd") + inspect(ws.recv().read_all_text(), content="abcd") } } } @@ -181,16 +181,16 @@ async test "ping inside message" { let request = conn.read_request() let ws = @websocket.from_http_server(request, conn) defer ws.close() - inspect(ws.recv().read_all().text(), content="1234") + inspect(ws.recv().read_all_text(), content="1234") ws.send_text("abcd") } let ws = @websocket.connect("ws://localhost:\{port}") defer ws.close() @async.with_task_group() <| group => { group.spawn_bg() <| () => { - ws..start_message(Text)..ping()..write("1234").end_message() + ws..start_message(Text)..ping()..write_text("1234").end_message() } - inspect(ws.recv().read_all().text(), content="abcd") + inspect(ws.recv().read_all_text(), content="abcd") } } } @@ -208,7 +208,7 @@ async test "multiple ping" { let ws = @websocket.from_http_server(request, conn) defer ws.close() @async.sleep(100) - inspect(ws.recv().read_all().text(), content="1234") + inspect(ws.recv().read_all_text(), content="1234") ws.send_text("abcd") } let ws = @websocket.connect("ws://localhost:\{port}") @@ -221,7 +221,7 @@ async test "multiple ping" { } ws.send_text("1234") } - inspect(ws.recv().read_all().text(), content="abcd") + inspect(ws.recv().read_all_text(), content="abcd") } } } @@ -239,14 +239,14 @@ async test "ping cancelled" { let ws = @websocket.from_http_server(request, conn) defer ws.close() @async.sleep(200) - inspect(ws.recv().read_all().text(), content="1234") + inspect(ws.recv().read_all_text(), content="1234") ws.send_text("abcd") } let ws = @websocket.connect("ws://localhost:\{port}") defer ws.close() @async.with_task_group() <| group => { group.spawn_bg() <| () => { - inspect(ws.recv().read_all().text(), content="abcd") + inspect(ws.recv().read_all_text(), content="abcd") } debug_inspect( @async.with_timeout_opt(100, () => ws.ping(msg=b"1")), @@ -271,7 +271,7 @@ async test "duplicated ping" { let ws = @websocket.from_http_server(request, conn) defer ws.close() @async.sleep(100) - inspect(ws.recv().read_all().text(), content="1234") + inspect(ws.recv().read_all_text(), content="1234") ws.send_text("abcd") } let ws = @websocket.connect("ws://localhost:\{port}") @@ -291,7 +291,7 @@ async test "duplicated ping" { } ws.send_text("1234") } - inspect(ws.recv().read_all().text(), content="abcd") + inspect(ws.recv().read_all_text(), content="abcd") } } } diff --git a/src/websocket/external_client_test.mbt b/src/websocket/external_client_test.mbt index 9a94f5df..5a7a8346 100644 --- a/src/websocket/external_client_test.mbt +++ b/src/websocket/external_client_test.mbt @@ -25,9 +25,9 @@ async test "external client" { defer ws.close() for ;; { let msg = ws.recv() - let data = msg.read_all().text() + let data = msg.read_all_text() log.push("\{@debug.to_string(msg.kind)}: \{data}") - ws..start_message(msg.kind)..write(data).end_message() + ws..start_message(msg.kind)..write_text(data).end_message() } }) } @@ -36,7 +36,7 @@ async test "external client" { "ws://localhost:\{port}", ]) inspect( - output.text(), + @utf8.decode(output), content=( #|Connection opened #|Message from server: Hello, Server! diff --git a/src/websocket/server.mbt b/src/websocket/server.mbt index c616ac8c..f2f349cf 100644 --- a/src/websocket/server.mbt +++ b/src/websocket/server.mbt @@ -31,7 +31,7 @@ pub async fn Conn::from_http_server( ) -> Conn { try { async fn bad_request(msg : String) { - conn..send_response(400, "Bad Request")..write(msg).end_response() + conn..send_response(400, "Bad Request")..write_text(msg).end_response() raise InvalidHandshake(msg) } diff --git a/src/websocket/utf8_validation_test.mbt b/src/websocket/utf8_validation_test.mbt index 241fab46..53094f90 100644 --- a/src/websocket/utf8_validation_test.mbt +++ b/src/websocket/utf8_validation_test.mbt @@ -49,7 +49,7 @@ async test "UTF8 validation" { defer client.close() client..start_message(Text)..write(msg).end_message() let err = @test_util.expect_error_async <| () => { - client.recv().read_all().binary() + client.recv().read_all() } log.push( "client: \{@debug.to_string(msg.to_array())} => Err(\{@debug.to_string(err)})", @@ -110,7 +110,7 @@ async test "UTF8 validation large message" { let chunk_bin = @utf8.encode(chunk_text) group.spawn_bg() <| () => { client.start_message(Text) - client.write("a") + client.write_text("a") for n = 0; n < 10240; n = n + chunk_bin.length() { client.write(chunk_bin) } diff --git a/src/websocket/websocket_test.mbt b/src/websocket/websocket_test.mbt index b6da2081..0242f624 100644 --- a/src/websocket/websocket_test.mbt +++ b/src/websocket/websocket_test.mbt @@ -25,15 +25,15 @@ async test "websocket basic" { let ws = @websocket.from_http_server(request, conn) defer ws.close() let msg = ws.recv() - let msg_content = msg.read_all().text() + let msg_content = msg.read_all_text() debug_inspect(msg.kind, content="Binary") inspect(msg_content, content="abcd") - ws..start_message(msg.kind)..write(msg_content).end_message() + ws..start_message(msg.kind)..write_text(msg_content).end_message() let msg = ws.recv() - let msg_content = msg.read_all().text() + let msg_content = msg.read_all_text() debug_inspect(msg.kind, content="Text") inspect(msg_content, content="abcd") - ws..start_message(msg.kind)..write(msg_content).end_message() + ws..start_message(msg.kind)..write_text(msg_content).end_message() try ws.recv() catch { err => inspect(err, content="ConnectionClosed(Normal, None)") } noraise { @@ -45,11 +45,11 @@ async test "websocket basic" { client.send_binary(b"abcd") let msg = client.recv() debug_inspect(msg.kind, content="Binary") - inspect(msg.read_all().text(), content="abcd") + inspect(msg.read_all_text(), content="abcd") client.send_text("abcd") let msg = client.recv() debug_inspect(msg.kind, content="Text") - inspect(msg.read_all().text(), content="abcd") + inspect(msg.read_all_text(), content="abcd") client.send_close() } } @@ -68,7 +68,7 @@ async test "single frame multiple read" { defer ws.close() let msg = ws.recv() debug_inspect(msg.kind, content="Text") - json_inspect(msg.read_all().text(), content="abc") + json_inspect(msg.read_all_text(), content="abc") let msg = ws.recv() debug_inspect(msg.kind, content="Text") json_inspect(msg.read_exactly(2), content="ab") @@ -101,7 +101,7 @@ async test "ignored message" { let msg2 = ws.recv() // trying to read from `msg1` here should result in panic debug_inspect(msg2.kind, content="Text") - inspect(msg2.read_all().text(), content="1234") + inspect(msg2.read_all_text(), content="1234") } let client = @websocket.connect("ws://localhost:\{addr.port()}") defer client.close() @@ -139,10 +139,10 @@ async test "write buffering" { } let client = @websocket.connect("ws://localhost:\{addr.port()}") defer client.close() - client..start_message(Text).write("ab") + client..start_message(Text).write_text("ab") @async.sleep(100) // due to buffering, this message will not be sent directly - client..write("cd").end_message() + client..write_text("cd").end_message() // close the channel client.send_close() diff --git a/test_programs/lock_file/main.mbt b/test_programs/lock_file/main.mbt index 3b8946c6..c5aab5a2 100644 --- a/test_programs/lock_file/main.mbt +++ b/test_programs/lock_file/main.mbt @@ -53,7 +53,7 @@ async fn main { defer file.close() file.lock(kind) defer file.unlock() - @stdio.stdout.write("acquired lock\n") + @stdio.stdout.write_text("acquired lock\n") @async.sleep(duration) - @stdio.stdout.write("releasing lock\n") + @stdio.stdout.write_text("releasing lock\n") } diff --git a/test_programs/setup_sanitizer_check/main.mbt b/test_programs/setup_sanitizer_check/main.mbt index c67edd9c..264fe14e 100644 --- a/test_programs/setup_sanitizer_check/main.mbt +++ b/test_programs/setup_sanitizer_check/main.mbt @@ -47,7 +47,7 @@ async fn main { for file in files { guard file is "moon.pkg" else { } let full_path = "\{path}/\{file}" - let content = @fs.read_file(full_path).text().split("\n") + let content = @utf8.decode(@fs.read_file(full_path)).split("\n") let transformed = StringBuilder::new() let mut found = false for line in content { @@ -63,7 +63,7 @@ async fn main { ..write_string(link_config) .write_string("\n)") } - @fs.write_file(full_path, transformed.to_string()) + @fs.write_file(full_path, @utf8.encode(transformed.to_string())) } }) } diff --git a/test_programs/setup_sanitizer_check/moon.pkg b/test_programs/setup_sanitizer_check/moon.pkg index e93a6c92..d0020823 100644 --- a/test_programs/setup_sanitizer_check/moon.pkg +++ b/test_programs/setup_sanitizer_check/moon.pkg @@ -1,4 +1,5 @@ import { + "moonbitlang/core/encoding/utf8", "moonbitlang/core/json", "moonbitlang/async", "moonbitlang/async/fs",