diff options
-rw-r--r-- | acf/model/field.lua | 40 | ||||
-rw-r--r-- | acf/model/init.lua | 1 | ||||
-rw-r--r-- | acf/model/model.lua | 123 | ||||
-rw-r--r-- | acf/model/node.lua | 19 | ||||
-rw-r--r-- | acf/model/root.lua | 2 | ||||
-rw-r--r-- | acf/persistence/backends/null.lua | 10 | ||||
-rwxr-xr-x | dev-shell | 12 | ||||
-rw-r--r-- | server.lua | 27 |
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 @@ -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"]}' @@ -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 |