summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--acf/model/field.lua4
-rw-r--r--acf/model/init.lua9
-rw-r--r--acf/model/model.lua35
-rw-r--r--acf/model/node.lua40
-rw-r--r--acf/model/set.lua49
-rw-r--r--acf/modules/aaa.lua6
-rw-r--r--acf/modules/awall.lua4
-rwxr-xr-xdev-shell11
-rw-r--r--protocol.txt19
-rw-r--r--server.lua27
10 files changed, 136 insertions, 68 deletions
diff --git a/acf/model/field.lua b/acf/model/field.lua
index 7e3aa1d..436ce2c 100644
--- a/acf/model/field.lua
+++ b/acf/model/field.lua
@@ -117,12 +117,14 @@ function TreeNode:save(context, value)
local errors = err.ErrorDict()
for k, v in pairs(value) do
- errors:collect(function() new[k] = v end)
+ errors:collect(self.save_member, new, k, v)
end
errors:raise()
end
end
+function TreeNode.save_member(node, k, v) node[k] = v end
+
Model = class(TreeNode)
diff --git a/acf/model/init.lua b/acf/model/init.lua
index bf4f7b4..2800ab6 100644
--- a/acf/model/init.lua
+++ b/acf/model/init.lua
@@ -17,6 +17,7 @@ new = model.new
local to_field = model.to_field
node = require('acf.model.node')
+set = require('acf.model.set')
local object = require('acf.object')
local class = object.class
@@ -196,11 +197,9 @@ function Collection:load(context)
end
-PrimitiveList = class(Collection)
-
-function PrimitiveList:init(params)
- super(self, PrimitiveList):init(params, node.PrimitiveList)
-end
+Set = class(Collection)
+function Set:init(params) super(self, Set):init(params, set.Set) end
+function Set.save_member(node, k, v) set.add(node, v) end
-- experimental
diff --git a/acf/model/model.lua b/acf/model/model.lua
index 5fd96a7..7c27b50 100644
--- a/acf/model/model.lua
+++ b/acf/model/model.lua
@@ -71,22 +71,7 @@ function Model:init(context)
function mt.mmeta(name) return mt.field(name):meta() end
- mt.meta = {type='model',
- fields=util.map(function(f) return mt.mmeta(f) end,
- self._fields)}
-
- function mt.members()
- return util.map(function(f) return f.name end, mt.meta.fields)
- end
-
- function mt.validate()
- for _, name in ipairs(mt.members()) do
- local field = mt.field(name)
- if not field.compute then field:validate_saved() end
- end
- end
-
- function mt.__index(t, k)
+ function mt.get(k)
local f = mt.field(k)
if f then
if f.compute then return f:compute() end
@@ -95,9 +80,25 @@ function Model:init(context)
return mt.class[k]
end
- function mt.__newindex(t, k, v)
+ function mt.set(k, v)
local f = mt.field(k)
if not f then raise(mt.path, 'Field named '..k..' does not exist') end
f:save(v)
end
+
+ mt.meta = {
+ type='model',
+ fields=util.map(function(f) return mt.mmeta(f) end, self._fields)
+ }
+
+ function mt.members()
+ return util.map(function(f) return f.name end, mt.meta.fields)
+ end
+
+ function mt.validate()
+ for _, name in ipairs(mt.members()) do
+ local field = mt.field(name)
+ if not field.compute then field:validate_saved() end
+ end
+ end
end
diff --git a/acf/model/node.lua b/acf/model/node.lua
index 7a818be..cf3111d 100644
--- a/acf/model/node.lua
+++ b/acf/model/node.lua
@@ -52,6 +52,11 @@ TreeNode = class()
function TreeNode:init(context)
local mt = getmetatable(self)
update(mt, context)
+
+ function mt.set(k, v) rawset(self, k, v) end
+ function mt.__index(t, k) return mt.get(k) end
+ function mt.__newindex(t, k, v) mt.set(k, v) end
+
mt.txn.validable[mt.path] = mt.addr
end
@@ -88,28 +93,8 @@ function Collection:init(context, params)
end
end
- function mt.__index(t, k) return mt.field:load(k) end
- function mt.__newindex(t, k, v) mt.field:save(k, v) end
-end
-
-
-PrimitiveList = class(Collection)
-
-function PrimitiveList:init(context, params)
- super(self, PrimitiveList):init(context, params)
-
- local mt = getmetatable(self)
- local index = mt.__index
-
- function mt.__index(t, k)
- if type(k) == 'number' then return index(t, k) end
-
- for i, j in ipairs(mt.txn:get(mt.addr) or {}) do
- assert(i == tonumber(j))
- if mt.field:load(i) == k then return k end
- end
- raise(mt.path, 'Value does not exist: '..k)
- end
+ function mt.get(k) return mt.field:load(k) end
+ function mt.set(k, v) mt.field:save(k, v) end
end
@@ -138,3 +123,14 @@ mmeta = meta_func('mmeta')
parent = meta_func('parent')
path = meta_func('path')
validate = meta_func('validate')
+
+
+local rawpairs = pairs
+
+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
+ return rawpairs(res)
+end
diff --git a/acf/model/set.lua b/acf/model/set.lua
new file mode 100644
index 0000000..be6c2de
--- /dev/null
+++ b/acf/model/set.lua
@@ -0,0 +1,49 @@
+--[[
+Copyright (c) 2012-2013 Kaarle Ritvanen
+See LICENSE file for license details
+--]]
+
+module(..., package.seeall)
+
+local TreeNode = require('acf.model.field').TreeNode
+local npairs = require('acf.model.node').pairs
+local object = require('acf.object')
+
+
+local function find(set, value)
+ for i, member in npairs(set) do
+ if member == value then return i end
+ end
+end
+
+
+Set = object.class(require('acf.model.node').Collection)
+
+function Set:init(context, params)
+ assert(not object.isinstance(params.field, TreeNode))
+ object.super(self, 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.__newindex(t, k, v)
+ assert(v == nil)
+ local i = find(self, k)
+ if not i then return end
+
+ local len = #mt.members()
+ while i < len do
+ mt.set(i, mt.get(i + 1))
+ i = i + 1
+ end
+ mt.set(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
+end
diff --git a/acf/modules/aaa.lua b/acf/modules/aaa.lua
index 23cb6df..e98e686 100644
--- a/acf/modules/aaa.lua
+++ b/acf/modules/aaa.lua
@@ -8,18 +8,18 @@ module(..., package.seeall)
local M = require('acf.model')
Role = M.new()
-Role.permissions = M.Collection{type=M.Reference{scope='../../../permissions'}}
+Role.permissions = M.Set{type=M.Reference{scope='../../../permissions'}}
User = M.new()
User.password = M.String
User.real_name = M.String
-User.roles = M.Collection{type=M.Reference{scope='../../../roles'}}
+User.roles = M.Set{type=M.Reference{scope='../../../roles'}}
function User:check_password(password) return password == self.password end
Authentication = M.new()
Authentication.users = M.Collection{type=User}
Authentication.roles = M.Collection{type=Role}
-Authentication.permissions = M.PrimitiveList{type=M.String}
+Authentication.permissions = M.Set{type=M.String}
M.register('auth',
'/json'..require('lfs').currentdir()..'/config/aaa.json',
diff --git a/acf/modules/awall.lua b/acf/modules/awall.lua
index dbb335a..2920bdb 100644
--- a/acf/modules/awall.lua
+++ b/acf/modules/awall.lua
@@ -55,8 +55,8 @@ Service['icmp-type'] = M.String
-- TODO fw zone
Zone = M.new()
-Zone.iface = M.PrimitiveList{type=M.String}
-Zone.addr = M.PrimitiveList{type=M.String}
+Zone.iface = M.Set{type=M.String}
+Zone.addr = M.Set{type=M.String}
Zone['route-back'] = M.Boolean{default=false}
LogClass = M.new()
diff --git a/dev-shell b/dev-shell
index 397c7af..9e64500 100755
--- a/dev-shell
+++ b/dev-shell
@@ -45,6 +45,7 @@ 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>
Start transaction: start
@@ -65,12 +66,20 @@ EOF
_acf_req "$path" "$@"
}
+ function _acf_post_req {
+ _acf_obj_req "$2" -d "$3" -X $1
+ }
+
function get {
_acf_obj_req "$1"
}
function put {
- _acf_obj_req "$1" -d "$2" -X PUT
+ _acf_post_req PUT "$@"
+ }
+
+ function post {
+ _acf_post_req POST "$@"
}
function delete {
diff --git a/protocol.txt b/protocol.txt
index ffc50bf..a0ba4d4 100644
--- a/protocol.txt
+++ b/protocol.txt
@@ -36,16 +36,16 @@ resp: JSON object, with the following attributes:
data: JSON serialization of object
- primitive types as JSON primitives
- references as path names (relative to scope)
- - models and collections as JSON objects or arrays with
- members as attributes:
- - primitive members as JSON primitives
- - reference, model, and collection members as path names
+ - models, collections, and sets as JSON objects or arrays
+ with members as attributes:
+ - primitive members as JSON primitives
+ - reference, model, and collection members as path names
meta: JSON object, with the following attributes
- name (last component of path name)
- ui-name (shown to user)
- description (optional help text)
- - type (e.g. model, collection, reference, string, number,
- boolean)
+ - type (e.g. model, collection, set, reference, string,
+ number, boolean)
- widget (name of client-side JS module used to display
the data)
- required (boolean)
@@ -70,6 +70,13 @@ req: PUT /config/<obj_path>
Delete object:
req: DELETE /config/<obj_path>
+Add member to a set:
+req: POST /config/<set_path>
+ - body shall contain a JSON primitive
+
+Delete set member:
+req: DELETE /config/<set_path>/<member>
+
Invoke object-specific action (not yet supported by server):
req: POST /config/<obj_path>/<action>
- arguments passed as a JSON array in body; serialization as in
diff --git a/server.lua b/server.lua
index 0bd5553..43a6a09 100644
--- a/server.lua
+++ b/server.lua
@@ -120,21 +120,18 @@ return function(env)
if method == 'GET' then
local obj = txn:search(path)
- if obj == nil then return 404 end
-
local res
if isinstance(obj, acf.model.node.TreeNode) then
local node = {}
- for _, k in ipairs(acf.model.node.members(obj)) do
- local v = obj[k]
- if isinstance(v, acf.model.node.TreeNode) then
- v = acf.model.node.path(v)
- end
- node[k] = v
+ for k, v in acf.model.node.pairs(obj) do
+ node[k] = isinstance(
+ v,
+ acf.model.node.TreeNode
+ ) and acf.model.node.path(v) or v
end
res = {data=node, meta=acf.model.node.meta(obj)}
-
+
else
res = {
data=obj,
@@ -145,8 +142,16 @@ return function(env)
return 200, nil, res
end
- -- TODO implement POST for invoking object-specific actions
- if method == 'DELETE' then parent[name] = nil
+ 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
+
+ acf.model.set.add(obj, data)
+
+ elseif method == 'DELETE' then parent[name] = nil
elseif method == 'PUT' then parent[name] = data
else return 405 end