summaryrefslogtreecommitdiffstats
path: root/acf
diff options
context:
space:
mode:
authorKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2013-03-07 07:14:03 +0000
committerKaarle Ritvanen <kaarle.ritvanen@datakunkku.fi>2013-03-07 12:42:24 +0000
commitf272cb4c51cb2bb3752269faf431bcf4bfbc0686 (patch)
tree4183714fffacb78cd451ebd051d7321a368c128b /acf
parent7df3e9fa87497bc65b454dca79f6f4fc133fd24b (diff)
downloadaconf-f272cb4c51cb2bb3752269faf431bcf4bfbc0686.tar.bz2
aconf-f272cb4c51cb2bb3752269faf431bcf4bfbc0686.tar.xz
forward relevant error messages to client
Diffstat (limited to 'acf')
-rw-r--r--acf/error.lua97
-rw-r--r--acf/init.lua3
-rw-r--r--acf/model/field.lua26
-rw-r--r--acf/model/init.lua26
-rw-r--r--acf/model/model.lua6
-rw-r--r--acf/model/node.lua13
-rw-r--r--acf/modules/awall.lua4
-rw-r--r--acf/persistence/init.lua5
-rw-r--r--acf/transaction/backend.lua11
-rw-r--r--acf/transaction/init.lua7
-rw-r--r--acf/util.lua7
11 files changed, 171 insertions, 34 deletions
diff --git a/acf/error.lua b/acf/error.lua
new file mode 100644
index 0000000..26dfb86
--- /dev/null
+++ b/acf/error.lua
@@ -0,0 +1,97 @@
+--[[
+Copyright (c) 2012-2013 Kaarle Ritvanen
+See LICENSE file for license details
+--]]
+
+module(..., package.seeall)
+
+local object = require('acf.object')
+local class = object.class
+
+local util = require('acf.util')
+
+require 'json'
+
+
+local function pack(...) return arg end
+
+
+local ErrorTable = class()
+
+function ErrorTable:init() self.errors = {} end
+
+function ErrorTable:success() return not next(self.errors) end
+
+function ErrorTable:raise()
+ if not self:success() then error(json.encode(self.errors)) end
+end
+
+
+ErrorList = class(ErrorTable)
+
+function ErrorList:init(label)
+ object.super(self, ErrorList):init()
+ self.label = label
+end
+
+function ErrorList:insert(msg)
+ table.insert(util.setdefault(self.errors, self.label, {}), msg)
+end
+
+
+ErrorDict = class(ErrorTable)
+
+function ErrorDict:collect(func, ...)
+ local function pack(success, ...)
+ return success, success and arg or arg[1]
+ end
+
+ local success, res = pack(
+ xpcall(
+ function() return func(unpack(arg)) end,
+ function(err)
+ local _, _, data = string.find(err, '.-: (.+)')
+ local success, res = pcall(json.decode, data)
+ if success and type(res) == 'table' then return res end
+ return data..'\n'..debug.traceback()
+ end
+ )
+ )
+
+ if success then return unpack(res) end
+
+ if type(res) == 'table' then
+ for label, errors in pairs(res) do
+ for _, err in ipairs(errors) do
+ table.insert(util.setdefault(self.errors, label, {}), err)
+ end
+ end
+ else error(res) end
+end
+
+
+function raise(label, msg)
+ local err = ErrorList(label)
+ err:insert(msg)
+ err:raise()
+end
+
+function relabel(label, ...)
+ local err = ErrorDict()
+ local res = pack(err:collect(unpack(arg)))
+ if err:success() then return unpack(res) end
+
+ elist = ErrorList(label)
+ for lbl, el in pairs(err.errors) do
+ for _, e in ipairs(el) do elist:insert(lbl..': '..e) end
+ end
+ elist:raise()
+end
+
+function call(...)
+ local err = ErrorDict()
+ local res = pack(err:collect(unpack(arg)))
+ if err:success() then return true, unpack(res) end
+ if err.errors.system then error(err.errors.system[1]) end
+ return false, err.errors
+end
diff --git a/acf/init.lua b/acf/init.lua
index 3358571..8f89c08 100644
--- a/acf/init.lua
+++ b/acf/init.lua
@@ -1,5 +1,5 @@
--[[
-Copyright (c) 2012 Kaarle Ritvanen
+Copyright (c) 2012-2013 Kaarle Ritvanen
See LICENSE file for license details
--]]
@@ -8,6 +8,7 @@ module(..., package.seeall)
require('acf.util').loadmods('modules')
require 'acf.model'
+call = require('acf.error').call
require 'acf.object'
require 'acf.path'
require 'acf.transaction'
diff --git a/acf/model/field.lua b/acf/model/field.lua
index d2e62a5..e1bdeaf 100644
--- a/acf/model/field.lua
+++ b/acf/model/field.lua
@@ -1,10 +1,13 @@
--[[
-Copyright (c) 2012 Kaarle Ritvanen
+Copyright (c) 2012-2013 Kaarle Ritvanen
See LICENSE file for license details
--]]
module(..., package.seeall)
+local err = require('acf.error')
+local raise = err.raise
+
local node = require('acf.model.node')
local object = require('acf.object')
@@ -63,10 +66,10 @@ end
function Field:_validate(txn, path, value)
if self.required and value == nil then
- error('Required value not set: '..path)
+ raise(path, 'Required value not set')
end
if self.choice and value ~= nil and not contains(self.choice, value) then
- error('Invalid value for '..path..': '..value)
+ raise(path, 'Invalid value')
end
if value ~= nil then self:validate(txn, path, value) end
return value
@@ -92,16 +95,27 @@ function TreeNode:save(txn, path, addr, value)
if object.isinstance(value, node.TreeNode) then
-- TODO clone if TreeNode has wrong path
- assert(node.path(value) == path)
+ if node.path(value) ~= path then
+ raise(path, 'Attempted to assign foreign object as value')
+ end
return
end
txn:set(addr)
+
if value then
- assert(type(value) == 'table')
+ if type(value) ~= 'table' then
+ raise(path, 'Cannot assign primitive value')
+ end
+
txn:set(addr, 'table')
local new = self:load(txn, path, addr, true)
- for k, v in pairs(value) do new[k] = v end
+
+ local errors = err.ErrorDict()
+ for k, v in pairs(value) do
+ errors:collect(function() new[k] = v end)
+ end
+ errors:raise()
end
end
diff --git a/acf/model/init.lua b/acf/model/init.lua
index f333886..44b1f5e 100644
--- a/acf/model/init.lua
+++ b/acf/model/init.lua
@@ -5,6 +5,10 @@ See LICENSE file for license details
module(..., package.seeall)
+error = require('acf.error')
+local raise = error.raise
+local relabel = error.relabel
+
local fld = require('acf.model.field')
local Field = fld.Field
@@ -34,7 +38,7 @@ local Primitive = class(Field)
function Primitive:validate(txn, path, value)
local t = self.dtype
- if type(value) ~= t then error('Not a '..t..': '..tostring(value)) end
+ if type(value) ~= t then raise(path, 'Not a '..t) end
end
@@ -48,7 +52,7 @@ end
function String:validate(txn, path, value)
super(self, String):validate(txn, path, value)
if self['max-length'] and string.len(value) > self['max-length'] then
- error('Maximum length exceeded: '..value)
+ raise(path, 'Maximum length exceeded')
end
end
@@ -79,7 +83,7 @@ Integer = class(Number)
function Integer:validate(txn, path, value)
super(self, Integer):validate(txn, path, value)
- if math.floor(value) ~= value then error('Not an integer: '..value) end
+ if math.floor(value) ~= value then raise(path, 'Not an integer') end
end
@@ -101,7 +105,7 @@ end
function Range:validate(txn, path, value)
local comps = stringy.split(value, '-')
- if #comps > 2 then error('Invalid range') end
+ if #comps > 2 then raise(path, 'Invalid range') end
for _, v in ipairs(comps) do
to_field(self.type):_validate(txn, path, v)
end
@@ -124,8 +128,9 @@ function Reference:meta(txn, path, addr)
local res = super(self, Reference):meta(txn, path, addr)
res.scope = self:abs_scope(path)
- local base = txn:search(res.scope)
- local objs = base and txn:get(getmetatable(base).addr) or {}
+ local objs = txn:get(
+ getmetatable(relabel('system', txn.search, txn, res.scope)).addr
+ ) or {}
res.choice = map(function(p) return pth.join(res.scope, p) end, objs)
res['ui-choice'] = objs
@@ -153,17 +158,18 @@ function Reference:_validate(txn, path, value)
local scope = self:abs_scope(path)
local prefix = scope..'/'
if not stringy.startswith(value, prefix) then
- error('Reference out of scope ('..scope..')')
+ raise(path, 'Reference out of scope ('..scope..')')
end
value = string.sub(value, string.len(prefix) + 1, -1)
end
-- assume one-level ref for now
- assert(not string.find(value, '/'))
- if not self:follow(txn, path, value) then
- error('Does not exist: '..value)
+ if string.find(value, '/') then
+ raise(path, 'Subtree references not yet supported')
end
+
-- TODO check instance type
+ relabel(path, self.follow, self, txn, path, value)
return value
end
diff --git a/acf/model/model.lua b/acf/model/model.lua
index 2efcb6f..6186d04 100644
--- a/acf/model/model.lua
+++ b/acf/model/model.lua
@@ -1,10 +1,12 @@
--[[
-Copyright (c) 2012 Kaarle Ritvanen
+Copyright (c) 2012-2013 Kaarle Ritvanen
See LICENSE file for license details
--]]
module(..., package.seeall)
+local raise = require('acf.error').raise
+
local fld = require('acf.model.field')
local Field = fld.Field
@@ -88,7 +90,7 @@ function Model:init(txn, path, addr)
function mt.__newindex(t, k, v)
local f = mt.field(k)
- if not f then error('Field named '..k..' does not exist') end
+ if not f then raise(mt.path, 'Field named '..k..' does not exist') end
f:save(v)
txn.validate[mt.path] = function() self:validate() end
end
diff --git a/acf/model/node.lua b/acf/model/node.lua
index 083757c..3459fce 100644
--- a/acf/model/node.lua
+++ b/acf/model/node.lua
@@ -5,6 +5,7 @@ See LICENSE file for license details
module(..., package.seeall)
+local raise = require('acf.error').raise
local object = require('acf.object')
local class = object.class
local super = object.super
@@ -51,9 +52,13 @@ end
function TreeNode:search(path)
if #path == 0 then return self end
- local next = path[1]
+ local name = path[1]
+ local next = self[name]
+ if next == nil then
+ raise(getmetatable(self).path, 'Subordinate does not exist: '..name)
+ end
table.remove(path, 1)
- return TreeNode.search(self[next], path)
+ return TreeNode.search(next, path)
end
@@ -65,7 +70,7 @@ function Collection:init(txn, path, addr, field, required)
if required then
txn.validate[path] = function()
if #txn:get(addr) == 0 then
- error('Collection cannot be empty: '..path)
+ raise(path, 'Collection cannot be empty')
end
end
end
@@ -100,7 +105,7 @@ function PrimitiveList:init(txn, path, addr, field, required)
assert(i == tonumber(j))
if mt.field:load(i) == k then return k end
end
- error('Value does not exist: '..k)
+ raise(path, 'Value does not exist: '..k)
end
end
diff --git a/acf/modules/awall.lua b/acf/modules/awall.lua
index 0b73f9c..dbb335a 100644
--- a/acf/modules/awall.lua
+++ b/acf/modules/awall.lua
@@ -21,14 +21,14 @@ function IPv4Addr:validate(txn, path, value)
end
end
if test(string.match(value, '(%d+)%.(%d+)%.(%d+)%.(%d+)')) then
- error('Invalid IP address: '..value)
+ M.error.raise(path, 'Invalid IP address')
end
end
Port = class(M.Integer)
function Port:validate(txn, path, value)
super(self, Port):validate(txn, path, value)
- if value < 0 or value > 65535 then error('Invalid port: '..value) end
+ if value < 0 or value > 65535 then M.error.raise(path, 'Invalid port') end
end
PortRange = class(M.Range)
diff --git a/acf/persistence/init.lua b/acf/persistence/init.lua
index f674e9c..2533560 100644
--- a/acf/persistence/init.lua
+++ b/acf/persistence/init.lua
@@ -1,5 +1,5 @@
--[[
-Copyright (c) 2012 Kaarle Ritvanen
+Copyright (c) 2012-2013 Kaarle Ritvanen
See LICENSE file for license details
--]]
@@ -39,8 +39,7 @@ function DataStore:set_multiple(mods)
for _, mod in ipairs(mods) do
local path, t, value = unpack(mod)
local backend, comps = self:split_path(path)
- if not bms[backend] then bms[backend] = {} end
- table.insert(bms[backend], {comps, t, value})
+ table.insert(util.setdefault(bms, backend, {}), {comps, t, value})
end
for backend, bm in pairs(bms) do backend:set(bm) end
diff --git a/acf/transaction/backend.lua b/acf/transaction/backend.lua
index 5b8c53f..e907c6f 100644
--- a/acf/transaction/backend.lua
+++ b/acf/transaction/backend.lua
@@ -1,10 +1,12 @@
--[[
-Copyright (c) 2012 Kaarle Ritvanen
+Copyright (c) 2012-2013 Kaarle Ritvanen
See LICENSE file for license details
--]]
module(..., package.seeall)
+local err = require('acf.error')
+
-- TODO each transaction backend (i.e. persistence manager or
-- transaction proper) should be implemented as a thread or have its
-- internal state stored in shared storage (with appropriate locking)
@@ -23,7 +25,7 @@ function TransactionBackend:init() self.mod_time = {} end
function TransactionBackend:get_if_older(path, timestamp)
local value, ts = self:get(path)
- if ts > timestamp then error('Concurrent modification: '..path) end
+ if ts > timestamp then err.raise('conflict', path) end
return value, ts
end
@@ -41,8 +43,11 @@ end
-- TODO should be atomic, mutex with set_multiple
function TransactionBackend:comp_and_setm(accessed, mods)
+ local errors = err.ErrorDict()
for path, timestamp in pairs(accessed) do
- self:get_if_older(path, timestamp)
+ errors:collect(self.get_if_older, self, path, timestamp)
end
+ errors:raise()
+
self:set_multiple(mods)
end
diff --git a/acf/transaction/init.lua b/acf/transaction/init.lua
index 7f2f6b9..99b8b9d 100644
--- a/acf/transaction/init.lua
+++ b/acf/transaction/init.lua
@@ -1,10 +1,11 @@
--[[
-Copyright (c) 2012 Kaarle Ritvanen
+Copyright (c) 2012-2013 Kaarle Ritvanen
See LICENSE file for license details
--]]
module(..., package.seeall)
+local ErrorDict = require('acf.error').ErrorDict
local RootModel = require('acf.model').RootModel
local object = require('acf.object')
local super = object.super
@@ -121,7 +122,9 @@ function Transaction:search(path) return self.root:search(pth.split(path)) end
function Transaction:commit()
self:check()
- for _, func in pairs(self.validate) do func() end
+ local errors = ErrorDict()
+ for path, func in pairs(self.validate) do errors:collect(func) end
+ errors:raise()
local mods = {}
local handled = {}
diff --git a/acf/util.lua b/acf/util.lua
index cbea072..9788c81 100644
--- a/acf/util.lua
+++ b/acf/util.lua
@@ -1,10 +1,15 @@
--[[
-Copyright (c) 2012 Kaarle Ritvanen
+Copyright (c) 2012-2013 Kaarle Ritvanen
See LICENSE file for license details
--]]
module(..., package.seeall)
+function setdefault(t, k, v)
+ if t[k] == nil then t[k] = v end
+ return t[k]
+end
+
function setdefaults(dst, src)
for k, v in pairs(src) do if dst[k] == nil then dst[k] = v end end
return dst