Class: BSV::Network::Protocol

Inherits:
Object
  • Object
show all
Defined in:
lib/bsv/network/protocol.rb

Overview

Protocol is the base class for all BSV network protocol definitions.

Subclasses declare their commands via the endpoint DSL macro. Each endpoint maps a command name (Symbol) to an HTTP method, a path template, and a response handler. The subscription macro is a placeholder for future WebSocket support.

Subclass isolation is enforced via an inherited hook — each subclass receives its own empty @endpoints and @subscriptions hashes. Adding endpoints to a subclass never affects the parent.

HTTP dispatch routes through call: if a call_<name> escape hatch method exists on the instance, it is called; otherwise default_call interpolates the URL template, makes the HTTP request, and maps the response to a Result.

Example

class MyProtocol < BSV::Network::Protocol
  endpoint :get_tx, :get, '/v1/tx/{txid}'
  endpoint :broadcast, :post, '/v1/tx', response: :json
end

p = MyProtocol.new(base_url: 'https://api.example.com', network: 'main')
MyProtocol.commands #=> #<Set: {:get_tx, :broadcast}>

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(base_url:, api_key: nil, network: nil, http_client: nil) ⇒ Protocol

Returns a new instance of Protocol.

Parameters:

  • base_url (String)

    base URL, may contain {network} placeholder

  • api_key (String, nil) (defaults to: nil)

    API key for authenticated requests

  • network (String, Symbol, nil) (defaults to: nil)

    network name (e.g. ‘main’, ‘test’)

  • http_client (Object, nil) (defaults to: nil)

    injectable HTTP client (used in Task 3)



112
113
114
115
116
117
# File 'lib/bsv/network/protocol.rb', line 112

def initialize(base_url:, api_key: nil, network: nil, http_client: nil)
  @api_key     = api_key
  @network     = network
  @http_client = http_client
  @base_url    = build_base_url(base_url, network)
end

Instance Attribute Details

#api_keyObject (readonly)

Returns the value of attribute api_key.



106
107
108
# File 'lib/bsv/network/protocol.rb', line 106

def api_key
  @api_key
end

#base_urlObject (readonly)

Returns the value of attribute base_url.



106
107
108
# File 'lib/bsv/network/protocol.rb', line 106

def base_url
  @base_url
end

#http_clientObject (readonly)

Returns the value of attribute http_client.



106
107
108
# File 'lib/bsv/network/protocol.rb', line 106

def http_client
  @http_client
end

#networkObject (readonly)

Returns the value of attribute network.



106
107
108
# File 'lib/bsv/network/protocol.rb', line 106

def network
  @network
end

Class Method Details

.commandsSet<Symbol>

Returns a Set of command names declared on this protocol class.

Returns:

  • (Set<Symbol>)


65
66
67
# File 'lib/bsv/network/protocol.rb', line 65

def commands
  Set.new(@endpoints.keys)
end

.endpoint(command_name, http_method, path_template, response: :raw) ⇒ Object

Registers an endpoint definition for this protocol class.

Parameters:

  • command_name (Symbol)

    the command name (e.g. :broadcast)

  • http_method (Symbol)

    :get or :post

  • path_template (String)

    path with {param} placeholders

  • response (Symbol, #call) (defaults to: :raw)

    response handler — :raw, :json, :json_array, or a callable (lambda/proc)



44
45
46
47
48
49
50
# File 'lib/bsv/network/protocol.rb', line 44

def endpoint(command_name, http_method, path_template, response: :raw)
  @endpoints[command_name] = {
    method: http_method,
    path: path_template,
    response: response
  }
end

.endpointsHash

Returns a frozen copy of the endpoints hash for introspection.

Returns:

  • (Hash)


72
73
74
# File 'lib/bsv/network/protocol.rb', line 72

def endpoints
  @endpoints.dup.freeze
end

.inherited(subclass) ⇒ Object

Give each subclass its own isolated @endpoints and @subscriptions hashes. Deep-copies the parent’s endpoints so that existing declarations are inherited but mutations on the subclass do not affect the parent.



86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/bsv/network/protocol.rb', line 86

def inherited(subclass)
  super
  # Deep copy: each endpoint value is a plain hash of scalar values,
  # so a one-level transform_values dup is sufficient.
  parent_endpoints = @endpoints.each_with_object({}) do |(k, v), h|
    h[k] = v.dup
  end
  parent_subscriptions = @subscriptions.each_with_object({}) do |(k, v), h|
    h[k] = v.dup
  end
  subclass.instance_variable_set(:@endpoints,     parent_endpoints)
  subclass.instance_variable_set(:@subscriptions, parent_subscriptions)
end

.subscription(event_name, path, **opts) ⇒ Object

Registers a subscription definition. Placeholder for Phase C WebSocket support. Stored but not callable at runtime.

Parameters:

  • event_name (Symbol)

    the event name

  • path (String)

    WebSocket path

  • opts (Hash)

    additional options (reserved)



58
59
60
# File 'lib/bsv/network/protocol.rb', line 58

def subscription(event_name, path, **opts)
  @subscriptions[event_name] = { path: path }.merge(opts)
end

.subscriptionsHash

Returns a frozen copy of the subscriptions hash for introspection.

Returns:

  • (Hash)


79
80
81
# File 'lib/bsv/network/protocol.rb', line 79

def subscriptions
  @subscriptions.dup.freeze
end

Instance Method Details

#call(command_name, *args, **kwargs) ⇒ Result::Success, ...

Dispatches a command by name.

If a method named call_<command_name> exists on the instance it is used as an escape hatch — that method receives args and kwargs and MUST return a Result. Otherwise default_call is invoked.

Subscriptions are not callable; calling one raises NotImplementedError.

Parameters:

  • command_name (Symbol, String)

    command to invoke

  • args (Array)

    positional arguments forwarded to path interpolation

  • kwargs (Hash)

    keyword arguments forwarded to path interpolation

Returns:

Raises:

  • (ArgumentError)

    when command_name is not registered



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/bsv/network/protocol.rb', line 132

def call(command_name, *args, **kwargs)
  name = command_name.to_sym

  if self.class.subscriptions.key?(name)
    raise NotImplementedError,
          "#{name} is a subscription — WebSocket dispatch is not yet implemented"
  end

  escape = :"call_#{name}"
  if respond_to?(escape, true)
    return kwargs.empty? ? send(escape, *args) : send(escape, *args, **kwargs)
  end

  default_call(name, *args, **kwargs)
end

#default_call(command_name, *args, **kwargs) ⇒ Result::Success, ...

Dispatches a command directly via HTTP, bypassing any escape hatch.

Path placeholders (+param+) are filled from kwargs first; any remaining placeholders are filled positionally from args. Named kwargs take precedence over positional args for the same placeholder.

POST body is taken from kwargs.delete(:body) (removed before path interpolation).

Parameters:

  • command_name (Symbol)

    registered command name

  • args (Array)

    positional path parameters

  • kwargs (Hash)

    named path parameters (and optional :body)

Returns:

Raises:

  • (ArgumentError)

    when command_name is not registered or a required path parameter is missing



163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/bsv/network/protocol.rb', line 163

def default_call(command_name, *args, **kwargs)
  name = command_name.to_sym
  defn = self.class.endpoints[name]
  raise ArgumentError, "unknown command: #{name}" unless defn

  body       = kwargs.delete(:body)
  path       = interpolate_path(defn[:path], args, kwargs)
  uri        = URI("#{@base_url}#{path}")
  request    = build_request(defn[:method], uri, body)
  response   = execute(uri, request)

  map_response(response, defn[:response])
end