Expand description
A Rust client for TLCP, the Text Lightstreamer Client Protocol.
Lightstreamer is a server that pushes real-time data to clients over an ordinary web connection. TLCP is the text protocol it speaks. This crate implements TLCP 2.5.0 and nothing else: it opens a session, keeps it alive through the network’s bad behaviour, delivers updates as a typed stream, and gets out of the way. There is no market-data model here, no cache, no reconnect-and-hope layer — just the protocol, done properly.
§A complete program
This connects to the public demo server the specification’s own transcripts use, subscribes to two items, and prints what changes.
use futures_util::StreamExt;
use lightstreamer_rs::{
AdapterSet, Client, ClientConfig, FieldSchema, ItemGroup, ServerAddress, Snapshot,
Subscription, SubscriptionEvent, SubscriptionMode,
};
#[tokio::main]
async fn main() -> lightstreamer_rs::Result<()> {
let config = ClientConfig::builder(ServerAddress::try_new("https://push.lightstreamer.com")?)
.with_adapter_set(AdapterSet::try_new("DEMO")?)
.build()?;
// This example is only interested in the data. Opting out of the
// session events is a `drop`, not an underscore: naming a receiver
// `_session_events` would *keep* it, and a stream that is held but
// never read stalls the client once it fills. See `SessionEvents`.
let (client, session_events) = Client::connect(config).await?;
drop(session_events);
let mut updates = client
.subscribe(
Subscription::new(
SubscriptionMode::Merge,
ItemGroup::from_items(["item1", "item2"])?,
FieldSchema::from_fields(["last_price", "time", "pct_change"])?,
)
.with_snapshot(Snapshot::On),
)
.await?;
while let Some(event) = updates.next().await {
match event {
SubscriptionEvent::Update(update) => {
println!("{}: {:?}", update.item_name(), update.changed_fields());
}
SubscriptionEvent::Rejected(error) => {
eprintln!("the server refused the subscription: {error}");
break;
}
_ => {}
}
}
client.disconnect().await
}§The three things worth knowing before you start
Delivery is a Stream, not a callback. Subscribing gives you an
Updates stream; the client gives you a SessionEvents stream. Both
are ordinary futures_util::Streams carrying closed, matchable enums, so
the borrow checker never lands in the middle of your update handler
(docs/adr/0003-typed-event-stream-as-delivery-surface.md).
Reconnection is automatic; its consequences are not hidden. When the
connection breaks, this crate reconnects. What it will not do is pretend
nothing happened: SessionEvent::Connected carries a Continuity that
says whether the same session came back — in which case whatever you
computed from it is still valid — or whether a new one replaced it, in
which case it is not
(docs/adr/0005-recovery-is-visible-in-the-event-stream.md). An
application holding an order book or a portfolio needs that distinction,
and TLCP is one of the few protocols that actually provides it.
Streams are bounded and lossless. Every stream this crate hands you has
a fixed capacity and, when it fills, the client blocks rather than
discarding anything — so a consumer that stops reading stalls the client
instead of silently losing data. The full reasoning, and what to do if you
genuinely cannot keep up, is on Updates.
§Two things that catch people out
Null and empty are different values. TLCP distinguishes a field with no
value from a field holding the empty string, and so does
FieldValue: Null and Text("") are not equal and do not mean the
same thing. A quote adapter with no bid to report sends the first; one
whose status line is deliberately blank sends the second. Collapsing them
is a decision you make explicitly, with
FieldValue::text or FieldValue::text_or — this crate will not make
it for you. Note also the two levels of absence:
ItemUpdate::field returns None when the schema has no such field at
all, and Some(FieldValue::Null) when the field exists and is null.
Item and field names come from you, not from the server. TLCP transmits
counts, never names [docs/spec/04-notifications.md §3.1]. So
ItemUpdate::item_name can only report a name you supplied — which you
do by spelling out the list with ItemGroup::from_items and
FieldSchema::from_fields. If you subscribe by naming a server-side
group with ItemGroup::try_new instead, the server resolves it and this
crate never learns what the items are called, so names fall back to the
1-based position rendered in decimal. That stand-in is deliberately not a
lookup key: ItemUpdate::field_by_name with "3" returns None rather
than the third field, so a placeholder can never be mistaken for a real
name. Use ItemUpdate::declared_item_name when you need to know which
you got.
§Shutdown
Dropping the Client stops everything: the background tasks, the socket,
and every stream still outstanding. Dropping an Updates stream
unsubscribes. Nothing needs to be deregistered and nothing needs
std::process::exit.
§What is deliberately not here
The transports, the wire parser and the session state machine are private.
They are the crate’s job, not yours, and keeping them private is what lets
them change without breaking you. What lib.rs re-exports is the
whole public surface and the whole semantic-versioning promise.
§Testing your own code against this crate
An ItemUpdate cannot be constructed from outside, because forging one
that a session never produced is not something an application should be
able to do by accident. Your own parsing layer still deserves unit tests,
so the off-by-default test-util feature adds one thing and nothing else:
test_util::ItemUpdateBuilder, which assembles an update with no session
behind it. Enable it under [dev-dependencies] and it never reaches your
release build.
§Errors
Everything fallible returns Result, and Error has one variant per
condition worth reacting to. Codes the server supplied are preserved as
numbers in ServerError, never flattened into a message — and which of
the protocol’s two code catalogs a number belongs to is decided by the
variant carrying it, because the two overlap. See error.
§Provenance
Version 1.0.0 onwards is an independently authored implementation licensed under MIT. Versions up to and including 0.3.3 were GPL-3.0-only and share no code with this one; they remain published, under that licence, as historical versions.
Every wire behaviour in this crate is derived from the official
specification and cites the chapter of docs/spec/ it comes from. See
docs/adr/0001-mit-relicensing-clean-room.md.
Re-exports§
pub use config::AdapterSet;pub use config::ClientConfig;pub use config::ClientConfigBuilder;pub use config::ConfigError;pub use config::ConnectionOptions;pub use config::Credentials;pub use config::MAX_TIMING;pub use config::RetryPolicy;pub use config::ServerAddress;pub use config::Transport;pub use error::Error;pub use error::Result;pub use error::ServerError;pub use error::TransportError;
Modules§
- config
- Everything a client needs to be told before it can open a session.
- error
- The public error taxonomy.
Structs§
- Changed
Fields - Iterator over the fields an
ItemUpdatechanged; seeItemUpdate::changed_fields. - Client
- A live Lightstreamer session.
- Command
Fields - Where the key and the command sit in a
COMMAND-mode field schema [docs/spec/04-notifications.md§3.2]. - Connected
- The session is bound to a server and streaming.
- Field
- One field of an
ItemUpdate: where it sits, what it is called, what it holds, and whether this update changed it. - Field
Schema - Which fields a subscription asks for.
- Fields
- Iterator over every field of an
ItemUpdate; seeItemUpdate::fields. - Frequency
Limit - A checked decimal frequency, in updates per second.
- Item
Group - Which items a subscription covers.
- Item
Update - One real-time update, decoded: the new state of one item of one subscription, plus which of its fields this update actually changed.
- Message
- A message on its way to the server’s Metadata Adapter.
- Message
Outcome - The report of one message’s fate.
- Recovery
- Where a recovered flow actually resumed, relative to where it was asked to.
- Resubscribed
- One subscription re-created on a session that replaced a lost one.
- Sequence
Name - The name of an ordered message sequence
[
docs/spec/03-requests.md§12.1]. - Session
Events - The stream of session-level events.
- Subscription
- A request to subscribe to a set of items.
- Subscription
Id - An opaque handle identifying one subscription for as long as you hold it.
- Updates
- The stream of one subscription’s events.
Enums§
- Buffer
Size - How much the server may buffer per item before it has to drop or merge
[
docs/spec/03-requests.md§6.1]. - Closed
Reason - Why the session ended for good.
- Continuity
- What a newly bound session means for state you built on the previous one.
- Disconnect
Reason - Why the session stopped streaming.
- Field
Value - The value of one field of one item.
- Filtering
- Whether the server may drop or merge updates for a subscription to keep up
[
docs/spec/04-notifications.md§3.8]. - Item
Command - What a
COMMAND-mode update did to the row named by its key. - MaxFrequency
- A ceiling on how often the server may send updates for an item
[
docs/spec/03-requests.md§6.1]. - Message
Result - What became of a message you sent.
- Recovery
Outcome - The three ways a recovery can turn out.
- Server
Info - Something the server said about itself or about the session, which changes nothing but may be worth logging.
- Session
Event - Everything the client tells you about the session.
- Snapshot
- Whether the server should send the current state before the live flow
[
docs/spec/03-requests.md§6.1]. - State
Validity - What a bind means for state an application derived from an earlier one.
- Subscription
Event - Everything one subscription can tell you.
- Subscription
Mode - How the server treats the items of a subscription
[
docs/spec/03-requests.md§6.1]. - Update
Frequency - The update rate the server has settled on for a subscription
[
docs/spec/04-notifications.md§3.8]. - Update
Kind - Whether an update carries part of an item’s initial state or a live change.