This guide will help you upgrade your code from yawc 0.2.x to 0.3.0. The 0.3 release includes several breaking changes focused on API simplification and improved architecture.
- Quick Migration Checklist
- Breaking Changes
- New Features
- Step-by-Step Migration
- Common Migration Patterns
- Troubleshooting
- Replace all
FrameViewwithFrame - Update field access to use accessor methods
- Remove
loggingfeature flag (now always enabled) - Remove
jsonfeature flag (addserde_jsondirectly if needed) - Update
reqwestto 0.13 if using thereqwestfeature - Update imports (if using
FrameViewspecifically)
The FrameView type has been removed. All frame operations now use the unified Frame type.
use yawc::frame::FrameView;
let frame = FrameView::text("Hello");
ws.send(frame).await?;
while let Some(frame) = ws.next().await {
match frame.opcode {
OpCode::Text => println!("{}", frame.as_str()),
_ => {}
}
}use yawc::frame::Frame;
let frame = Frame::text("Hello");
ws.send(frame).await?;
while let Some(frame) = ws.next().await {
match frame.opcode() {
OpCode::Text => println!("{}", frame.as_str()),
_ => {}
}
}Changes needed:
- Replace
FrameViewwithFramein all imports and type annotations - Update field access to use methods (see next section)
Direct field access on Frame is no longer possible. Use accessor methods instead.
| 0.3.x (Old) | 0.3.x (New) | Notes |
|---|---|---|
frame.opcode |
frame.opcode() |
Returns OpCode |
frame.payload |
frame.payload() |
Returns &Bytes |
frame.fin |
frame.is_fin() |
Returns bool |
| N/A | frame.is_compressed() |
New accessor for compression flag |
| N/A | frame.into_parts() |
Returns (OpCode, bool, Bytes) - consumes frame |
let opcode = frame.opcode;
let payload = frame.payload.clone();
let is_final = frame.fin;
// Pattern matching
match frame.opcode {
OpCode::Text => {
let text = std::str::from_utf8(&frame.payload)?;
println!("{}", text);
}
_ => {}
}let opcode = frame.opcode();
let payload = frame.payload().clone();
let is_final = frame.is_fin();
// Pattern matching - use method call
match frame.opcode() {
OpCode::Text => {
let text = frame.as_str(); // Helper method for text frames
println!("{}", text);
}
_ => {}
}
// Or destructure if you need ownership
let (opcode, is_fin, payload) = frame.into_parts();
match opcode {
OpCode::Text => {
let text = std::str::from_utf8(&payload)?;
println!("{}", text);
}
_ => {}
}The logging feature flag has been removed. Logging is now always available through the log crate.
Before (0.1.x):
[dependencies]
yawc = { version = "0.1", features = ["logging"] }After (0.3.x):
[dependencies]
yawc = "0.3"
# logging is now always available via the log crateThe json feature flag has been removed. If you need JSON serialization, add serde_json directly to your dependencies.
Before (0.1.x):
[dependencies]
yawc = { version = "0.1", features = ["json"] }After (0.3.x):
[dependencies]
yawc = "0.3"
serde_json = "1.0" # Add directly if needed
serde = { version = "1.0", features = ["derive"] }smol: Support for the smol async runtime (see examples/client_smol.rs)
If you're using the reqwest feature, update your reqwest dependency to 0.13.
Before (0.1.x):
[dependencies]
yawc = { version = "0.1", features = ["reqwest"] }
reqwest = "0.12"After (0.3.x):
[dependencies]
yawc = { version = "0.3", features = ["reqwest"] }
reqwest = "0.13"Note: reqwest 0.13 has its own breaking changes. Consult the reqwest changelog if you use reqwest directly in your code.
While migrating, you may want to take advantage of new features in 0.3:
Protect against incomplete fragmented messages:
use yawc::{WebSocket, Options};
use std::time::Duration;
let ws = WebSocket::connect("wss://example.com".parse()?)
.with_options(
Options::default()
.with_fragment_timeout(Duration::from_secs(30))
)
.await?;Create fragmented messages more easily:
use yawc::Frame;
// Send a fragmented text message
let first = Frame::text("Hello, ").with_fin(false);
let last = Frame::continuation("World!");
ws.send(first).await?;
ws.send(last).await?;// Safe string conversion
let text = frame.as_str(); // Returns &str, panics if not UTF-8
// Extract close frame data
if let Some(code) = frame.close_code() {
println!("Close code: {:?}", code);
}
if let Ok(Some(reason)) = frame.close_reason() {
println!("Close reason: {}", reason);
}
// Destructure for ownership
let (opcode, is_fin, payload) = frame.into_parts();yawc 0.3 can work with async runtimes other than tokio:
// See examples/client_smol.rs for a complete example
use smol::net::TcpStream;
// Implement a simple adapter
struct SmolStream(TcpStream);
impl tokio::io::AsyncRead for SmolStream {
// Bridge implementation
}
impl tokio::io::AsyncWrite for SmolStream {
// Bridge implementation
}Update your Cargo.toml:
[dependencies]
# Before
# yawc = { version = "0.2", features = ["logging", "json", "reqwest"] }
# After
yawc = { version = "0.3", features = ["reqwest"] }
serde_json = "1.0" # Only if you were using the json feature
reqwest = "0.13" # Only if you were using the reqwest featureReplace FrameView imports with Frame:
// Before
use yawc::frame::FrameView;
// After
use yawc::frame::Frame;Replace FrameView constructors with Frame:
// Before
let frame = FrameView::text("Hello");
let frame = FrameView::binary(vec![1, 2, 3]);
let frame = FrameView::ping("");
let frame = FrameView::close(CloseCode::Normal, b"Goodbye");
// After
let frame = Frame::text("Hello");
let frame = Frame::binary(vec![1, 2, 3]);
let frame = Frame::ping("");
let frame = Frame::close(CloseCode::Normal, b"Goodbye");Replace direct field access with method calls:
Find and replace patterns:
frame.opcode→frame.opcode()frame.payload→frame.payload()(returns&Bytes, clone if needed)frame.fin→frame.is_fin()
Using your editor:
In VSCode:
Find: \.opcode(?!\()
Replace: .opcode()
Find: \.payload(?!\()
Replace: .payload()
Find: \.fin(?!\()
Replace: .is_fin()
Change direct field access in patterns to method calls:
// Before
match frame.opcode {
OpCode::Text => { /* ... */ }
OpCode::Binary => { /* ... */ }
_ => {}
}
// After
match frame.opcode() {
OpCode::Text => { /* ... */ }
OpCode::Binary => { /* ... */ }
_ => {}
}Run your test suite and verify:
- All WebSocket connections work correctly
- Message sending and receiving functions properly
- Control frames (ping/pong/close) are handled correctly
- Compression still works if you're using it
- Error handling still works as expected
Before (0.1.x):
use yawc::{WebSocket, frame::FrameView};
use futures::StreamExt;
while let Some(frame) = ws.next().await {
match frame.opcode {
OpCode::Text | OpCode::Binary => {
ws.send(frame).await?;
}
_ => {}
}
}After (0.3.x):
use yawc::{WebSocket, frame::Frame};
use futures::StreamExt;
while let Some(frame) = ws.next().await {
match frame.opcode() {
OpCode::Text | OpCode::Binary => {
ws.send(frame).await?;
}
_ => {}
}
}Before (0.1.x):
while let Some(frame) = ws.next().await {
if frame.opcode == OpCode::Text {
let text = std::str::from_utf8(&frame.payload)?;
process_message(text);
}
}After (0.3.x):
while let Some(frame) = ws.next().await {
if frame.opcode() == OpCode::Text {
let text = frame.as_str(); // More convenient helper
process_message(text);
}
}Before (0.1.x):
let mut frames = vec![
FrameView::text("Message 1"),
FrameView::text("Message 2"),
FrameView::binary(vec![1, 2, 3]),
];
for frame in frames {
ws.send(frame).await?;
}After (0.3.x):
let frames = vec![
Frame::text("Message 1"),
Frame::text("Message 2"),
Frame::binary(vec![1, 2, 3]),
];
for frame in frames {
ws.send(frame).await?;
}Before (0.1.x):
if frame.opcode == OpCode::Close {
if let Some(code) = frame.close_code() {
println!("Closing with code: {:?}", code);
}
if let Ok(Some(reason)) = frame.close_reason() {
println!("Reason: {}", reason);
}
}After (0.3.x):
if frame.opcode() == OpCode::Close {
if let Some(code) = frame.close_code() {
println!("Closing with code: {:?}", code);
}
if let Ok(Some(reason)) = frame.close_reason() {
println!("Reason: {}", reason);
}
}New in 0.3.x - Use into_parts() when you need owned data:
// Before (0.1.x) - had to clone
let opcode = frame.opcode;
let payload = frame.payload.clone();
// After (0.3.x) - can get ownership without cloning
let (opcode, is_fin, payload) = frame.into_parts();
// payload is now owned Bytes, no clone neededCause: Direct field access is no longer allowed.
Solution: Use the accessor method:
// Error
let op = frame.opcode;
// Fix
let op = frame.opcode();Cause: FrameView has been removed.
Solution: Replace with Frame:
// Error
use yawc::frame::FrameView;
let frame: FrameView = ...;
// Fix
use yawc::frame::Frame;
let frame: Frame = ...;Cause: Trying to access payload as a field.
Solution: Use payload() method or as_str() helper:
// Error
let text = std::str::from_utf8(&frame.payload)?;
// Fix 1: Use accessor
let text = std::str::from_utf8(frame.payload())?;
// Fix 2: Use helper (better for text frames)
let text = frame.as_str();Cause: payload() returns a reference, not owned data.
Solution: Either clone or use into_parts():
// Option 1: Clone
let payload = frame.payload().clone();
// Option 2: Use into_parts() for ownership
let (opcode, is_fin, payload) = frame.into_parts();Cause: The logging feature has been removed.
Solution: Remove it from your feature flags:
# Before
yawc = { version = "0.2", features = ["logging"] }
# After
yawc = "0.3"Cause: The json feature has been removed.
Solution: Add serde_json directly:
# Before
yawc = { version = "0.2", features = ["json"] }
# After
yawc = "0.3"
serde_json = "1.0"The 0.3 release includes several performance improvements:
- SIMD optimizations for frame masking/unmasking (automatic when available)
- Improved fragment handling with better memory management
- Zero-copy improvements where possible
- More efficient compression handling
Your application should see equal or better performance after upgrading. If you notice regressions, please file an issue with benchmarks.
If you encounter issues not covered in this guide:
- Check the CHANGELOG.md for complete list of changes
- Review the examples for working code
- Read the API documentation
- File an issue on GitHub
- CHANGELOG.md - Complete list of changes
- MIGRATION.md - Migrating from tokio-tungstenite
- API Documentation - Complete API reference
- Examples - Working example code