The typed async-imap API cannot express the four responses that decide whether
a sync saw everything or whether a mutation actually happened. ENABLE does not
exist, and RFC 7162 3.2.3 makes a server answer BAD to a QRESYNC SELECT without
it. select()'s parser has no arm for VANISHED, so a deleted message is simply
never learned about. And run_command_and_check_ok discards the tagged response
code, which is exactly where RFC 4315 puts COPYUID and RFC 7162 puts MODIFIED.
Hand-rolled rather than forked. run_command and read_response are public and
imap-proto already parses Vanished and CopyUid, so what was missing is a
collector that reads to the tagged response and keeps what arrived on the way.
That is a hundred lines against maintaining a fork for the same result.
Two subtleties the collector exists to get right. COPYUID arrives in an
UNTAGGED OK for MOVE (RFC 6851 4.3 advises exactly that), so a collector
reading only the tagged response misses it on every conforming server and then
has no proof the move happened. And UID EXPUNGE answers with VANISHED rather
than EXPUNGE once QRESYNC is enabled (4.4), so counting EXPUNGE alone reports
failure on every successful delete; both are gathered.
MODIFIED is the one real gap. imap-proto 0.16.7 has no ResponseCode variant for
it, and ResponseData's raw bytes are behind a private module, so it cannot be
read from the parse tree at all. Its resp_text is opt(resp_text_code) followed
by free text, so an unrecognised code is not an error -- the whole
"[MODIFIED 7,9]" survives in the information string, and that is where it is
recovered from. Parsing a response code out of a human-readable field is not
how this should work forever; upstreaming the variant is the real fix. It is
deterministic, it is pinned by a test against a server emitting the real
syntax, and the alternative is no conflict detection at all.
Absent COPYUID is deliberately not treated as failure. A UID set matching
nothing succeeds having done nothing (RFC 3501 6.4.8), and a conforming server
may also omit the code for a UIDNOTSTICKY destination, so absence means unknown.
Also fixes the mock server reading the first parenthesised group of a STORE as
its flag list, which made a conditional store apply UNCHANGEDSINCE and the
mod-sequence as flag names. The flag list is always the final argument.
Refs #388