Snakepit.Bridge.Protocol (snakepit v0.1.2)
Wire protocol for Python bridge communication.
This module handles the encoding and decoding of messages between Elixir and Python processes using a JSON-based protocol with 4-byte length headers for proper framing.
Protocol Format
Each message consists of:
- 4-byte big-endian length header indicating the JSON payload size
- JSON payload containing the actual message data
Request Format
%{
"id" => integer(),
"command" => string(),
"args" => map(),
"timestamp" => iso8601_string()
}
Response Format
# Success
%{
"id" => integer(),
"success" => true,
"result" => any(),
"timestamp" => iso8601_string()
}
# Error
%{
"id" => integer(),
"success" => false,
"error" => string(),
"timestamp" => iso8601_string()
}
Features
- Request/response correlation using unique IDs
- Timestamping for debugging and metrics
- Structured error handling
- JSON encoding for cross-language compatibility
- Length-prefixed framing for reliable message boundaries
Summary
Functions
Creates a standardized error response.
Creates a standardized success response.
Decodes a response received from the Python bridge.
Encodes a request for sending to the Python bridge.
Extracts the request ID from a message.
Generates a unique request ID.
Validates that a request has all required fields.
Validates that a response has all required fields.
Types
Functions
@spec create_error_response(request_id(), error_reason()) :: map()
Creates a standardized error response.
Generates a properly formatted error response that can be sent back to the caller when an error occurs during request processing.
Examples
iex> Snakepit.Bridge.Protocol.create_error_response(1, "Command failed")
%{"id" => 1, "success" => false, "error" => "Command failed", "timestamp" => _}
@spec create_success_response(request_id(), result()) :: map()
Creates a standardized success response.
Generates a properly formatted success response that can be sent back to the caller when a request completes successfully.
Examples
iex> Snakepit.Bridge.Protocol.create_success_response(1, %{"status" => "ok"})
%{"id" => 1, "success" => true, "result" => %{"status" => "ok"}, "timestamp" => _}
@spec decode_response(binary()) :: {:ok, request_id(), result()} | {:error, request_id(), error_reason()} | {:error, :decode_error} | {:error, :malformed_response} | {:error, :binary_data}
Decodes a response received from the Python bridge.
Parses JSON response data and extracts the request ID, success status, and either the result or error information.
Examples
iex> json = ~s({"id":1,"success":true,"result":{"status":"ok"}})
iex> Snakepit.Bridge.Protocol.decode_response(json)
{:ok, 1, %{"status" => "ok"}}
iex> json = ~s({"id":2,"success":false,"error":"Something went wrong"})
iex> Snakepit.Bridge.Protocol.decode_response(json)
{:error, 2, "Something went wrong"}
iex> Snakepit.Bridge.Protocol.decode_response("invalid json")
{:error, :decode_error}
@spec encode_request(request_id(), command(), args()) :: binary()
Encodes a request for sending to the Python bridge.
Creates a properly formatted request message with a unique ID, command, arguments, and timestamp. The message is encoded as JSON for transmission.
Examples
iex> Snakepit.Bridge.Protocol.encode_request(1, :ping, %{})
~s({"id":1,"command":"ping","args":{},"timestamp":"2024-01-01T00:00:00Z"})
iex> Snakepit.Bridge.Protocol.encode_request(2, "create_program", %{signature: %{}})
~s({"id":2,"command":"create_program","args":{"signature":{}},"timestamp":"2024-01-01T00:00:00Z"})
@spec extract_request_id(map()) :: request_id() | nil
Extracts the request ID from a message.
Safely extracts the request ID from either a request or response message. Returns nil if the ID is not present or not valid.
Examples
iex> Snakepit.Bridge.Protocol.extract_request_id(%{"id" => 42})
42
iex> Snakepit.Bridge.Protocol.extract_request_id(%{"command" => "ping"})
nil
@spec generate_request_id() :: request_id()
Generates a unique request ID.
Creates a monotonically increasing request ID that can be used for request/response correlation. Uses System.unique_integer for high performance without serialization bottlenecks.
@spec validate_request(map()) :: :ok | {:error, :invalid_command | :invalid_id | :missing_command | :missing_id}
Validates that a request has all required fields.
Checks that a request map contains the necessary fields and that they have the correct types.
Examples
iex> request = %{"id" => 1, "command" => "ping", "args" => %{}}
iex> Snakepit.Bridge.Protocol.validate_request(request)
:ok
iex> Snakepit.Bridge.Protocol.validate_request(%{"id" => 1})
{:error, "Missing required field: command"}
@spec validate_response(map()) :: :ok | {:error, :invalid_id | :invalid_success | :missing_error | :missing_id | :missing_result | :missing_success}
Validates that a response has all required fields.
Checks that a response map contains the necessary fields for either a success or error response.
Examples
iex> response = %{"id" => 1, "success" => true, "result" => %{}}
iex> Snakepit.Bridge.Protocol.validate_response(response)
:ok
iex> response = %{"id" => 1, "success" => false, "error" => "Failed"}
iex> Snakepit.Bridge.Protocol.validate_response(response)
:ok
iex> Snakepit.Bridge.Protocol.validate_response(%{"id" => 1})
{:error, "Missing required field: success"}