Module accessor.lua
Module to access arbitrary depth table keys.
Example:
local t = { some = { arbitrary = { key = "hello world", } } } local key = "some.arbitrary.key" local accessor = Accessor(t) local value = accessor:get("some.arbitrary.key") assert(value == "hello world")
For each key a function will be generated. The function will be cached so the 2nd lookup will be faster.
Warning: make sure to protect yourself from unlimited cache growth! Check lua_cache.
Design use case: looking up user provided keys in json http bodies. The keys are the same for each request, the bodies different.
Info:
- Copyright: (c) 2020-2020 Thijs Schreijer
- License: see LICENSE file
Functions
Accessor.lua_cache () | Returns a new table based cache. |
Accessor.new (options) | Constructor, creates a new accessor object. |
Accessor:create_index (source_table) | Adds an __index meta-method for automatic lookups. |
Accessor:create_proxy (source_table) | Creates a proxy table for automatic lookups. |
Accessor:get (element, source_table) | Gets a value from the table. |
Accessor:get_source () | Returns the source table. |
Accessor:validate_key (element) | Validate a user provided key. |
Functions
- Accessor.lua_cache ()
-
Returns a new table based cache. Minimal cache implementation interface compatible with resty-lru caches. This cache is table based and will allow unlimited growth. If used with OpenResty then use the compatible OpenResty LRU cache when calling new.
A cache should implement the following methods:
ok = cache:set(key, value) value = cache(key)
Returns:
-
a new cache object
- Accessor.new (options)
-
Constructor, creates a new accessor object. The options table supports the following fields:
source
(optional, defaults to an empty table) a table in which the keys will be looked up by default by the newly created accessor objectcache
(optional, defaults to a new lua_cache instance) a cache object to store the generated lookup functions, see lua_cache for the required interface
Parameters:
- options (optional) options table
Returns:
-
accessor object
- Accessor:create_index (source_table)
-
Adds an
__index
meta-method for automatic lookups. Returns the source table. The table will have its metatable replaced by a new one with only an__index
method. So if a lookup fails, it will retry with an 'accessor' lookup.This modifies the original table, but is more performant than a proxy table (see create_proxy as an alternative).
NOTE: where a regular get call would return a
nil+error
, this table will only returnnil
. This is because the__index
metamethod only has a single return value, and hence will drop the error string.Parameters:
- source_table
(optional table, defaults to the
accessor
default table)
Returns:
-
the source table, with a new meta-table (existing one will be replaced)
Usage:
local t = { hello = { world = "tieske" } } local p = Accessor({ source = t }):create_proxy() assert(t == p) -- they are the same tables print("regular: ", p.hello.world) --> "regular: tieske" print("accessor: ", p["hello.world"] --> "accessor: tieske"
- source_table
(optional table, defaults to the
- Accessor:create_proxy (source_table)
-
Creates a proxy table for automatic lookups.
A proxy table is a new (empty) table with an
__index
meta-method that will first do a regular lookup in the source table, and if that fails, it will retry with an 'accessor' lookup.Because this creates a new proxy table, it will not alter the original metatable or methods (see also create_index as an alternative).
NOTE: where a regular get call would return a
nil+error
, this proxy will only returnnil
. This is because the__index
metamethod only has a single return value, and hence will drop the error string.Parameters:
- source_table
(optional table, defaults to the
accessor
default table)
Returns:
-
a new proxy table
Usage:
local t = { hello = { world = "tieske" } } local p = Accessor({ source = t }):create_proxy() assert(t ~= p) -- they are different tables print("regular: ", p.hello.world) --> "regular: tieske" print("accessor: ", p["hello.world"] --> "accessor: tieske"
- source_table
(optional table, defaults to the
- Accessor:get (element, source_table)
-
Gets a value from the table.
Parameters:
- element
(string) the key to lookup (if not a string, then the return value will be
nil
) - source_table
(optional table, defaults to the
accessor
default table)
Returns:
-
the value or
nil+error
Usage:
local t = { hello = { world = "tieske" } } local accessor = Accessor({ source = t }) print(accessor:get("hello.world")) --> "tieske" print(t.does_not_exist.world) --> error! print(accessor:get("does_not_exist.world")) --> "nil, a lookup error" local t2 = { hello = { world = "someone" } } print(accessor:get("hello.world", t2)) --> "someone" -- NOTE: when doing the lookup in t2, the same 'accessor' is used, which means -- it uses the same functions cache, so the 2nd lookup uses the function generated -- by the first lookup, despite it being used on a different table. -- So there is no need to create accessor objects for each table.
- element
(string) the key to lookup (if not a string, then the return value will be
- Accessor:get_source ()
-
Returns the source table.
This is the table provided as
options.source
to new.Returns:
-
source table
- Accessor:validate_key (element)
-
Validate a user provided key.
Checks whether the key generates a proper function.
Parameters:
- element (string) the key to validate
Returns:
true
ornil+error
Usage:
assert(accessor:("this[2].is.valid['as a key'].right")) -- ok assert(accessor:("this is not")) -- fails