///// Email content encoding utilities for MIME messages.
///// This module provides functions for encoding email addresses and strings
///// using various MIME content transfer encodings such as Base64, Quoted-Printable,
///// and others defined in RFC standards.
import gleam/int
import gleam/list
import gleam/pair
import gleam/result
import internal/encoder/base64 as b64
import internal/encoder/bit
import internal/encoder/encoding.{
type Encoding, Base64, Bit, PercentEncoding, QuotedPrintable, Text,
}
import internal/encoder/idna
import internal/encoder/percent_encoding as pe
import internal/encoder/quoted_printable as qp
import internal/encoder/text
/// Encodes an email address using IDNA (Internationalized Domain Names in Applications).
pub fn encode_email_address(
email address: String,
with mode: encoding.EncodingMode,
) -> Result(String, encoding.EncoderError) {
idna.encode_email_address(address, mode)
}
/// Encodes a string using the specified content transfer encoding.
///
/// This function delegates to the appropriate encoder based on the encoding type.
pub fn encode_string(
encode string: String,
using encoding: Encoding,
with mode: encoding.EncodingMode,
start position: Int,
preferred_size preferred_size: Int,
maximum_size maximum_size: Int,
) -> Result(List(String), encoding.EncoderError) {
case encoding {
Base64 -> b64.encode_string(string, position, preferred_size)
Bit(bits) -> bit.encode_string(string, bits, mode, maximum_size)
PercentEncoding -> pe.encode_string(string, position, preferred_size)
QuotedPrintable(rfc) ->
qp.encode_string(string, rfc, position, preferred_size)
Text(field_type) ->
text.encode_string(
string,
field_type,
mode,
position,
preferred_size,
maximum_size,
)
}
}
/// Determines the optimal order of encodings to try for encoding a string within a size limit.
///
/// Estimates the encoded size for each encoding and returns them sorted by size
/// from smallest to largest, filtering out encodings that would exceed the maximum size.
/// This is useful for content negotiation to find the most efficient encoding.
pub fn determine_encoding_order(
order encodings: List(Encoding),
encode string: String,
with mode: encoding.EncodingMode,
maximum_size maximum_size: Int,
) -> List(Encoding) {
encodings
|> list.map(fn(encoding) {
encoding
|> estimate_encoded_size(mode, string, maximum_size)
|> result.map(fn(size) { #(encoding, size) })
})
|> result.values()
|> list.sort(fn(a, b) { int.compare(pair.second(a), pair.second(b)) })
|> list.map(pair.first)
}
/// Returns the canonical string name for an encoding type.
pub fn name(encoding: Encoding) -> String {
case encoding {
Base64 -> "base64"
Bit(bits) -> int.to_string(bits) <> "bit"
PercentEncoding -> "percent-encoding"
QuotedPrintable(_) -> "quoted-printable"
Text(_) -> "quoted-string"
}
}
fn estimate_encoded_size(
encoding: Encoding,
mode: encoding.EncodingMode,
value: String,
maximum_size: Int,
) -> Result(Int, encoding.EncoderError) {
case encoding {
Base64 -> b64.estimate_encoded_size(value)
Bit(bits) -> bit.estimate_encoded_size(value, bits, mode, maximum_size)
PercentEncoding -> pe.estimate_encoded_size(value, maximum_size)
QuotedPrintable(rfc) -> qp.estimate_encoded_size(value, rfc)
Text(field_type) ->
text.estimate_encoded_size(value, field_type, mode, maximum_size)
}
}