summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--acf/model/model.lua8
-rw-r--r--acf/model/node.lua34
-rw-r--r--acf/model/root.lua12
-rw-r--r--acf/model/set.lua8
-rw-r--r--acf/transaction/init.lua4
-rwxr-xr-xdev-shell5
-rw-r--r--protocol.txt4
-rw-r--r--server.lua5
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()
diff --git a/dev-shell b/dev-shell
index eaacde9..d04935e 100755
--- a/dev-shell
+++ b/dev-shell
@@ -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:
diff --git a/server.lua b/server.lua
index c4f3fa4..8ad6d88 100644
--- a/server.lua
+++ b/server.lua
@@ -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)