dev_apply.erl - AO Resolution Execution Device
Overview
Purpose: Execute AO resolutions on messages with path-based request routing
Module: dev_apply
Device Name: apply@1.0
Pattern: Base + Request → Resolution
This device executes AO resolutions by applying a request to a base message. It supports path-based execution with prefixes (base: and request:) to explicitly specify which message to retrieve data from, and can operate in two modes: single execution via a path key, or paired execution combining base and request messages.
Supported Modes
- Single Execution: Apply a path from the request/base to execute
- Paired Execution: Combine separate base and request messages via
pairkey - Prefixed Paths: Support
base:andrequest:prefixes for explicit targeting
Dependencies
- HyperBEAM:
hb_ao,hb_maps,hb_path,hb_util,hb_message,hb_http,hb_http_server - Includes:
include/hb.hrl - Testing:
eunit
Public Functions Overview
%% Device Configuration
-spec info(Opts) -> DeviceInfo.
%% Request Handlers
-spec default(Key, Base, Request, Opts) -> Result.
-spec pair(Base, Request, Opts) -> Result.
-spec pair(PathToSet, Base, Request, Opts) -> Result.Public Functions
1. info/1
-spec info(Opts) -> DeviceInfo
when
Opts :: map(),
DeviceInfo :: map().Description: Return device configuration. Excludes specific keys from default handler and provides the default resolution function.
Excluded Keys:<<"keys">>- Reserved for device introspection<<"set">>- Reserved for setting operations<<"set_path">>- Reserved for path setting<<"remove">>- Reserved for removal operations
-module(dev_apply_info_test).
-include_lib("eunit/include/eunit.hrl").
info_structure_test() ->
Info = dev_apply:info(#{}),
?assert(is_map(Info)),
?assert(maps:is_key(excludes, Info)),
?assert(maps:is_key(default, Info)),
Excludes = maps:get(excludes, Info),
?assert(lists:member(<<"keys">>, Excludes)),
?assert(lists:member(<<"set">>, Excludes)).2. default/4
-spec default(Key, Base, Request, Opts) -> Result
when
Key :: binary(),
Base :: map(),
Request :: map(),
Opts :: map(),
Result :: {ok, term()} | {error, map()}.Description: Default handler for key resolution. If both base and request keys are present in the request, delegates to pair/4. Otherwise, evaluates the key as an apply-path.
-module(dev_apply_default_test).
-include_lib("eunit/include/eunit.hrl").
default_single_execution_test() ->
hb:init(),
Base = #{
<<"device">> => <<"apply@1.0">>,
<<"body">> => <<"/~meta@1.0/build/node">>,
<<"other-key">> => <<"data">>
},
Request = #{
<<"path">> => <<"body">>
},
{ok, Result} = hb_ao:resolve(Base, Request, #{}),
?assertEqual(<<"HyperBEAM">>, Result).
default_pair_execution_test() ->
Base = #{
<<"device">> => <<"apply@1.0">>,
<<"container">> => #{ <<"value">> => <<"DATA">> }
},
Request = #{
<<"req-msg">> => #{ <<"path">> => <<"value">> },
<<"base">> => <<"container">>,
<<"request">> => <<"req-msg">>
},
{ok, Result} = dev_apply:default(
<<"undefined">>,
Base,
Request,
#{}
),
?assertEqual(<<"DATA">>, Result).3. pair/3, pair/4
-spec pair(Base, Request, Opts) -> Result
when
Base :: map(),
Request :: map(),
Opts :: map(),
Result :: {ok, term()} | {error, map()}.
-spec pair(PathToSet, Base, Request, Opts) -> Result
when
PathToSet :: binary(),
Base :: map(),
Request :: map(),
Opts :: map(),
Result :: {ok, term()} | {error, map()}.Description: Apply a request message to a base message. Retrieves the base and request paths from the messages, then resolves the request against the base. Optional PathToSet parameter sets the path key in the prepared request.
-module(dev_apply_pair_test).
-include_lib("eunit/include/eunit.hrl").
pair_basic_test() ->
Base = #{
<<"device">> => <<"apply@1.0">>,
<<"data-container">> => #{ <<"key">> => <<"VALUE">> },
<<"base">> => <<"data-container">>
},
Request = #{
<<"path-spec">> => <<"key">>,
<<"request">> => <<"path-spec">>,
<<"path">> => <<"pair">>
},
{ok, Result} = hb_ao:resolve(Base, Request, #{}),
?assertEqual(<<"VALUE">>, Result).
pair_with_prefix_test() ->
{ok, Result} = hb_ao:resolve(
<<
"/~meta@1.0/build",
"/node~apply@1.0&node=TEST&base=request:&request=base:"
>>,
#{}
),
?assertEqual(<<"TEST">>, Result).
pair_custom_path_test() ->
Base = #{
<<"data">> => #{ <<"custom-path">> => <<"RESULT">> }
},
Request = #{
<<"req-msg">> => #{},
<<"base">> => <<"data">>,
<<"request">> => <<"req-msg">>
},
{ok, Result} = dev_apply:default(
<<"custom-path">>,
Base,
Request,
#{}
),
?assertEqual(<<"RESULT">>, Result).Path Prefix System
Supported Prefixes
base: prefix:
- Targets the base message explicitly
- Example:
base:keyretrieveskeyfrom base - Empty after colon:
base:returns entire base message
request: or req: prefix:
- Targets the request message explicitly
- Example:
request:dataretrievesdatafrom request - Empty after colon:
request:returns entire request message
- Checks request first, then base
- Falls back to base if not found in request
Path Examples
% Get key from request first, then base
<<"key">>
% Get key explicitly from base
<<"base:key">>
% Get nested path from request
<<"request:nested/path">>
% Get entire base message
<<"base:">>
% Get entire request message
<<"req:">>Common Patterns
%% Single key execution
Base = #{
<<"device">> => <<"apply@1.0">>,
<<"my-path">> => <<"/~meta@1.0/info/address">>,
<<"data">> => <<"other-data">>
},
Request = #{
<<"path">> => <<"my-path">>
},
{ok, Address} = hb_ao:resolve(Base, Request, #{}).
%% Paired execution
Base = #{
<<"device">> => <<"apply@1.0">>,
<<"storage">> => #{ <<"item">> => <<"VALUE">> },
<<"base">> => <<"storage">>
},
Request = #{
<<"accessor">> => <<"item">>,
<<"request">> => <<"accessor">>,
<<"path">> => <<"pair">>
},
{ok, Value} = hb_ao:resolve(Base, Request, #{}).
%% Using path prefixes
Base = #{
<<"device">> => <<"apply@1.0">>,
<<"base-data">> => <<"FROM-BASE">>
},
Request = #{
<<"source">> => <<"base:base-data">>,
<<"path">> => <<"source">>
},
{ok, Data} = hb_ao:resolve(Base, Request, #{}).
%% HTTP-based execution
Wallet = ar_wallet:new(),
Msg = hb_message:commit(
#{
<<"device">> => <<"apply@1.0">>,
<<"user-path">> => <<"/user-data/key">>,
<<"user-data">> => #{ <<"key">> => <<"DATA">> }
},
#{ priv_wallet => Wallet }
),
Node = hb_http_server:start_node(#{priv_wallet => Wallet}),
{ok, Result} = hb_http:request(
<<"GET">>,
Node,
<<"/user-path">>,
Msg,
#{ priv_wallet => Wallet }
).Execution Flow
Single Execution Mode
1. Find source path (or use entire base if not specified)
2. Find apply-path from request or base
3. Retrieve value at apply-path
4. Execute resolution with path as singleton messagePaired Execution Mode
1. Extract base path from request/base
2. Extract request path from request/base
3. Retrieve base source message
4. Retrieve request source message
5. Set path in request if PathToSet provided
6. Resolve request against baseError Handling
Common Errors
Path Not Found:{error, #{
<<"body">> => <<"Path `/invalid/path` to apply not found.">>
}}{error, #{
<<"body">> => <<"Source path `/source` to apply not found.">>
}}{error, #{
<<"body">> => <<"Path `/bad:path` is invalid.">>
}}Use Cases
1. Dynamic Path Execution
Execute different paths based on request parameters without hardcoding logic.
2. Message Composition
Combine data from multiple sources (base and request) for complex operations.
3. Indirect Resolution
Store paths in messages and execute them dynamically, enabling flexible routing.
4. Prefix-Based Targeting
Explicitly control which message (base or request) provides data, avoiding ambiguity.
5. HTTP Request Routing
Route HTTP requests through signed messages to user-specific paths.
References
- AO Core -
hb_ao.erl - Message Handling -
hb_message.erl - Path Processing -
hb_path.erl - HTTP Server -
hb_http_server.erl
Notes
- Path Resolution: Checks request first, then base (unless prefixed)
- Prefix Support:
base:,request:, andreq:explicitly target messages - Empty Prefix: Returns entire message (e.g.,
base:returns full base) - Excluded Keys: Device reserves certain keys for system operations
- Pair Mode: Automatically invoked when both
baseandrequestkeys present - Default Handler: Routes to eval or pair based on message structure
- Error Messages: Descriptive errors include the problematic path
- HTTP Integration: Works seamlessly with HTTP server for web access
- Signature Support: Compatible with signed messages via
hb_message:commit - Dynamic Execution: Paths can reference other paths for multi-level resolution
- Source Key: Optional
sourcekey specifies base message location - Apply-Path Key: Specifies which path to execute on the base
- Normalization: All paths normalized to binary format
- Root Path: Empty paths normalize to
/ - Testing: Comprehensive test suite covers all execution modes