summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--acf/model/field.lua40
-rw-r--r--acf/model/init.lua1
-rw-r--r--acf/model/model.lua123
-rw-r--r--acf/model/node.lua19
-rw-r--r--acf/model/root.lua2
-rw-r--r--acf/persistence/backends/null.lua10
-rwxr-xr-xdev-shell12
-rw-r--r--server.lua27
8 files changed, 167 insertions, 67 deletions
diff --git a/acf/model/field.lua b/acf/model/field.lua
index 436ce2c..26d4621 100644
--- a/acf/model/field.lua
+++ b/acf/model/field.lua
@@ -29,12 +29,27 @@ local function auto_ui_name(name)
end
-Field = class()
+Member = class()
-function Field:init(params)
+function Member:init(params)
for k, v in pairs(params or {}) do
if self[k] == nil then self[k] = v end
end
+end
+
+function Member:meta(context)
+ return {
+ name=self.name,
+ description=self.description,
+ ['ui-name']=self['ui-name'] or auto_ui_name(self.name)
+ }
+end
+
+
+Field = class(Member)
+
+function Field:init(params)
+ super(self, Field):init(params)
if self.choice and not self['ui-choice'] then
self['ui-choice'] = map(auto_ui_name, self.choice)
@@ -47,17 +62,16 @@ end
function Field:meta(context)
assert(self.dtype)
- return {
- name=self.name,
- type=self.dtype,
- required=self.required,
- default=self.default,
- choice=self.choice,
- description=self.description,
- ['ui-name']=self['ui-name'] or auto_ui_name(self.name),
- widget=self.widget,
- ['ui-choice']=self['ui-choice']
- }
+ local res = super(self, Field):meta(context)
+
+ res.type = self.dtype
+ res.required = self.required
+ res.default = self.default
+ res.choice = self.choice
+ res.widget = self.widget
+ res['ui-choice'] = self['ui-choice']
+
+ return res
end
function Field:load(context)
diff --git a/acf/model/init.lua b/acf/model/init.lua
index bf67f98..00fc7aa 100644
--- a/acf/model/init.lua
+++ b/acf/model/init.lua
@@ -13,6 +13,7 @@ local fld = require('acf.model.field')
local Field = fld.Field
local model = require('acf.model.model')
+Action = model.Action
new = model.new
local to_field = model.to_field
diff --git a/acf/model/model.lua b/acf/model/model.lua
index e94d3fb..9808157 100644
--- a/acf/model/model.lua
+++ b/acf/model/model.lua
@@ -9,22 +9,60 @@ local raise = require('acf.error').raise
local fld = require('acf.model.field')
local Field = fld.Field
+local Member = fld.Member
local node = require('acf.model.node')
+local BoundMember = node.BoundMember
local object = require('acf.object')
local class = object.class
local super = object.super
local isinstance = object.isinstance
+local pth = require('acf.path')
local util = require('acf.util')
-function to_field(obj, addr)
+local function to_member(obj, addr)
if object.issubclass(obj, Model) then
return fld.Model{model=obj, addr=addr}
end
- return getmetatable(obj).class and obj or obj{addr=addr}
+ local res = getmetatable(obj).class and obj or obj{addr=addr}
+ assert(isinstance(res, Member))
+ return res
+end
+
+function to_field(obj, addr)
+ local res = to_member(obj, addr)
+ assert(isinstance(res, Field))
+ return res
+end
+
+
+Action = class(Member)
+
+function Action:init(params)
+ super(self, Action):init(params)
+
+ if not self.func then error('Function not defined for action') end
+
+ if self.field then
+ assert(type(self.field) == 'table')
+ self.field = to_field(self.field)
+ self.field.addr = '/null/action'
+ end
+
+ getmetatable(self).__newindex = function(t, k, v)
+ assert(k == 'name')
+ rawset(t, k, v)
+ if t.field then t.field.name = v end
+ end
+end
+
+function Action:meta(context)
+ local res = super(self, Action):meta(context)
+ if self.field then res.arg = self.field:meta(context) end
+ return res
end
@@ -32,7 +70,7 @@ function new(base)
if not base then base = Model end
local res = class(base)
- res._fields = base == node.TreeNode and {} or util.copy(base._fields)
+ res.members = base == node.TreeNode and {} or util.copy(base.members)
local mt = util.copy(getmetatable(res))
@@ -42,13 +80,13 @@ function new(base)
assert(v)
local override = t[k]
- if type(v) == 'table' then v = to_field(v) end
+ if type(v) == 'table' then v = to_member(v) end
rawset(t, k, v)
- if isinstance(v, Field) then
+ if isinstance(v, Member) then
v.name = k
- if not override then table.insert(t._fields, k) end
+ if not override then table.insert(t.members, k) end
end
end
@@ -66,34 +104,65 @@ function Model:init(context)
local mt = getmetatable(self)
- function mt.field(name)
- local res = mt.class[name]
- return isinstance(res, Field) and node.BoundField(self, res) or nil
+ local function member(name, strict, tpe)
+ local m = mt.class[name]
+
+ if not tpe then tpe = Member end
+ if not isinstance(m, tpe) then m = nil end
+
+ if m == nil then
+ if strict then raise(mt.path, 'Does not exist: '..name) end
+ return
+ end
+
+ return BoundMember(self, m)
end
- function mt.has_field(name) return mt.field(name) end
+ function mt.valid_member(name) return member(name) end
- function mt.mmeta(name) return mt.field(name):meta() end
+ function mt.mmeta(name) return member(name, true):meta() end
function mt.load(k, create)
- local f = mt.field(k)
- if f then
- if f.compute then return f:compute() end
- return f:load(create)
+ local v = mt.class[k]
+
+ if isinstance(v, Field) then
+ v = BoundMember(self, v)
+ if v.compute then return v:compute() end
+ return v:load(create)
+ end
+
+ if isinstance(v, Action) then
+ local f = v.field and BoundMember(self, v.field)
+ if create then return f and f:load(true) end
+
+ return function(var)
+ if f then f:save(var)
+ elseif var ~= nil then
+ raise(
+ pth.join(mt.path, v.name),
+ 'Action does not accept an input argument'
+ )
+ end
+ local res = v.func(self, f and f:load())
+ if f then mt.txn:set(v.field.addr) end
+ return res
+ end
end
- return mt.class[k]
- end
- 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)
+ return v
end
- mt.meta = {
- type='model',
- fields=util.map(function(f) return mt.mmeta(f) end, self._fields)
- }
+ function mt.save(k, v) return member(k, true, Field):save(v) end
+
+ local function tmeta(tpe)
+ local res = {}
+ for _, name in ipairs(self.members) do
+ local m = member(name, false, tpe)
+ if m then table.insert(res, m:meta()) end
+ end
+ return res
+ end
+ mt.meta = {type='model', fields=tmeta(Field), actions=tmeta(Action)}
function mt.members()
return util.map(function(f) return f.name end, mt.meta.fields)
@@ -101,8 +170,8 @@ function Model:init(context)
function mt.validate()
for _, name in ipairs(mt.members()) do
- local field = mt.field(name)
- if not field.compute then field:validate_saved() end
+ local f = member(name, false, Field)
+ if not f.compute then f:validate_saved() end
end
end
diff --git a/acf/model/node.lua b/acf/model/node.lua
index 748b20d..7852c9e 100644
--- a/acf/model/node.lua
+++ b/acf/model/node.lua
@@ -14,9 +14,9 @@ local pth = require('acf.path')
local update = require('acf.util').update
-BoundField = class()
+BoundMember = class()
-function BoundField:init(parent, field)
+function BoundMember:init(parent, field)
local pmt = getmetatable(parent)
local mt = {}
@@ -80,8 +80,8 @@ function TreeNode:search(path, create)
local mt = getmetatable(self)
local name = path[1]
- if not mt.has_field(name) then
- raise(mt.path, 'Field does not exist: '..name)
+ if not mt.valid_member(name) then
+ raise(mt.path, 'Member does not exist: '..name)
end
local next = mt.get(name, create)
@@ -106,12 +106,13 @@ function Collection:init(context, params)
self.init = nil
self.search = nil
+ local field = BoundMember(self, params.field)
+
local mt = getmetatable(self)
- mt.field = BoundField(self, params.field)
- mt.meta = {type='collection', members=mt.field:meta('$')}
+ mt.meta = {type='collection', members=field:meta('$')}
- function mt.has_field(name) return true end
+ function mt.valid_member(name) return true end
function mt.mmeta(name) return mt.meta.members end
function mt.members() return mt.txn:get(mt.addr) or {} end
@@ -121,8 +122,8 @@ function Collection:init(context, params)
end
end
- function mt.load(k, create) return mt.field:load(k, create) end
- function mt.save(k, v) mt.field:save(k, v) end
+ function mt.load(k, create) return field:load(k, create) end
+ function mt.save(k, v) field:save(k, v) end
end
diff --git a/acf/model/root.lua b/acf/model/root.lua
index 8571527..b2bebb1 100644
--- a/acf/model/root.lua
+++ b/acf/model/root.lua
@@ -14,7 +14,7 @@ local pth = require('acf.path')
RootModel = model.new()
function RootModel:init(txn)
- object.super(self, RootModel):init{txn=txn, path='/', addr='/volatile'}
+ object.super(self, RootModel):init{txn=txn, path='/', addr='/null/root'}
end
function RootModel:has_permission(user, permission)
diff --git a/acf/persistence/backends/null.lua b/acf/persistence/backends/null.lua
new file mode 100644
index 0000000..62a793f
--- /dev/null
+++ b/acf/persistence/backends/null.lua
@@ -0,0 +1,10 @@
+--[[
+Copyright (c) 2012-2013 Kaarle Ritvanen
+See LICENSE file for license details
+--]]
+
+module(..., package.seeall)
+
+backend = require('acf.object').class()
+function backend:get(path) if #path == 0 then return {} end end
+function backend:set(mods) end
diff --git a/dev-shell b/dev-shell
index d04935e..e3bc936 100755
--- a/dev-shell
+++ b/dev-shell
@@ -65,12 +65,12 @@ EOF
else
cat >&2 <<EOF
Available commands:
- Fetch object: get <path>
- 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
+ Fetch object: get <path>
+ Create/update object: put <path> <JSON content>
+ Add member to set/perform action: post <path> <JSON content>
+ Delete object: delete <path>
+ Fetch metadata: meta <path>
+ Start transaction: start
Example: put /awall/zone/internet '{"iface": ["eth0"]}'
diff --git a/server.lua b/server.lua
index 6be07da..ea5d12c 100644
--- a/server.lua
+++ b/server.lua
@@ -120,7 +120,7 @@ return function(env)
if stringy.startswith(path, '/config/') then
path = string.sub(path, 8, -1)
- local parent, name
+ local parent, name, res
if path ~= '/' then
parent = txn:search(acf.path.parent(path))
name = acf.path.name(path)
@@ -128,7 +128,8 @@ return function(env)
if method == 'GET' then
local obj = txn:search(path)
- local res
+
+ if type(obj) == 'function' then return 404 end
if isinstance(obj, mnode.TreeNode) then
if not mnode.has_permission(obj, user, 'read') then
@@ -161,16 +162,20 @@ return function(env)
if method == 'POST' then
local obj = txn:search(path)
- if not acf.object.isinstance(obj, acf.model.set.Set) then
- -- TODO invoke model-specific actions
- return 405
- end
- if not mnode.has_permission(obj, user, 'create') then
- return 403
- end
+ if acf.object.isinstance(obj, acf.model.set.Set) then
+ if not mnode.has_permission(obj, user, 'create') then
+ return 403
+ end
+ acf.model.set.add(obj, data)
- acf.model.set.add(obj, data)
+ elseif type(obj) == 'function' then
+ if not mnode.has_permission(parent, user, name) then
+ return 403
+ end
+ res = obj(data)
+
+ else return 405 end
else
local obj = parent[name]
@@ -200,7 +205,7 @@ return function(env)
end
txn:commit()
- return 205
+ return res == nil and 205 or 200, nil, res
end
if path == '/' then