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
//! Gemini Response Headers //! //! This module contains types to represent Gemini response headers, construct //! them from their component parts, and for examining the type and content of //! the Gemini <META> field, which represents, among other things, the MIME type //! of successful Gemini responses. See `MetaKind` for more. //! //! # Examples //! //! ``` //! use gemini::{Header, MetaKind, Status}; //! //! assert_eq!(Header::new(Status::INPUT, "Hello!".to_string()).unwrap().meta_kind(), MetaKind::Prompt); //! assert_eq!(Header::new(Status::SUCCESS, "".to_string()).unwrap().mime_type().unwrap(), "text/gemini; charset=utf-8"); //! ``` //! //! some item comments are taken verbatim from //! [the Gemini spec](https://gemini.circumlunar.space/docs/specification.html) use crate::status::{Category, Status}; // I'd love for this struct to derive(Copy), but a naive implementation would // always incur 1kb of overhead per header, which seems unnecessary when most // will probably be merely a few bytes. How to weigh those concerns? /// Gemini response headers. /// /// Consist of a valid `Status` along with a <META> field, which has a maximum /// length of 1024 bytes and must be valid utf-8 text. #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd)] pub struct Header { /// Status associated with the response header. pub status: Status, meta: String, } /// Type that represents the semantics of the <META> field for different sorts /// of response statuses. #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum MetaKind { /// The <META> line is a prompt which should be displayed to the user. Prompt, /// <META> is a new URL for the requested resource. The URL may be absolute or relative. MimeType, /// <META> is a new URL for the requested resource. RedirectTarget, /// The contents of <META> may provide additional information on the response, and should be displayed to human users. Message, } impl Header { /// Maximum length, in bytes, of the <META> field. pub const MAX_META_LEN: usize = 1024; /// Default MIME type for successful Gemini responses. pub const DEFAULT_MIME_TYPE: &'static str = "text/gemini; charset=utf-8"; /// Retrieve the <META> field as a utf-8 encoded string slice. pub fn meta(&self) -> &str { self.meta.as_str() } /// Return which `MetaKind` is assoicated with the response. /// /// Clients should use this to decide how to interpret response bodies. pub fn meta_kind(&self) -> MetaKind { match self.status.category() { Category::Input => MetaKind::Prompt, Category::Success => MetaKind::MimeType, Category::Redirect => MetaKind::RedirectTarget, Category::TemporaryFailure | Category::PermanentFailure | Category::ClientCertificateRequired => MetaKind::Message, } } /// Return the MIME media type for a header which has it. pub fn mime_type(&self) -> Option<&str> { match self.meta_kind() { MetaKind::MimeType => Some(self.meta()), _ => None, } } /// Construct a new `Header` from a valid `Status` and a utf-8 string. /// /// Will return an `Err` if the length of the provided meta exceeds /// `Self::MAX_META_LEN`. pub fn new(status: Status, meta: String) -> Option<Self> { if meta.len() < Self::MAX_META_LEN { let meta = match status { Status::SUCCESS if meta.trim().is_empty() => Self::DEFAULT_MIME_TYPE.to_string(), _ => meta, }; Some(Header { status, meta }) } else { None } } /// Construct a `Status::SUCCESS` header with the given mime-type. pub fn success(mime_type: String) -> Option<Self> { Self::new(Status::SUCCESS, mime_type) } /// Construct a `Status::SUCCESS` header with the text/gemini mime-type. pub fn gemtext() -> Self { let status = Status::SUCCESS; let meta = Self::DEFAULT_MIME_TYPE.to_string(); Header { status, meta } } }