///|
/// Convenience constructors keep destinations opaque; broker semantics are external.
/// Omitted credentials stay absent; explicit empty strings are retained.
/// CONNECT values are literal. encode/send validates forbidden delimiters.
pub fn connect(
host : String,
heartbeat~ : String = "0,0",
login? : String,
passcode? : String,
) -> Frame {
let headers = [
("accept-version", "1.2"),
("host", host),
("heart-beat", heartbeat),
]
if login is Some(value) {
headers.push(("login", value))
}
if passcode is Some(value) {
headers.push(("passcode", value))
}
Frame::new("CONNECT", headers)
}
///|
pub fn send_message(
destination : String,
body : Bytes,
transaction~ : String = "",
receipt~ : String = "",
) -> Frame {
let h = [
("destination", destination),
("content-type", "application/octet-stream"),
]
if transaction != "" {
h.push(("transaction", transaction))
}
if receipt != "" {
h.push(("receipt", receipt))
}
Frame::new("SEND", h, body~)
}
///|
pub fn subscribe(
id : String,
destination : String,
ack~ : String = "auto",
) -> Frame {
Frame::new("SUBSCRIBE", [
("id", id),
("destination", destination),
("ack", ack),
])
}
///|
pub fn unsubscribe(id : String) -> Frame {
Frame::new("UNSUBSCRIBE", [("id", id)])
}
///|
pub fn acknowledge(
id : String,
negative~ : Bool = false,
transaction~ : String = "",
) -> Frame {
let h = [("id", id)]
if transaction != "" {
h.push(("transaction", transaction))
}
Frame::new(if negative { "NACK" } else { "ACK" }, h)
}
///|
pub fn begin_transaction(id : String) -> Frame {
Frame::new("BEGIN", [("transaction", id)])
}
///|
pub fn finish_transaction(id : String, commit~ : Bool = true) -> Frame {
Frame::new(if commit { "COMMIT" } else { "ABORT" }, [("transaction", id)])
}
///|
pub fn disconnect(receipt : String) -> Frame {
Frame::new("DISCONNECT", [("receipt", receipt)])
}
///|
/// Explicit negotiation while the original connect() remains a 1.2 constructor.
/// Legacy 1.0-only CONNECT omits host/version/heartbeat negotiation headers.
pub fn connect_versions(
host : String,
versions : Array[Version],
heartbeat~ : String = "0,0",
login? : String,
passcode? : String,
) -> Frame raise ProtocolError {
if versions.is_empty() || versions.length() > 3 {
raise Invalid("offer one to three versions")
}
let mut text = ""
for version in versions {
text = text + (if text == "" { "" } else { "," }) + version.wire()
}
ignore(offered_versions(text))
let (cx, cy) = heartbeat_pair(heartbeat)
let headers : Array[(String, String)] = []
if versions == [V10] {
if cx != 0 || cy != 0 {
raise Invalid("STOMP 1.0 has no heartbeat negotiation")
}
} else {
headers.push(("accept-version", text))
headers.push(("host", host))
headers.push(("heart-beat", heartbeat))
}
if login is Some(value) {
headers.push(("login", value))
}
if passcode is Some(value) {
headers.push(("passcode", value))
}
Frame::new("CONNECT", headers)
}
///|
/// 1.0 accepts id-less subscriptions; 1.1/1.2 require an id.
pub fn subscribe_versioned(
version : Version,
destination : String,
id~ : String = "",
ack~ : String = "auto",
) -> Frame raise ProtocolError {
if ack != "auto" &&
ack != "client" &&
(ack != "client-individual" || version == V10) {
raise Invalid("invalid versioned acknowledgement mode")
}
let headers = [("destination", destination), ("ack", ack)]
if id != "" {
headers.push(("id", id))
} else if version != V10 {
raise Invalid("subscription id required")
}
Frame::new("SUBSCRIBE", headers)
}
///|
/// For 1.0 only, unsubscribe by destination when no id was supplied.
pub fn unsubscribe_destination(destination : String) -> Frame {
Frame::new("UNSUBSCRIBE", [("destination", destination)])
}
///|
/// Caller supplies the original MESSAGE; formula-like reinterpretation of IDs is avoided.
pub fn acknowledge_message(
version : Version,
message : Frame,
negative~ : Bool = false,
transaction~ : String = "",
) -> Frame raise ProtocolError {
if message.command != "MESSAGE" {
raise Invalid("acknowledgement requires MESSAGE")
}
if negative && version == V10 {
raise Invalid("NACK unavailable in STOMP 1.0")
}
let headers : Array[(String, String)] = []
match version {
V12 => headers.push(("id", required(message, "ack")))
V11 => {
headers.push(("message-id", required(message, "message-id")))
headers.push(("subscription", required(message, "subscription")))
}
V10 => headers.push(("message-id", required(message, "message-id")))
}
if transaction != "" {
headers.push(("transaction", transaction))
}
Frame::new(if negative { "NACK" } else { "ACK" }, headers)
}