summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--acf/model/model.lua17
-rw-r--r--acf/model/node.lua19
-rw-r--r--acf/transaction/init.lua33
-rw-r--r--acf/util.lua8
-rwxr-xr-xdev-shell14
-rw-r--r--protocol.txt4
-rw-r--r--server.lua24
7 files changed, 69 insertions, 50 deletions
diff --git a/acf/model/model.lua b/acf/model/model.lua
index 6a9a843..aa27312 100644
--- a/acf/model/model.lua
+++ b/acf/model/model.lua
@@ -79,6 +79,13 @@ function Model:init(txn, path, addr)
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)
local f = mt.field(k)
if f then
@@ -93,14 +100,4 @@ function Model:init(txn, path, addr)
if not f then raise(mt.path, 'Field named '..k..' does not exist') end
f:save(v)
end
-
- txn.validate[mt.path] = function() self:validate() end
-end
-
-function Model:validate()
- local mt = getmetatable(self)
- for _, name in ipairs(mt.members()) do
- local field = mt.field(name)
- if not field.compute then field:validate_saved() end
- end
end
diff --git a/acf/model/node.lua b/acf/model/node.lua
index 3459fce..f893d6a 100644
--- a/acf/model/node.lua
+++ b/acf/model/node.lua
@@ -48,6 +48,8 @@ function TreeNode:init(txn, path, addr)
mt.txn = txn
mt.path = path
mt.addr = addr
+
+ txn.validable[path] = true
end
function TreeNode:search(path)
@@ -67,14 +69,6 @@ Collection = class(TreeNode)
function Collection:init(txn, path, addr, field, required)
super(self, Collection):init(txn, path, addr)
- if required then
- txn.validate[path] = function()
- if #txn:get(addr) == 0 then
- raise(path, 'Collection cannot be empty')
- end
- end
- end
-
self.init = nil
self.search = nil
@@ -84,6 +78,14 @@ function Collection:init(txn, path, addr, field, required)
mt.meta = {type='collection', members=mt.field:meta('$')}
function mt.mmeta(name) return mt.meta.members end
function mt.members() return txn:get(addr) or {} end
+
+ function mt.validate()
+ if required then
+ if #txn:get(addr) == 0 then
+ raise(path, 'Collection cannot be empty')
+ end
+ 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
@@ -132,3 +134,4 @@ members = meta_func('members')
meta = meta_func('meta')
mmeta = meta_func('mmeta')
path = meta_func('path')
+validate = meta_func('validate')
diff --git a/acf/transaction/init.lua b/acf/transaction/init.lua
index eb92c34..e9b8bbc 100644
--- a/acf/transaction/init.lua
+++ b/acf/transaction/init.lua
@@ -11,7 +11,9 @@ local object = require('acf.object')
local super = object.super
local pth = require('acf.path')
local be_mod = require('acf.transaction.backend')
-local copy = require('acf.util').copy
+
+local util = require('acf.util')
+local copy = util.copy
local function remove_list_value(list, value)
@@ -30,7 +32,7 @@ end
local Transaction = object.class(be_mod.TransactionBackend)
-function Transaction:init(backend)
+function Transaction:init(backend, validate)
super(self, Transaction):init()
self.backend = backend
@@ -42,7 +44,8 @@ function Transaction:init(backend)
self.modified = {}
self.deleted = {}
- self.validate = {}
+ self.validate = validate
+ self.validable = {}
self.root = RootModel(self)
end
@@ -122,11 +125,15 @@ function Transaction:search(path) return self.root:search(pth.split(path)) end
function Transaction:commit()
self:check()
- local errors = ErrorDict()
- for path, func in pairs(self.validate) do
- if not self.deleted[path] then errors:collect(func) end
+ if self.validate then
+ local errors = ErrorDict()
+ for path, _ in pairs(copy(self.validable)) do
+ if not self.deleted[path] then
+ errors:collect(getmetatable(self:search(path)).validate)
+ end
+ end
+ errors:raise()
end
- errors:raise()
local mods = {}
local handled = {}
@@ -174,8 +181,18 @@ function Transaction:commit()
for path, _ in pairs(self.added) do insert_add(path) end
self.backend:comp_and_setm(self.access_time, mods)
+
+
+ if not self.validate then
+ util.update(self.backend.validable, self.validable)
+ end
+
self.backend = nil
end
+
local store = require('acf.persistence').DataStore()
-function start(txn) return Transaction(txn or store) end
+
+function start(txn, defer_validation)
+ return Transaction(txn or store, not (txn and defer_validation))
+end
diff --git a/acf/util.lua b/acf/util.lua
index 9788c81..9ca287c 100644
--- a/acf/util.lua
+++ b/acf/util.lua
@@ -10,11 +10,15 @@ function setdefault(t, k, v)
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
+function update(dst, src, preserve)
+ for k, v in pairs(src) do
+ if not preserve or dst[k] == nil then dst[k] = v end
+ end
return dst
end
+function setdefaults(dst, src) return update(dst, src, true) end
+
function copy(var)
return type(var) == 'table' and setdefaults({}, var) or var
end
diff --git a/dev-shell b/dev-shell
index aeaedc9..397c7af 100755
--- a/dev-shell
+++ b/dev-shell
@@ -21,6 +21,7 @@ function _acf_start_req {
local var=ACF_$3
shift 3
+ local current=${!var}
eval "export $var=\$(_acf_req \"\$url\" \"\$@\" \
-D /proc/self/fd/1 -o /proc/self/fd/3 | \
sed 's/^$hdr: \\(.\\+\\)'\$'\\r''\$/\\1/;ta;d;:a;q')"
@@ -28,7 +29,7 @@ function _acf_start_req {
bash --rcfile "$ACF_QD_CLI" && _acf_req $url -X DELETE
- eval $var=
+ eval $var=$current
}
if [ "$ACF_AUTH_TOKEN" ]; then
@@ -55,12 +56,7 @@ EOF
PS1="$ACF_USER@acf2-dev-shell${ACF_TXN_ID:+($ACF_TXN_ID)}> "
function start {
- if [ "$ACF_TXN_ID" ]; then
- echo "Nested transactions not yet supported" >&2
- return 1
- else
- _acf_start_req / Transaction-ID TXN_ID -X POST
- fi
+ _acf_start_req / Transaction-ID TXN_ID -X POST
}
function _acf_obj_req {
@@ -83,7 +79,7 @@ EOF
function commit {
if [ "$ACF_TXN_ID" ]; then
- if _acf_req / -X POST; then
+ if _acf_req / -X PUT; then
echo Committed >&2
exit 1
fi
@@ -145,6 +141,8 @@ EOF
echo >&2
fi
+ ACF_TXN_ID=
+
_acf_start_req /login Auth-Token AUTH_TOKEN \
-d "{\"username\": \"$ACF_USER\", \"password\": \"$PASSWORD\"}"
fi
diff --git a/protocol.txt b/protocol.txt
index 0d699f3..c869224 100644
--- a/protocol.txt
+++ b/protocol.txt
@@ -19,9 +19,11 @@ req: POST /
resp: txn ID (in header as X-ACF-Transaction-ID)
- use X-ACF-Transaction-ID in the header of any subsequent
request to process it in the transaction's context
+ - nested transactions can be started by using it in a subsequent
+ start transaction request
Commit transaction:
-req: POST /
+req: PUT /
X-ACF-Transaction-ID: <txn_id>
Abort transaction:
diff --git a/server.lua b/server.lua
index cbbc82f..592e1e1 100644
--- a/server.lua
+++ b/server.lua
@@ -64,13 +64,14 @@ return function(env)
txn_id = tonumber(env.HTTP_X_ACF_TRANSACTION_ID)
end
- local txn
+ local parent_txn
if txn_id then
- txn = txns[txn_id]
- if not txn then
+ parent_txn = txns[txn_id]
+ if not parent_txn then
return wrap(400, nil, 'Invalid transaction ID')
end
- else txn = acf.transaction.start() end
+ end
+ local txn = acf.transaction.start(parent_txn, true)
local function fetch_user(name)
user = name and txn:search('/auth/users')[name]
@@ -150,7 +151,7 @@ return function(env)
elseif method == 'PUT' then parent[name] = data
else return 405 end
- if not txn_id then txn:commit() end
+ txn:commit()
return 205
end
@@ -159,21 +160,18 @@ return function(env)
return 301, {['Location']='/browser/'}
end
- if not ({DELETE=true, POST=true})[method] then
- return 405
- end
-
- if txn_id then
- if method == 'POST' then txn:commit() end
+ if ({DELETE=true, PUT=true})[method] then
+ if not txn_id then return 405 end
+ if method == 'PUT' then parent_txn:commit() end
txns[txn_id] = nil
return 204
end
- if method == 'DELETE' then return 405 end
+ if method ~= 'POST' then return 405 end
last_txn_id = last_txn_id + 1
local txn_id = last_txn_id
- txns[txn_id] = txn
+ txns[txn_id] = acf.transaction.start(parent_txn)
return 204, {['X-ACF-Transaction-ID']=txn_id}
end