-- | -- -- Copyright: -- This file is part of the package addy. It is subject to the license -- terms in the LICENSE file found in the top-level directory of this -- distribution and at: -- -- https://code.devalot.com/open/addy -- -- No part of this package, including this file, may be copied, -- modified, propagated, or distributed except according to the terms -- contained in the LICENSE file. -- -- License: BSD-2-Clause -- -- Email addressed are complicated, really complicated. This library -- supports all standardized forms of email addresses, including those -- with UTF-8 encoded Unicode characters. The standards used by this -- library include: -- -- * RFC 1123: Requirements for Internet Hosts -- Application and Support -- * RFC 2181: Clarifications to the DNS Specification -- * RFC 3696: Application Techniques for Checking and Transformation of Names -- * RFC 5321: Simple Mail Transfer Protocol -- * RFC 5322: Internet Message Format -- * RFC 6531: SMTP Extension for Internationalized Email -- * RFC 6532: Internationalized Email Headers module Addy ( -- * How to use this library -- $use -- * Decoding and encoding decode, decodeLenient, encode, encodeFull, -- * Email addresses EmailAddr, emailAddr, emailAddrLit, displayName, localPart, domain, comments, -- * Display name DisplayName, _DisplayName, validateDisplayName, -- * Local part LocalPart, _LocalPart, validateLocalPart, -- * Domain part Domain (..), _Domain, _DomainLiteral, DomainName, _DomainName, validateDomainName, -- * Host names HostName, _HostNames, _HostName, validateHostName, -- * Address literals AddressLiteral (..), _IpAddressLiteral, _TaggedAddressLiteral, _AddressLiteral, AddressTag, _AddressTag, validateAddressTag, Literal, _Literal, validateLiteral, -- * Comments Comment (..), _Comment, commentLoc, commentContent, CommentLoc (..), CommentContent, _CommentContent, validateCommentContent, ) where import Addy.Internal.Parser as P import Addy.Internal.Render as R import Addy.Internal.Types import Addy.Internal.Validation import Control.Lens (Prism', prism') import Validation.Combinators (successToMaybe) -- | Decode an email address. -- -- @since 0.1.0.0 decode :: Text -> Either (NonEmpty Error) EmailAddr decode = P.parseWithMode P.Strict -- | Decode an email address, allowing obsolete characters. The -- obsolete characters are parsed but not included in the output. -- -- This is useful for exacting email addresses from mail messages but -- should not be used to validate user input. -- -- >>> Addy.decode "my . email . addy@(WTF)example.com" -- Left (ParserFailedError "local part > quoted content > '\"': Failed reading: satisfy" :| []) -- -- >>> Addy.decodeLenient "my . email . addy@(WTF)example.com" -- Right (EmailAddr "my.email.addy@example.com (WTF)") -- -- @since 0.1.0.0 decodeLenient :: Text -> Either (NonEmpty Error) EmailAddr decodeLenient = P.parseWithMode P.Lenient -- | Encode an email address as text. This function produces the -- short form of an email address. That is, just the 'LocalPart' and -- the 'Domain' separated by @\@@. -- -- @since 0.1.0.0 encode :: EmailAddr -> Text encode = R.renderToText R.Short -- | Encode a complete email address to text, including the optional -- display name and any comments. -- -- @since 0.1.0.0 encodeFull :: EmailAddr -> Text encodeFull = R.renderToText R.Full -- | Build an 'EmailAddr' from a 'LocalPart' and 'DomainName'. -- -- @since 0.1.0.0 emailAddr :: LocalPart -> DomainName -> EmailAddr emailAddr l d = EmailAddr Nothing l (Domain d) [] -- | Build an 'EmailAddr' from a 'LocalPart' and an 'AddressLiteral'. -- -- @since 0.1.0.0 emailAddrLit :: LocalPart -> AddressLiteral -> EmailAddr emailAddrLit l d = EmailAddr Nothing l (DomainLiteral d) [] -- | Prism for working with display names. -- -- > import Control.Lens ((^?), review) -- -- To convert text into a 'DisplayName' with content validation: -- -- >>> "Some Text" ^? _DisplayName -- Just (DisplayName "Some Text") -- >>> "Some\nText" ^? _DisplayName -- Nothing -- Validation failed. -- -- To access the text content of a 'DisplayName': -- -- >>> review _DisplayName someDisplayName -- "Some Text" -- -- Uses 'validateDisplayName' to perform validation. -- -- @since 0.1.0.0 _DisplayName :: Prism' Text DisplayName _DisplayName = prism' displayNameText (validateDisplayName >>> successToMaybe) -- | Prism for working with the local part of an email address. -- -- > import Control.Lens ((^?), review) -- -- To convert text to a 'LocalPart' with content validation: -- -- >>> "cockroach+mouse" ^? _LocalPart -- Just (LocalPart "cockroach+mouse") -- >>> "cockroach\nmouse" ^? _LocalPart -- Nothing -- Validation failed. -- -- To access the text content of a 'LocalPart': -- -- >>> review _LocalPart someLocalPart -- "cockamouse" -- -- Uses 'validateLocalPart' to perform validation. -- -- @since 0.1.0.0 _LocalPart :: Prism' Text LocalPart _LocalPart = prism' localPartText (validateLocalPart >>> successToMaybe) -- | Prism for working with domain names. -- -- > import Control.Lens ((^?), review) -- -- To convert text to a 'DomainName' with validation: -- -- >>> "gmail.com" ^? _DomainName -- Just (DomainName "gmail.com") -- >>> "too.many.dots." ^? _DomainName -- Nothing -- -- To access the text content of a 'DomainName': -- -- >>> review _DomainName someDomainName -- "gmail.com" -- -- Uses 'validateDomainName' to perform validation. -- -- @since 0.1.0.0 _DomainName :: Prism' Text DomainName _DomainName = prism' domainNameText (validateDomainName >>> successToMaybe) -- | Prism for working with host names (DNS labels). -- -- > import Control.Lens ((^?), review) -- -- To convert text to a host name with validation: -- -- >>> "com" ^? _HostName -- Just (HostName "com") -- >>> "com." ^? _HostName -- Nothing -- Validation failed. -- -- To access the text content of a 'HostName': -- -- >>> review _HostName someHostName -- "com" -- -- Uses 'validateHostName' to perform validation. -- -- @since 0.1.0.0 _HostName :: Prism' Text HostName _HostName = prism' hostNameText (validateHostName >>> successToMaybe) -- | Prism for working with the 'AddressTag' for an 'AddressLiteral'. -- -- > import Control.Lens ((^?), review) -- -- To convert text to an address tag with validation: -- -- >>> "IPv6" ^? _AddressTag -- Just (AddressTag "IPv6") -- >>> "[IPv6]" ^? _AddressTag -- Nothing -- Validation failed. -- -- To access the text content of an 'AddressTag': -- -- >>> review _AddressTag someTag -- "tag" -- -- Uses 'validateAddressTag' to perform validation. -- -- @since 0.1.0.0 _AddressTag :: Prism' Text AddressTag _AddressTag = prism' addressTagText (validateAddressTag >>> successToMaybe) -- | Prism for working with the literal text of an address literal. -- -- > import Control.Lens ((^?), review) -- -- To convert text to an address literal with validation: -- -- >>> "127.0.0.1" ^? _Literal -- Just (Literal "127.0.0.1") -- >>> "[]" ^? _Literal -- Nothing -- Validation failed. -- -- To access the text content of a 'Literal': -- -- >>> review _Literal someLiteral -- "127.0.0.1" -- -- Uses 'validateLiteral' to perform validation. -- -- @since 0.1.0.0 _Literal :: Prism' Text Literal _Literal = prism' literalText (validateLiteral >>> successToMaybe) -- | Prism for working with the text content of a comment. -- -- > import Control.Lens ((^?), review) -- -- To convert text to a comment with validation: -- -- >>> "best email ever" ^? _CommentContent -- Just (CommentContent "best email ever") -- >>> "\n" ^? _CommentContent -- Nothing -- -- To access the text content of the comment: -- -- >>> review _CommentContent someComment -- "super" -- -- Uses 'validateCommentContent' to perform validation. -- -- @since 0.1.0.0 _CommentContent :: Prism' Text CommentContent _CommentContent = prism' commentContentText (validateCommentContent >>> successToMaybe) -- $use -- -- == Importing -- -- This library is designed to be imported qualified: -- -- > import qualified Addy -- -- == Decoding addresses -- -- To decode (parse) an email address from text: -- -- >>> Addy.decode "example@example.com" -- Right (EmailAddr "example@example.com") -- -- >>> Addy.decode "我買@屋企.香港" -- Right (EmailAddr "\25105\36023@\23627\20225.\39321\28207") -- -- >>> Addy.decode "Mary Smith (hi there!)" -- Right (EmailAddr "Mary Smith (hi there!)") -- -- >>> Addy.decode "example@[127.0.0.1]" -- Right (EmailAddr "example@[127.0.0.1]") -- -- After decoding, an address is automatically normalized by performing -- NFC normalization and down-casing the domain name: -- -- >>> Addy.decode "My.Email.Addy@ExAmPlE.COM" -- Right (EmailAddr "My.Email.Addy@example.com") -- -- == Encoding addresses -- -- Turning an email address back to text is just as easy: -- -- >>> Addy.encode address -- "example@example.com" -- -- If an address has an optional display name or comments you can -- render those with the 'encodeFull' function. -- -- >>> :{ -- Addy.decode "Mary Smith (hi there!)" -- & second Addy.encodeFull -- :} -- Right "Mary Smith (hi there!)" -- -- == Creating addresses -- -- In order to prevent invalid email addresses from being created this -- library uses @newtype@ wrappers and does not export the data -- constructors. Therefore you'll need to use the smart constructor -- approach using the 'emailAddr' function. -- -- If you want to work with the validation functions directly we -- recommend 'Applicative' syntax: -- -- >>> :{ -- Addy.emailAddr -- <$> Addy.validateLocalPart "pjones" -- <*> Addy.validateDomainName "devalot.com" -- :} -- Success (EmailAddr "pjones@devalot.com") -- -- Prisms for the @newtype@ wrappers are provided if you want to use optics: -- -- >>> :{ -- Addy.emailAddr -- <$> "pjones" ^? Addy._LocalPart -- <*> "devalot.com" ^? Addy._DomainName -- :} -- Just (EmailAddr "pjones@devalot.com") -- -- == Optics -- -- Lens and prisms are provided to make working with email addresses easier: -- -- > import qualified Addy -- > import Control.Lens -- -- >>> Addy.decode "example@example.com" ^? _Right . Addy.domain -- Just (Domain (DomainName "example.com")) -- -- >>> Addy.decode "example@example.com" -- ^? _Right . Addy.domain . Addy._Domain . Addy._HostNames -- Just [HostName "example",HostName "com"] -- -- >>> Addy.decode "example@example.com" -- ^.. _Right . Addy.domain . Addy._Domain -- . Addy._HostNames . traversed . re _HostName -- ["example","com"] -- -- == A word about address literals -- -- Believe it or not, this is a completely valid email address: -- -- >>> Addy.decode "example@[what's my domain name?]" -- ^? _Right . Addy.domain -- Just (DomainLiteral (AddressLiteral (Literal "what's my domain name?"))) -- -- If you're working with email messages it might be useful to capture -- these address literals, especially if you know how to interpret -- them. However, if you're validating user input you probably don't -- want to allow these. -- -- >>> Addy.decode "e@[127.0.0.1]" ^? _Right -- >>= failover (Addy.domain . Addy._Domain) id -- Nothing -- -- >>> Addy.decode "example@example.com" ^? _Right -- >>= failover (Addy.domain . Addy._Domain) id -- Just (EmailAddr "example@example.com")