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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
//! Create embed fields.

use std::{
    error::Error,
    fmt::{Display, Formatter, Result as FmtResult},
};
use twilight_model::channel::embed::EmbedField;

/// Error creating an embed field.
///
/// This is returned from [`EmbedFieldBuilder::new`].
///
/// [`EmbedFieldBuilder::new`]: struct.EmbedFieldBuilder.html#method.new
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum EmbedFieldError {
    /// Name is empty.
    NameEmpty {
        /// Provided name. Although empty, the same owned allocation is
        /// included.
        name: String,
        /// Provided value.
        value: String,
    },
    /// Name is longer than 256 UTF-16 code points.
    NameTooLong {
        /// Provided name.
        name: String,
        /// Provided value.
        value: String,
    },
    /// Value is empty.
    ValueEmpty {
        /// Provided name.
        name: String,
        /// Provided value. Although empty, the same owned allocation is
        /// included.
        value: String,
    },
    /// Value is longer than 1024 UTF-16 code points.
    ValueTooLong {
        /// Provided name.
        name: String,
        /// Provided value.
        value: String,
    },
}

impl Display for EmbedFieldError {
    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
        match self {
            Self::NameEmpty { .. } => f.write_str("the field name is empty"),
            Self::NameTooLong { .. } => f.write_str("the field name is too long"),
            Self::ValueEmpty { .. } => f.write_str("the field value is empty"),
            Self::ValueTooLong { .. } => f.write_str("the field value is too long"),
        }
    }
}

impl Error for EmbedFieldError {}

/// Create an embed field with a builder.
///
/// This can be passed into [`EmbedBuilder::field`].
///
/// Fields are not inlined by default. Use [`inline`] to inline a field.
///
/// [`EmbedBuilder::field`]: ../builder/struct.EmbedBuilder.html#method.field
/// [`inline`]: #method.inline
#[derive(Clone, Debug, Eq, PartialEq)]
#[must_use = "must be built into an embed field"]
pub struct EmbedFieldBuilder(EmbedField);

impl EmbedFieldBuilder {
    /// The maximum number of UTF-16 code points that can be in a field name.
    ///
    /// This is used by [`new`].
    ///
    /// [`new`]: #method.new
    pub const NAME_LENGTH_LIMIT: usize = 256;

    /// The maximum number of UTF-16 code points that can be in a field value.
    ///
    /// This is used by [`new`].
    ///
    /// [`new`]: #method.new
    pub const VALUE_LENGTH_LIMIT: usize = 1024;

    /// Create a new default embed field builder.
    ///
    /// The name is limited to 256 UTF-16 code points, and the value is limited
    /// to 1024.
    ///
    /// # Errors
    ///
    /// Returns [`EmbedFieldError::NameEmpty`] if the provided name is
    /// empty.
    ///
    /// Returns [`EmbedFieldError::NameTooLong`] if the provided name is
    /// longer than 256 UTF-16 code points.
    ///
    /// Returns [`EmbedFieldError::ValueEmpty`] if the provided value is
    /// empty.
    ///
    /// Returns [`EmbedFieldError::ValueTooLong`] if the provided value
    /// is longer than 1024 UTF-16 code points.
    ///
    /// [`EmbedFieldError::NameEmpty`]: enum.EmbedFieldError.html#variant.NameEmpty
    /// [`EmbedFieldError::NameTooLong`]: enum.EmbedFieldError.html#variant.NameTooLong
    /// [`EmbedFieldError::ValueEmpty`]: enum.EmbedFieldError.html#variant.ValueEmpty
    /// [`EmbedFieldError::ValueTooLong`]: enum.EmbedFieldError.html#variant.ValueTooLong
    pub fn new(name: impl Into<String>, value: impl Into<String>) -> Result<Self, EmbedFieldError> {
        Self::_new(name.into(), value.into())
    }

