///|
/// sqlc WASM plugin protocol — unframed raw protobuf over stdin/stdout.
///
/// Native WASI I/O via inline WAT FFI (wasi_io.mbt). WASI fd_read/fd_write
/// handle stdin/stdout I/O directly without 4-byte LE framing.
///
/// Protocol flow:
/// 1. run_io_loop reads all bytes from stdin via fd_read until EOF
/// 2. run_io_loop calls `process_message_outcome(data: Bytes)`
/// 3. On success run_io_loop writes raw response bytes to stdout via fd_write
/// 4. On failure run_io_loop writes the error to stderr and exits non-zero
/// (sqlc surfaces stderr to the user; see sqlc internal/ext/wasm/wasm.go)
///
/// Protocol entry point — pure data transformation.
//
// Copyright 2026 Mairzzcllo
//
// 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.
//
///|
/// Outcome of processing a raw protobuf GenerateRequest.
pub enum MessageOutcome {
Ok(Bytes)
Err(String)
}
///|
fn format_process_error(e : Error) -> String {
match e {
Failure::Failure(msg) => msg
_ => "codegen failed"
}
}
///|
/// Process a raw protobuf GenerateRequest and return success or error message.
/// Used by the WASI I/O loop for sqlc-visible error reporting via stderr.
pub fn process_message_outcome(data : Bytes) -> MessageOutcome {
if data.length() == 0 {
return Err("empty GenerateRequest")
}
try {
let req = decode_request(data)
let resp = process_request(req)
Ok(encode_response(resp))
} catch {
e => Err(format_process_error(e))
}
}
///|
/// Process a raw protobuf GenerateRequest and return raw GenerateResponse bytes.
/// Moonrun tests use this entry point; errors return an empty GenerateResponse.
/// Production sqlc path uses `process_message_outcome` + stderr instead.
///
/// Codec errors propagate via `raise` from `decode_request` and are caught here.
/// `abort()` must not be used on decode paths (WASM trap bypasses catch).
pub fn process_message(data : Bytes) -> Bytes {
match process_message_outcome(data) {
Ok(bytes) => bytes
Err(_) => {
let empty_resp : GenerateResponse = { files: [] }
encode_response(empty_resp)
}
}
}
///|
test "P0-070: process_message corrupt protobuf returns empty response" {
let enc = Encoder::new()
enc.write_tag(4, WT_LEN)
enc.write_varint(100)
enc.write_byte((0x41).to_byte())
let data = enc.finish()
let response_bytes = process_message(data)
let resp = decode_response(response_bytes)
inspect(resp.files.length(), content="0")
}
///|
test "P0-001: process_message_outcome empty buffer returns Err" {
match process_message_outcome(b"") {
Ok(_) => inspect(false, content="true")
Err(msg) => inspect(msg.contains("empty"), content="true")
}
}
///|
test "P0-001: process_message_outcome corrupt protobuf returns Err" {
let enc = Encoder::new()
enc.write_tag(4, WT_LEN)
enc.write_varint(100)
enc.write_byte((0x41).to_byte())
let data = enc.finish()
match process_message_outcome(data) {
Ok(_) => inspect(false, content="true")
Err(msg) => inspect(msg.length() > 0, content="true")
}
}
///|
test "P0-001: process_message_outcome path traversal returns Err with message" {
let base = make_users_request()
let settings_with_traversal = {
version: base.settings.version,
engine: base.settings.engine,
schema: base.settings.schema,
queries: base.settings.queries,
codegen: {
out: "../etc",
plugin: base.settings.codegen.plugin,
options: base.settings.codegen.options,
env: base.settings.codegen.env,
process: base.settings.codegen.process,
wasm: base.settings.codegen.wasm,
},
}
let req : GenerateRequest = {
settings: settings_with_traversal,
catalog: base.catalog,
queries: base.queries,
sqlc_version: base.sqlc_version,
plugin_options: b"package_name=test",
global_options: b"",
}
match process_message_outcome(encode_request(req)) {
Ok(_) => inspect(false, content="true")
Err(msg) => inspect(msg.contains("traversal"), content="true")
}
}
///|
test "P0-001: process_message_outcome unsupported engine returns Err" {
let base = make_users_request()
let settings = {
version: base.settings.version,
engine: "mysql",
schema: base.settings.schema,
queries: base.settings.queries,
codegen: base.settings.codegen,
}
let req : GenerateRequest = {
settings,
catalog: base.catalog,
queries: base.queries,
sqlc_version: base.sqlc_version,
plugin_options: b"package_name=test",
global_options: b"",
}
match process_message_outcome(encode_request(req)) {
Ok(_) => inspect(false, content="true")
Err(msg) => inspect(msg.contains("postgresql"), content="true")
}
}