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:

  1. 4-byte big-endian length header indicating the JSON payload size
  2. 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

args()

@type args() :: map()

command()

@type command() :: atom() | String.t()

error_reason()

@type error_reason() :: String.t()

request_id()

@type request_id() :: non_neg_integer()

result()

@type result() :: any()

Functions

create_error_response(id, error_reason)

@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" => _}

create_success_response(id, result)

@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" => _}

decode_response(data)

@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}

encode_request(id, command, args)

@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"})

extract_request_id(arg1)

@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

generate_request_id()

@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.

validate_request(request)

@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"}

validate_response(response)

@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"}