diff options
-rw-r--r-- | acf/model/model.lua | 8 | ||||
-rw-r--r-- | acf/model/node.lua | 34 | ||||
-rw-r--r-- | acf/model/root.lua | 12 | ||||
-rw-r--r-- | acf/model/set.lua | 8 | ||||
-rw-r--r-- | acf/transaction/init.lua | 4 | ||||
-rwxr-xr-x | dev-shell | 5 | ||||
-rw-r--r-- | protocol.txt | 4 | ||||
-rw-r--r-- | server.lua | 5 |
8 files changed, 59 insertions, 21 deletions
diff --git a/acf/model/model.lua b/acf/model/model.lua index 365fe20..e94d3fb 100644 --- a/acf/model/model.lua +++ b/acf/model/model.lua @@ -71,18 +71,20 @@ function Model:init(context) return isinstance(res, Field) and node.BoundField(self, res) or nil end + function mt.has_field(name) return mt.field(name) end + function mt.mmeta(name) return mt.field(name):meta() end - function mt.get(k) + function mt.load(k, create) local f = mt.field(k) if f then if f.compute then return f:compute() end - return f:load() + return f:load(create) end return mt.class[k] end - function mt.set(k, v) + function mt.save(k, v) local f = mt.field(k) if not f then raise(mt.path, 'Field named '..k..' does not exist') end f:save(v) diff --git a/acf/model/node.lua b/acf/model/node.lua index b73cf52..748b20d 100644 --- a/acf/model/node.lua +++ b/acf/model/node.lua @@ -53,9 +53,10 @@ function TreeNode:init(context) local mt = getmetatable(self) update(mt, context) - function mt.set(k, v) rawset(self, k, v) end + function mt.save(k, v) rawset(self, k, v) end + function mt.get(k, create) return mt.load(k, create) end function mt.__index(t, k) return mt.get(k) end - function mt.__newindex(t, k, v) mt.set(k, v) end + function mt.__newindex(t, k, v) mt.save(k, v) end function mt.has_permission(user, permission) local p = permission..mt.path @@ -72,19 +73,28 @@ function TreeNode:init(context) mt.txn.validable[mt.path] = mt.addr end -function TreeNode:search(path) +function TreeNode:search(path, create) + if type(path) == 'string' then path = pth.split(path) end + if #path == 0 then return self end - local cpath = getmetatable(self).path + local mt = getmetatable(self) local name = path[1] - local next = self[name] - if next == nil then raise(cpath, 'Subordinate does not exist: '..name) end + if not mt.has_field(name) then + raise(mt.path, 'Field does not exist: '..name) + end + + local next = mt.get(name, create) + if next == nil and (not create or #path > 1) then + raise(mt.path, 'Subordinate does not exist: '..name) + end table.remove(path, 1) if #path > 0 and type(next) ~= 'table' then - raise(pth.join(cpath, name), 'Is a primitive value') + raise(pth.join(mt.path, name), 'Is a primitive value') end - return TreeNode.search(next, path) + + return TreeNode.search(next, path, create) end @@ -100,6 +110,8 @@ function Collection:init(context, params) mt.field = BoundField(self, params.field) mt.meta = {type='collection', members=mt.field:meta('$')} + + function mt.has_field(name) return true end function mt.mmeta(name) return mt.meta.members end function mt.members() return mt.txn:get(mt.addr) or {} end @@ -109,8 +121,8 @@ function Collection:init(context, params) end end - function mt.get(k) return mt.field:load(k) end - function mt.set(k, v) mt.field:save(k, v) end + function mt.load(k, create) return mt.field:load(k, create) end + function mt.save(k, v) mt.field:save(k, v) end end @@ -148,6 +160,6 @@ function pairs(tbl) if not object.isinstance(tbl, TreeNode) then return rawpairs(tbl) end local mt = getmetatable(tbl) local res = {} - for _, member in ipairs(mt.members()) do res[member] = mt.get(member) end + for _, member in ipairs(mt.members()) do res[member] = mt.load(member) end return rawpairs(res) end diff --git a/acf/model/root.lua b/acf/model/root.lua index a786064..8571527 100644 --- a/acf/model/root.lua +++ b/acf/model/root.lua @@ -5,20 +5,28 @@ See LICENSE file for license details module(..., package.seeall) +local node = require('acf.model.node') local model = require('acf.model.model') -local super = require('acf.object').super +local object = require('acf.object') +local pth = require('acf.path') RootModel = model.new() function RootModel:init(txn) - super(self, RootModel):init{txn=txn, path='/', addr='/volatile'} + object.super(self, RootModel):init{txn=txn, path='/', addr='/volatile'} end function RootModel:has_permission(user, permission) return permission == 'read' end +function RootModel:meta(path) + local obj = self:search(path, true) + if object.isinstance(obj, node.TreeNode) then return node.meta(obj) end + return node.mmeta(self:search(pth.parent(path), true), pth.name(path)) +end + function register(name, addr, field) RootModel[name] = model.to_field(field, addr) end diff --git a/acf/model/set.lua b/acf/model/set.lua index be6c2de..2ab9d6d 100644 --- a/acf/model/set.lua +++ b/acf/model/set.lua @@ -26,7 +26,7 @@ function Set:init(context, params) local mt = getmetatable(self) mt.meta.type = 'set' - function mt.__index(t, k) return find(self, k) and k end + function mt.get(k, create) return (create or find(self, k)) and k end function mt.__newindex(t, k, v) assert(v == nil) @@ -35,15 +35,15 @@ function Set:init(context, params) local len = #mt.members() while i < len do - mt.set(i, mt.get(i + 1)) + mt.save(i, mt.load(i + 1)) i = i + 1 end - mt.set(len, nil) + mt.save(len, nil) end end function add(set, value) local mt = getmetatable(set) - if not find(set, value) then mt.set(#mt.members() + 1, value) end + if not find(set, value) then mt.save(#mt.members() + 1, value) end end diff --git a/acf/transaction/init.lua b/acf/transaction/init.lua index 48b1832..4c48d94 100644 --- a/acf/transaction/init.lua +++ b/acf/transaction/init.lua @@ -125,7 +125,9 @@ function Transaction:set_multiple(mods) end end -function Transaction:search(path) return self.root:search(pth.split(path)) end +function Transaction:search(path) return self.root:search(path) end + +function Transaction:meta(path) return self.root:meta(path) end function Transaction:commit() self:check() @@ -69,6 +69,7 @@ Available commands: Create/update object: put <path> <JSON content> Add member to set: post <path> <JSON content> Delete object: delete <path> + Fetch metadata: meta <path> Start transaction: start Example: put /awall/zone/internet '{"iface": ["eth0"]}' @@ -82,6 +83,10 @@ EOF _acf_start_req / Transaction-ID TXN_ID -X POST } + function meta { + _acf_req "/meta$1" + } + function _acf_obj_req { local path=/config$1 shift diff --git a/protocol.txt b/protocol.txt index bc0998b..8a2ae9d 100644 --- a/protocol.txt +++ b/protocol.txt @@ -85,6 +85,10 @@ resp: action-specific JSON - for time-consuming actions, can return multiple JSON documents, each containing a status update +Get metadata (for a potentially non-existent object): +req: GET /meta/<path> +resp: as the meta attribute in get object response + Use of HTTP status codes: @@ -110,6 +110,11 @@ return function(env) local success, code, hdr, res = acf.call( function() + if stringy.startswith(path, '/meta/') then + if method ~= 'GET' then return 405 end + return 200, nil, txn:meta(string.sub(path, 6, -1)) + end + if stringy.startswith(path, '/config/') then path = string.sub(path, 8, -1) |