Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ pub fn main() {
stratus.Binary(_msg) -> stratus.continue(state)
stratus.User(Close) -> {
let assert Ok(_) =
stratus.close_with_reason(conn, stratus.GoingAway(<<"goodbye">>))
stratus.close(conn, stratus.GoingAway(<<"goodbye!">>))
stratus.stop()
}
}
Expand Down
53 changes: 44 additions & 9 deletions src/stratus.gleam
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import exception
import gleam/bit_array
import gleam/bool
import gleam/bytes_tree.{type BytesTree}
import gleam/crypto
import gleam/erlang/charlist
Expand Down Expand Up @@ -72,6 +73,11 @@ pub type SocketReason {
Exbadseq
}

pub type CustomCloseError {
SocketFail(SocketReason)
InvalidCode
}

fn convert_socket_reason(reason: socket.SocketReason) -> SocketReason {
case reason {
socket.Badarg -> Badarg
Expand Down Expand Up @@ -267,7 +273,7 @@ pub type InitializationError {
HandshakeFailed(HandshakeError)
// The actor failed to start, most likely due to a timeout in your `init`
ActorFailed(actor.StartError)
//
//
FailedToTransferSocket(SocketReason)
}

Expand Down Expand Up @@ -669,25 +675,38 @@ pub fn send_ping(conn: Connection, data: BitArray) -> Result(Nil, SocketReason)
|> result.map_error(convert_socket_reason)
}

/// This will close the WebSocket connection.
pub fn close(conn: Connection) -> Result(Nil, SocketReason) {
close_with_reason(conn, Normal(body: <<>>))
}

pub type CloseReason {
NotProvided
/// Status code: 1000
Normal(body: BitArray)
/// Status code: 1001
GoingAway(body: BitArray)
/// Status code: 1002
ProtocolError(body: BitArray)
/// Status code: 1003
UnexpectedDataType(body: BitArray)
/// Status code: 1007
InconsistentDataType(body: BitArray)
/// Status code: 1008
PolicyViolation(body: BitArray)
/// Status code: 1009
MessageTooBig(body: BitArray)
/// Status code: 1010
MissingExtensions(body: BitArray)
/// Status code: 1011
UnexpectedCondition(body: BitArray)
/// Use [`close_custom`](#close_custom) to send a custom close code.
Custom(CustomCloseReason)
}

/// Use [`close_custom`](#close_custom) to send a custom close code.
pub opaque type CustomCloseReason {
CustomCloseReason(code: Int, body: BitArray)
}

fn convert_close_reason(reason: CloseReason) -> websocket.CloseReason {
case reason {
NotProvided -> websocket.NotProvided
GoingAway(body:) -> websocket.GoingAway(body:)
InconsistentDataType(body:) -> websocket.InconsistentDataType(body:)
MessageTooBig(body:) -> websocket.MessageTooBig(body:)
Expand All @@ -697,13 +716,29 @@ fn convert_close_reason(reason: CloseReason) -> websocket.CloseReason {
ProtocolError(body:) -> websocket.ProtocolError(body:)
UnexpectedCondition(body:) -> websocket.UnexpectedCondition(body:)
UnexpectedDataType(body:) -> websocket.UnexpectedDataType(body:)
Custom(CustomCloseReason(code:, body:)) ->
websocket.CustomCloseReason(code:, body:)
}
}

/// This closes the WebSocket connection with a particular close reason.
pub fn close_with_reason(
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

from an ergonomics perspective, do you think making the CloseReason non-opaque and just keeping the close and close_with_reason as the only public functions? i'm not really a user of this library, so curious about your perspective

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I think about it, I think you got a point. The only close reason that actually needs to be opaque is the custom one, so that could be in its own opaque type with its own function. I'll get around to it in a few. It could introduce some naming problems with sent and received close reasons, but it's not anything we can't figure out.

/// Closes the connection with a custom close code between 1000 and 4999.
pub fn close_custom(
conn: Connection,
code code: Int,
body body: BitArray,
) -> Result(Nil, CustomCloseError) {
use <- bool.guard(
when: code >= 5000 || code < 1000,
return: Error(InvalidCode),
)

close(conn, Custom(CustomCloseReason(code:, body:)))
|> result.map_error(SocketFail)
}

pub fn close(
conn: Connection,
reason: CloseReason,
because reason: CloseReason,
) -> Result(Nil, SocketReason) {
let reason = convert_close_reason(reason)
let mask = crypto.strong_random_bytes(4)
Expand Down