1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
use crate::request::Form;
use serde::{Deserialize, Serialize};
use twilight_model::{
http::attachment::Attachment,
id::{marker::AttachmentMarker, Id},
};
pub struct AttachmentManager<'a> {
files: Vec<&'a Attachment>,
ids: Vec<Id<AttachmentMarker>>,
}
impl<'a> AttachmentManager<'a> {
pub const fn new() -> Self {
Self {
files: Vec::new(),
ids: Vec::new(),
}
}
pub fn build_form(&self, fields: &'a [u8]) -> Form {
let mut form = Form::new().json_part(b"payload_json", fields);
for file in &self.files {
let mut name = Vec::with_capacity(7 + num_digits(file.id));
name.extend(b"files[");
push_digits(file.id, &mut name);
name.extend(b"]");
form = form.file_part(name.as_ref(), file.filename.as_bytes(), file.file.as_ref());
}
form
}
pub fn get_partial_attachments(&self) -> Vec<PartialAttachment<'a>> {
self.files
.iter()
.map(|attachment| PartialAttachment {
description: attachment.description.as_deref(),
filename: Some(attachment.filename.as_ref()),
id: attachment.id,
})
.chain(self.ids.iter().map(|id| PartialAttachment {
description: None,
filename: None,
id: id.get(),
}))
.collect()
}
pub fn is_empty(&self) -> bool {
self.files.is_empty() && self.ids.is_empty()
}
#[must_use = "has no effect if not built into a Form"]
pub fn set_files(mut self, files: Vec<&'a Attachment>) -> Self {
self.files = files;
self
}
#[must_use = "has no effect if not built into a Form"]
pub fn set_ids(mut self, ids: Vec<Id<AttachmentMarker>>) -> Self {
self.ids = ids;
self
}
}
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct PartialAttachment<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub filename: Option<&'a str>,
pub id: u64,
}
const fn num_digits(index: u64) -> usize {
let mut index = index;
let mut len = 0;
if index < 10 {
return 1;
}
while index > 0 {
index /= 10;
len += 1;
}
len
}
const ASCII_NUMBER: u8 = 0x30;
fn push_digits(mut id: u64, buf: &mut Vec<u8>) {
let mut inner_buf = [0_u8; 20];
let mut i = 0;
while id >= 10 {
#[allow(clippy::cast_possible_truncation)]
let ascii = (id % 10) as u8 + ASCII_NUMBER;
inner_buf[i] = ascii;
id /= 10;
i += 1;
}
#[allow(clippy::cast_possible_truncation)]
let ascii = (id % 10) as u8 + ASCII_NUMBER;
inner_buf[i] = ascii;
i += 1;
inner_buf[..i].reverse();
buf.extend_from_slice(&inner_buf[..i]);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn push_digits_limits() {
let min_d = b"0";
let max_d = b"18446744073709551615";
let mut min_v = Vec::new();
let mut max_v = Vec::new();
push_digits(u64::MIN, &mut min_v);
push_digits(u64::MAX, &mut max_v);
assert_eq!(min_d[..], min_v[..]);
assert_eq!(max_d[..], max_v[..]);
}
#[test]
fn num_digits_count() {
assert_eq!(1, num_digits(0));
assert_eq!(1, num_digits(1));
assert_eq!(2, num_digits(10));
}
}