// Runnable tour of the memcached text protocol codec and of the single
// connection client. Run with `moon run cmd/main`.
///|
/// Render `bytes` as text, escaping control bytes so the framing stays visible.
///
/// The bytes are escaped one at a time instead of being handed to
/// `Bytes::to_string`, which renders a `Bytes` value (`b"..."`) rather than
/// decoding it.
fn render(bytes : Bytes) -> String {
let out = StringBuilder()
for byte in bytes {
out.write_string(byte.to_char().escape(quote=false))
}
out.to_string()
}
///|
/// Human readable name of a terminal status line.
fn status_name(status : @lib.Status) -> String {
if status == @lib.Status::Stored {
"STORED"
} else if status == @lib.Status::NotStored {
"NOT_STORED"
} else if status == @lib.Status::Exists {
"EXISTS"
} else if status == @lib.Status::NotFound {
"NOT_FOUND"
} else if status == @lib.Status::Deleted {
"DELETED"
} else if status == @lib.Status::Touched {
"TOUCHED"
} else {
"OK"
}
}
///|
/// Render an optional CAS token, using `-` when the server did not send one.
fn cas_text(cas : UInt64?) -> String {
match cas {
Some(unique) => unique.to_string()
None => "-"
}
}
///|
/// Describe one slot of a pipeline result.
///
/// A silent request has no reply to show, which is why the slot says so instead
/// of holding some placeholder response.
fn reply_text(reply : @lib.Response?) -> String {
match reply {
None => "silent request, nothing to read"
Some(@lib.Response::Values(values)) => "\{values.length()} value(s)"
Some(@lib.Response::Status(status)) => status_name(status)
Some(@lib.Response::Counter(value)) => value.to_string()
Some(@lib.Response::Version(version)) => version
Some(@lib.Response::Stats(entries)) => "\{entries.length()} stat(s)"
}
}
///|
fn run() -> Unit raise {
println("== request encoding ==")
let set_request : @lib.Request = @lib.Request::Storage(
op=@lib.StorageOp::Set,
key="foo",
flags=0,
exptime=0,
value=b"bar",
cas=None,
noreply=false,
)
println("set -> \{render(set_request.to_bytes())}")
let gets_request : @lib.Request = @lib.Request::Get(
keys=["foo", "bar"],
with_cas=true,
)
println("gets -> \{render(gets_request.to_bytes())}")
let delete_request : @lib.Request = @lib.Request::Delete(
key="foo",
noreply=false,
)
println("del -> \{render(delete_request.to_bytes())}")
let gat_request : @lib.Request = @lib.Request::Gat(
keys=["foo"],
exptime=30,
with_cas=false,
)
println("gat -> \{render(gat_request.to_bytes())}")
let quiet_delete : @lib.Request = @lib.Request::Delete(
key="foo",
noreply=true,
)
println("del! -> \{render(quiet_delete.to_bytes())}")
let stats_request : @lib.Request = @lib.Request::Stats(sub=Some("items"))
println("stat -> \{render(stats_request.to_bytes())}")
println(
"flush-> \{render(@lib.Request::FlushAll(delay=Some(10), noreply=false).to_bytes())}",
)
println("")
println("== single-connection client ==")
let connection = @lib.ScriptedConnection::new([
b"STORED\r\n", b"VALUE foo 0 3\r\nbar\r\nEND\r\n",
])
let client = @lib.Client::new(connection)
println("set -> \{status_name(client.set("foo", b"bar"))}")
let values = client.get(["foo"])
println("get -> \{values.length()} value(s)")
for value in values {
println(
" key=\{value.key} flags=\{value.flags} data=\{render(value.data)} cas=\{cas_text(value.cas)}",
)
}
println("wire -> \{render(connection.written())}")
println("")
println("== noreply and administrative commands ==")
let admin = @lib.ScriptedConnection::new([
b"VERSION 1.6.21\r\nSTAT curr_items 2\r\nSTAT version 1.6.21\r\nEND\r\n", b"OK\r\n",
b"OK\r\n",
])
let admin_client = @lib.Client::new(admin)
println("version -> \{admin_client.version()}")
let stats = admin_client.stats()
for entry in stats {
println("stat -> \{entry.name} = \{entry.value}")
}
println("flush -> \{status_name(admin_client.flush_all(None))}")
println("memlimit-> \{status_name(admin_client.cache_memlimit(64))}")
admin_client.send(@lib.Request::Delete(key="gone", noreply=true))
println("wire -> \{render(admin.written())}")
println("")
println("== pipelined batch ==")
let batch = @lib.ScriptedConnection::new([
b"STORED\r\n", b"VALUE foo 0 3\r\nbar\r\nEND\r\n",
])
let batch_client = @lib.Client::new(batch)
let replies = batch_client.pipeline([
@lib.Request::Storage(
op=@lib.StorageOp::Set,
key="foo",
flags=0,
exptime=0,
value=b"bar",
cas=None,
noreply=false,
),
@lib.Request::Delete(key="gone", noreply=true),
@lib.Request::Get(keys=["foo"], with_cas=false),
])
println("wire -> \{render(batch.written())}")
for index in 0.. #\{index} \{reply_text(replies[index])}")
}
println("")
println("== incremental decoding ==")
let decoder = @lib.Decoder::new()
decoder.feed(b"VALUE k 0 5\r\nhel")
println(
"half a response buffered: next() is none -> \{decoder.next() is None}",
)
decoder.feed(b"lo\r\nEND\r\n")
println("after the rest: next() is some -> \{decoder.next() is Some(_)}")
println("bytes left in the decoder: \{decoder.buffered()}")
}
///|
fn main {
run() catch {
err => println("error: \{err}")
}
}