    fn _new(name: String, value: String) -> Result<Self, EmbedFieldError> {
        if name.is_empty() {
            return Err(EmbedFieldError::NameEmpty { name, value });
        }

        if name.chars().count() > Self::NAME_LENGTH_LIMIT {
            return Err(EmbedFieldError::NameTooLong { name, value });
        }

        if value.is_empty() {
            return Err(EmbedFieldError::ValueEmpty { name, value });
        }

        if value.chars().count() > Self::VALUE_LENGTH_LIMIT {
            return Err(EmbedFieldError::ValueTooLong { name, value });
        }

        Ok(Self(EmbedField {
            inline: false,
            name,
            value,
        }))
    }

    /// Build into an embed field.
    #[must_use = "should be used as part of an embed builder"]
    pub fn build(self) -> EmbedField {
        self.0
    }

    /// Inline the field.
    ///
    /// # Examples
    ///
    /// Create an inlined field:
    ///
    /// ```rust
    /// use twilight_embed_builder::EmbedFieldBuilder;
    ///
    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
    /// let field = EmbedFieldBuilder::new("twilight", "is cool")?
    ///     .inline()
    ///     .build();
    /// # Ok(()) }
    /// ```
    pub fn inline(mut self) -> Self {
        self.0.inline = true;

        self
    }
}

impl From<EmbedFieldBuilder> for EmbedField {
    /// Convert an embed field builder into an embed field.
    ///
    /// This is equivalent to calling [`EmbedFieldBuilder::build`].
    ///
    /// [`EmbedFieldBuilder::build`]: #method.build
    fn from(builder: EmbedFieldBuilder) -> Self {
        builder.build()
    }
}

#[cfg(test)]
mod tests {
    use super::{EmbedFieldBuilder, EmbedFieldError};
    use static_assertions::{assert_fields, assert_impl_all, const_assert};
    use std::{error::Error, fmt::Debug};
    use twilight_model::channel::embed::EmbedField;

    assert_impl_all!(
        EmbedFieldError: Clone,
        Debug,
        Error,
        Eq,
        PartialEq,
        Send,
        Sync
    );
    assert_fields!(EmbedFieldError::NameEmpty: name, value);
    assert_fields!(EmbedFieldError::NameTooLong: name, value);
    assert_fields!(EmbedFieldError::ValueEmpty: name, value);
    assert_fields!(EmbedFieldError::ValueTooLong: name, value);
    assert_impl_all!(EmbedFieldBuilder: Clone, Debug, Eq, PartialEq, Send, Sync);
    const_assert!(EmbedFieldBuilder::NAME_LENGTH_LIMIT == 256);
    const_assert!(EmbedFieldBuilder::VALUE_LENGTH_LIMIT == 1024);
    assert_impl_all!(EmbedField: From<EmbedFieldBuilder>);

    #[test]
    fn test_new_errors() {
        assert!(matches!(
            EmbedFieldBuilder::new("", "a"),
            Err(EmbedFieldError::NameEmpty { name, value })
            if name.is_empty() && value.len() == 1
        ));
        assert!(matches!(
            EmbedFieldBuilder::new("a".repeat(257), "a"),
            Err(EmbedFieldError::NameTooLong { name, value })
            if name.len() == 257 && value.len() == 1
        ));
        assert!(matches!(
            EmbedFieldBuilder::new("a", ""),
            Err(EmbedFieldError::ValueEmpty { name, value })
            if name.len() == 1 && value.is_empty()
        ));
        assert!(matches!(
            EmbedFieldBuilder::new("a", "a".repeat(1025)),
            Err(EmbedFieldError::ValueTooLong { name, value })
            if name.len() == 1 && value.len() == 1025
        ));
    }

    #[test]
    fn test_builder_inline() -> Result<(), Box<dyn Error>> {
        let expected = EmbedField {
            inline: true,
            name: "name".to_owned(),
            value: "value".to_owned(),
        };
        let actual = EmbedFieldBuilder::new("name", "value")?.inline().build();

        assert_eq!(actual, expected);

        Ok(())
    }

    #[test]
    fn test_builder_no_inline() -> Result<(), Box<dyn Error>> {
        let expected = EmbedField {
            inline: false,
            name: "name".to_owned(),
            value: "value".to_owned(),
        };
        let actual = EmbedFieldBuilder::new("name", "value")?.build();

        assert_eq!(actual, expected);

        Ok(())
    }
}