1 /++
2 + Numerics for METADATA specification (post-3.2 WIP)
3 +/
4 module virc.numerics.metadata;
5 import virc.common : User;
6 import virc.numerics.definitions;
7 
8 struct RPL_WhoisKeyValue {
9 	import virc.target : Target;
10 	Target target;
11 	string key;
12 	string visibility;
13 	string value;
14 }
15 /++
16 +
17 + Format is `760 <Client> <Target> <Key> <Visibility> :<Value>`
18 +/
19 auto parseNumeric(Numeric numeric : Numeric.RPL_WHOISKEYVALUE, T)(T input, string prefixes, string channelTypes) {
20 	import std.typecons : Nullable;
21 	import virc.numerics.magicparser : autoParse;
22 	import virc.target : Target;
23 	struct Reduced {
24 		User me;
25 		string target;
26 		string key;
27 		string visibility;
28 		string value;
29 	}
30 	Nullable!RPL_WhoisKeyValue output;
31 	auto reply = autoParse!Reduced(input);
32 	if (!reply.isNull) {
33 		output = RPL_WhoisKeyValue(Target(reply.get.target, prefixes, channelTypes), reply.get.key, reply.get.visibility, reply.get.value);
34 	}
35 	return output;
36 }
37 ///
38 @safe pure nothrow unittest { //Numeric.RPL_WHOISKEYVALUE
39 	import virc.common : User;
40 	import std.range : only, takeNone;
41 	{
42 		with(parseNumeric!(Numeric.RPL_WHOISKEYVALUE)(only("client", "someone!test@example.com", "url", "*", "http://www.example.com"), "@", "#").get) {
43 			assert(target.user == User("someone!test@example.com"));
44 			assert(key == "url");
45 			assert(visibility == "*");
46 			assert(value == "http://www.example.com");
47 		}
48 	}
49 	{
50 		immutable logon = parseNumeric!(Numeric.RPL_WHOISKEYVALUE)(takeNone(only("")), "@", "#");
51 		assert(logon.isNull);
52 	}
53 	{
54 		immutable logon = parseNumeric!(Numeric.RPL_WHOISKEYVALUE)(only("*"), "@", "#");
55 		assert(logon.isNull);
56 	}
57 	{
58 		immutable logon = parseNumeric!(Numeric.RPL_WHOISKEYVALUE)(only("*", "url"), "@", "#");
59 		assert(logon.isNull);
60 	}
61 	{
62 		immutable logon = parseNumeric!(Numeric.RPL_WHOISKEYVALUE)(only("*", "url", "*"), "@", "#");
63 		assert(logon.isNull);
64 	}
65 }
66 
67 struct RPL_KeyValue {
68 	import virc.numerics.magicparser : Optional;
69 	import virc.target : Target;
70 	import std.typecons : Nullable;
71 	Target target;
72 	string key;
73 	string visibility;
74 	@Optional Nullable!string value;
75 }
76 /++
77 +
78 + Format is `761 <Client> <Target> <Key> <Visibility>[ :<Value>]`
79 +/
80 auto parseNumeric(Numeric numeric : Numeric.RPL_KEYVALUE, T)(T input, string prefixes, string channelTypes) {
81 	import std.typecons : Nullable;
82 	import virc.numerics.magicparser : autoParse, Optional;
83 	import virc.target : Target;
84 	struct Reduced {
85 		User me;
86 		string target;
87 		string key;
88 		string visibility;
89 		@Optional Nullable!string value;
90 	}
91 	Nullable!RPL_KeyValue output;
92 	auto reply = autoParse!Reduced(input);
93 	if (!reply.isNull) {
94 		output = RPL_KeyValue(Target(reply.get.target, prefixes, channelTypes), reply.get.key, reply.get.visibility, reply.get.value);
95 	}
96 	return output;
97 }
98 ///
99 @safe pure nothrow unittest { //Numeric.RPL_KEYVALUE
100 	import std.range : only, takeNone;
101 	import virc.common : User;
102 
103 	with(parseNumeric!(Numeric.RPL_KEYVALUE)(only("client", "someone!test@example.com", "url", "*", "http://www.example.com"), "@", "#").get) {
104 		assert(target== User("someone!test@example.com"));
105 		assert(key == "url");
106 		assert(visibility == "*");
107 		assert(value == "http://www.example.com");
108 	}
109 
110 	assert(parseNumeric!(Numeric.RPL_KEYVALUE)(takeNone(only("")), "@", "#").isNull);
111 	assert(parseNumeric!(Numeric.RPL_KEYVALUE)(only("*"), "@", "#").isNull);
112 	assert(parseNumeric!(Numeric.RPL_KEYVALUE)(only("*", "url"), "@", "#").isNull);
113 
114 	with(parseNumeric!(Numeric.RPL_KEYVALUE)(only("client", "*", "url", "*"), "@", "#").get) {
115 		assert(target == "*");
116 		assert(key == "url");
117 		assert(visibility == "*");
118 		assert(value.isNull);
119 	}
120 }
121 
122 struct RPL_KeyNotSet {
123 	import virc.target : Target;
124 	Target target;
125 	string key;
126 	string humanReadable;
127 }
128 /++
129 +
130 + Format is `766 <Client> <Target> <Key> :no matching key`
131 +/
132 auto parseNumeric(Numeric numeric : Numeric.RPL_KEYNOTSET, T)(T input, string prefixes, string channelTypes) {
133 	import std.typecons : Nullable;
134 	import virc.numerics.magicparser : autoParse;
135 	import virc.target : Target;
136 	struct Reduced {
137 		User me;
138 		string target;
139 		string key;
140 		string errorMessage;
141 	}
142 	Nullable!RPL_KeyNotSet output;
143 	auto reply = autoParse!Reduced(input);
144 	if (!reply.isNull) {
145 		output = RPL_KeyNotSet(Target(reply.get.target, prefixes, channelTypes), reply.get.key, reply.get.errorMessage);
146 	}
147 	return output;
148 }
149 ///
150 @safe pure nothrow unittest { //Numeric.RPL_KEYNOTSET
151 	import std.range : only, takeNone;
152 	import virc.common : User;
153 
154 	with(parseNumeric!(Numeric.RPL_KEYNOTSET)(only("client", "someone!test@example.com", "examplekey", "no matching key"), "@", "#").get) {
155 		assert(target== User("someone!test@example.com"));
156 		assert(key== "examplekey");
157 		assert(humanReadable == "no matching key");
158 	}
159 
160 	assert(parseNumeric!(Numeric.RPL_KEYNOTSET)(takeNone(only("")), "@", "#").isNull);
161 	assert(parseNumeric!(Numeric.RPL_KEYNOTSET)(only("client"), "@", "#").isNull);
162 	assert(parseNumeric!(Numeric.RPL_KEYNOTSET)(only("client", "someone!test@example.com"), "@", "#").isNull);
163 	assert(parseNumeric!(Numeric.RPL_KEYNOTSET)(only("client", "someone!test@example.com", "examplekey"), "@", "#").isNull);
164 }
165 
166 /++
167 +
168 + Format is `770 <Client> <Key1> [<Key2> ...]` OR
169 + `771 <Client> <Key1> [<Key2> ...]`
170 + `772 <Client> <Key1> [<Key2> ...]`
171 +/
172 auto parseNumeric(Numeric numeric, T)(T input) if ((numeric == Numeric.RPL_METADATASUBOK) || (numeric == Numeric.RPL_METADATAUNSUBOK) || (numeric == Numeric.RPL_METADATASUBS)) {
173 	import std.typecons : Nullable;
174 	Nullable!(typeof(input)) output;
175 	if (input.empty) {
176 		return output;
177 	}
178 	input.popFront();
179 	output = input;
180 	return output;
181 }
182 ///
183 @safe pure unittest { //Numeric.RPL_METADATASUBOK, Numeric.RPL_METADATAUNSUBOK, Numeric.RPL_METADATASUBS
184 	import std.array : array;
185 	import std.range : only, takeNone;
186 	import virc.common : User;
187 
188 	assert(parseNumeric!(Numeric.RPL_METADATASUBOK)(only("client", "url", "example")).get.array == ["url", "example"]);
189 	assert(parseNumeric!(Numeric.RPL_METADATAUNSUBOK)(only("client", "url", "example")).get.array == ["url", "example"]);
190 	assert(parseNumeric!(Numeric.RPL_METADATASUBS)(only("client", "url", "example")).get.array == ["url", "example"]);
191 
192 	assert(parseNumeric!(Numeric.RPL_METADATASUBOK)(takeNone(only(""))).isNull);
193 	assert(parseNumeric!(Numeric.RPL_METADATAUNSUBOK)(takeNone(only(""))).isNull);
194 	assert(parseNumeric!(Numeric.RPL_METADATASUBS)(takeNone(only(""))).isNull);
195 }
196 
197 struct ERR_MetadataSyncLater {
198 	import core.time : Duration;
199 	import std.typecons : Nullable;
200 	import virc.target : Target;
201 	Target target;
202 	Nullable!Duration retryAfter;
203 }
204 /++
205 +
206 + Format is `774 <Client> <Target>[ <RetryAfter>]`
207 +/
208 auto parseNumeric(Numeric numeric : Numeric.ERR_METADATASYNCLATER, T)(T input, string prefixes, string channelTypes) {
209 	import core.time : Duration;
210 	import std.typecons : Nullable;
211 	import virc.numerics.magicparser : autoParse, Optional;
212 	import virc.target : Target;
213 	struct Reduced {
214 		User me;
215 		string target;
216 		@Optional Duration time;
217 	}
218 	Nullable!ERR_MetadataSyncLater output;
219 	auto reply = autoParse!Reduced(input);
220 	if (!reply.isNull) {
221 		output = ERR_MetadataSyncLater(Target(reply.get.target, prefixes, channelTypes), Nullable!Duration(reply.get.time));
222 	}
223 	return output;
224 }
225 ///
226 @safe pure nothrow unittest { //Numeric.ERR_METADATASYNCLATER
227 	import core.time : seconds;
228 	import std.array : array;
229 	import std.range : only, takeNone;
230 	import virc.common : Channel;
231 
232 	with(parseNumeric!(Numeric.ERR_METADATASYNCLATER)(only("modernclient", "#bigchan", "4"), "@", "#").get) {
233 		assert(target.channel == Channel("#bigchan"));
234 		assert(retryAfter == 4.seconds);
235 	}
236 
237 	assert(parseNumeric!(Numeric.ERR_METADATASYNCLATER)(takeNone(only("")), "@", "#").isNull);
238 }