Skip to content

Commit a82c1d0

Browse files
committed
✨ Add support for VANISHED responses
This updates the parser to handle the VANISHED response, and adds a new response data class: VanishedData. VanishedData was originally written as a plain class and then converted to Data. I'm keeping most of the tests that I wrote prior to converting it to Data, but I probably wouldn't have written many of them if I had used Data in the first place.
1 parent 849c360 commit a82c1d0

File tree

5 files changed

+189
-9
lines changed

5 files changed

+189
-9
lines changed

lib/net/imap/response_data.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class IMAP < Protocol
66
autoload :FetchData, "#{__dir__}/fetch_data"
77
autoload :SearchResult, "#{__dir__}/search_result"
88
autoload :SequenceSet, "#{__dir__}/sequence_set"
9+
autoload :VanishedData, "#{__dir__}/vanished_data"
910

1011
# Net::IMAP::ContinuationRequest represents command continuation requests.
1112
#

lib/net/imap/response_parser.rb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,6 @@ def remaining_unparsed
769769
def response_data__ignored; response_data__unhandled(IgnoredResponse) end
770770
alias response_data__noop response_data__ignored
771771

772-
alias expunged_resp response_data__unhandled
773772
alias uidfetch_resp response_data__unhandled
774773
alias listrights_data response_data__unhandled
775774
alias myrights_data response_data__unhandled
@@ -841,6 +840,20 @@ def response_data__simple_numeric
841840
alias mailbox_data__exists response_data__simple_numeric
842841
alias mailbox_data__recent response_data__simple_numeric
843842

843+
# The name for this is confusing, because it *replaces* EXPUNGE
844+
# >>>
845+
# expunged-resp = "VANISHED" [SP "(EARLIER)"] SP known-uids
846+
def expunged_resp
847+
name = label "VANISHED"; SP!
848+
earlier = if lpar? then label("EARLIER"); rpar; SP!; true else false end
849+
uids = known_uids
850+
data = VanishedData[uids, earlier]
851+
UntaggedResponse.new name, data, @str
852+
end
853+
854+
# TODO: replace with uid_set
855+
alias known_uids sequence_set
856+
844857
# RFC3501 & RFC9051:
845858
# msg-att = "(" (msg-att-dynamic / msg-att-static)
846859
# *(SP (msg-att-dynamic / msg-att-static)) ")"

lib/net/imap/vanished_data.rb

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# frozen_string_literal: true
2+
3+
module Net
4+
class IMAP < Protocol
5+
6+
# Net::IMAP::VanishedData represents the contents of a +VANISHED+ response,
7+
# which is described by the
8+
# {QRESYNC}[https://www.rfc-editor.org/rfc/rfc7162.html] extension.
9+
# [{RFC7162 §3.2.10}[https://www.rfc-editor.org/rfc/rfc7162.html#section-3.2.10]].
10+
#
11+
# +VANISHED+ responses replace +EXPUNGE+ responses when either the
12+
# {QRESYNC}[https://www.rfc-editor.org/rfc/rfc7162.html] or the
13+
# {UIDONLY}[https://www.rfc-editor.org/rfc/rfc9586.html] extension has been
14+
# enabled.
15+
class VanishedData < Data.define(:uids, :earlier)
16+
17+
# Returns a new VanishedData object.
18+
#
19+
# * +uids+ will be converted by SequenceSet.[].
20+
# * +earlier+ will be converted to +true+ or +false+
21+
def initialize(uids:, earlier:)
22+
uids = SequenceSet[uids]
23+
earlier = !!earlier
24+
super
25+
end
26+
27+
##
28+
# :attr_reader: uids
29+
#
30+
# SequenceSet of UIDs that have been permanently removed from the mailbox.
31+
32+
##
33+
# :attr_reader: earlier
34+
#
35+
# +true+ when the response was caused by Net::IMAP#uid_fetch with
36+
# <tt>vanished: true</tt> or Net::IMAP#select/Net::IMAP#examine with
37+
# <tt>qresync: true</tt>.
38+
#
39+
# +false+ when the response is used to announce message removals within an
40+
# already selected mailbox.
41+
42+
# rdoc doesn't handle attr aliases nicely. :(
43+
alias earlier? earlier # :nodoc:
44+
##
45+
# :attr_reader: earlier?
46+
#
47+
# Alias for #earlier.
48+
49+
# Returns an Array of all of the UIDs in #uids.
50+
#
51+
# See SequenceSet#numbers.
52+
def to_a; uids.numbers end
53+
54+
end
55+
end
56+
end

test/net/imap/fixtures/response_parser/rfc7162_condstore_qresync_responses.yml

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -142,18 +142,38 @@
142142
:response: "* VANISHED (EARLIER) 41,43:116,118,120:211,214:540\r\n"
143143
:expected: !ruby/struct:Net::IMAP::UntaggedResponse
144144
name: VANISHED
145-
data: !ruby/struct:Net::IMAP::UnparsedData
146-
unparsed_data: "(EARLIER) 41,43:116,118,120:211,214:540"
145+
data: !ruby/object:Net::IMAP::VanishedData
146+
uids: !ruby/object:Net::IMAP::SequenceSet
147+
string: 41,43:116,118,120:211,214:540
148+
tuples:
149+
- - 41
150+
- 41
151+
- - 43
152+
- 116
153+
- - 118
154+
- 118
155+
- - 120
156+
- 211
157+
- - 214
158+
- 540
159+
earlier: true
147160
raw_data: "* VANISHED (EARLIER) 41,43:116,118,120:211,214:540\r\n"
148-
comment: |
149-
Note that QRESYNC isn't supported yet, so the data is unparsed.
150161

151162
"RFC7162 QRESYNC 3.2.7. EXPUNGE Command":
152163
:response: "* VANISHED 405,407,410,425\r\n"
153164
:expected: !ruby/struct:Net::IMAP::UntaggedResponse
154165
name: VANISHED
155-
data: !ruby/struct:Net::IMAP::UnparsedData
156-
unparsed_data: '405,407,410,425'
166+
data: !ruby/object:Net::IMAP::VanishedData
167+
uids: !ruby/object:Net::IMAP::SequenceSet
168+
string: '405,407,410,425'
169+
tuples:
170+
- - 405
171+
- 405
172+
- - 407
173+
- 407
174+
- - 410
175+
- 410
176+
- - 425
177+
- 425
178+
earlier: false
157179
raw_data: "* VANISHED 405,407,410,425\r\n"
158-
comment: |
159-
Note that QRESYNC isn't supported yet, so the data is unparsed.

test/net/imap/test_vanished_data.rb

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# frozen_string_literal: true
2+
3+
require "net/imap"
4+
require "test/unit"
5+
6+
class VanishedDataTest < Test::Unit::TestCase
7+
VanishedData = Net::IMAP::VanishedData
8+
SequenceSet = Net::IMAP::SequenceSet
9+
DataFormatError = Net::IMAP::DataFormatError
10+
11+
test ".new(uids: string, earlier: bool)" do
12+
vanished = VanishedData.new(uids: "1,3:5,7", earlier: true)
13+
assert_equal SequenceSet["1,3:5,7"], vanished.uids
14+
assert vanished.earlier?
15+
vanished = VanishedData.new(uids: "99,111", earlier: false)
16+
assert_equal SequenceSet["99,111"], vanished.uids
17+
refute vanished.earlier?
18+
end
19+
20+
test ".new, missing args raises ArgumentError" do
21+
assert_raise ArgumentError do VanishedData.new end
22+
assert_raise ArgumentError do VanishedData.new "1234" end
23+
assert_raise ArgumentError do VanishedData.new uids: "1234" end
24+
assert_raise ArgumentError do VanishedData.new earlier: true end
25+
end
26+
27+
test ".new, nil uids raises DataFormatError" do
28+
assert_raise DataFormatError do VanishedData.new uids: nil, earlier: true end
29+
assert_raise DataFormatError do VanishedData.new nil, true end
30+
end
31+
32+
test ".[uids: string, earlier: bool]" do
33+
vanished = VanishedData[uids: "1,3:5,7", earlier: true]
34+
assert_equal SequenceSet["1,3:5,7"], vanished.uids
35+
assert vanished.earlier?
36+
vanished = VanishedData[uids: "99,111", earlier: false]
37+
assert_equal SequenceSet["99,111"], vanished.uids
38+
refute vanished.earlier?
39+
end
40+
41+
test ".[uids, earlier]" do
42+
vanished = VanishedData["1,3:5,7", true]
43+
assert_equal SequenceSet["1,3:5,7"], vanished.uids
44+
assert vanished.earlier?
45+
vanished = VanishedData["99,111", false]
46+
assert_equal SequenceSet["99,111"], vanished.uids
47+
refute vanished.earlier?
48+
end
49+
50+
test ".[], mixing args raises ArgumentError" do
51+
assert_raise ArgumentError do
52+
VanishedData[1, true, uids: "1", earlier: true]
53+
end
54+
assert_raise ArgumentError do VanishedData["1234", earlier: true] end
55+
assert_raise ArgumentError do VanishedData[nil, true, uids: "1"] end
56+
end
57+
58+
test ".[], missing args raises ArgumentError" do
59+
assert_raise ArgumentError do VanishedData[] end
60+
assert_raise ArgumentError do VanishedData["1234"] end
61+
end
62+
63+
test ".[], nil uids raises DataFormatError" do
64+
assert_raise DataFormatError do VanishedData[nil, true] end
65+
assert_raise DataFormatError do VanishedData[nil, nil] end
66+
end
67+
68+
test "#to_a delegates to uids (SequenceSet#to_a)" do
69+
assert_equal [1, 2, 3, 4], VanishedData["1:4", true].to_a
70+
end
71+
72+
test "#deconstruct_keys returns uids and earlier" do
73+
assert_equal({uids: SequenceSet[1,9], earlier: true},
74+
VanishedData["1,9", true].deconstruct_keys([:uids, :earlier]))
75+
VanishedData["1:5", false] => VanishedData[uids: SequenceSet, earlier: false]
76+
end
77+
78+
test "#==" do
79+
assert_equal VanishedData[123, false], VanishedData["123", false]
80+
assert_equal VanishedData["3:1", false], VanishedData["1:3", false]
81+
end
82+
83+
test "#eql?" do
84+
assert VanishedData["1:3", false].eql?(VanishedData[1..3, false])
85+
refute VanishedData["3:1", false].eql?(VanishedData["1:3", false])
86+
refute VanishedData["1:5", false].eql?(VanishedData["1:3", false])
87+
refute VanishedData["1:3", true].eql?(VanishedData["1:3", false])
88+
end
89+
90+
end

0 commit comments

Comments
 (0)