patrol 1.2.0.5 → 1.3.0.0
raw patch · 14 files changed
+418/−9 lines, 14 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Patrol: data ClientReport
+ Patrol: data DataCategory
+ Patrol: data DiscardedEvent
+ Patrol.Type.ClientReport: ClientReport :: Maybe UTCTime -> [DiscardedEvent] -> ClientReport
+ Patrol.Type.ClientReport: [discardedEvents] :: ClientReport -> [DiscardedEvent]
+ Patrol.Type.ClientReport: [timestamp] :: ClientReport -> Maybe UTCTime
+ Patrol.Type.ClientReport: data ClientReport
+ Patrol.Type.ClientReport: empty :: ClientReport
+ Patrol.Type.ClientReport: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.ClientReport.ClientReport
+ Patrol.Type.ClientReport: instance GHC.Classes.Eq Patrol.Type.ClientReport.ClientReport
+ Patrol.Type.ClientReport: instance GHC.Internal.Show.Show Patrol.Type.ClientReport.ClientReport
+ Patrol.Type.DataCategory: Attachment :: DataCategory
+ Patrol.Type.DataCategory: Default :: DataCategory
+ Patrol.Type.DataCategory: Error :: DataCategory
+ Patrol.Type.DataCategory: Feedback :: DataCategory
+ Patrol.Type.DataCategory: Internal :: DataCategory
+ Patrol.Type.DataCategory: LogItem :: DataCategory
+ Patrol.Type.DataCategory: Monitor :: DataCategory
+ Patrol.Type.DataCategory: Profile :: DataCategory
+ Patrol.Type.DataCategory: ProfileChunk :: DataCategory
+ Patrol.Type.DataCategory: Replay :: DataCategory
+ Patrol.Type.DataCategory: Security :: DataCategory
+ Patrol.Type.DataCategory: Session :: DataCategory
+ Patrol.Type.DataCategory: Span :: DataCategory
+ Patrol.Type.DataCategory: TraceMetric :: DataCategory
+ Patrol.Type.DataCategory: Transaction :: DataCategory
+ Patrol.Type.DataCategory: data DataCategory
+ Patrol.Type.DataCategory: fromText :: MonadThrow m => Text -> m DataCategory
+ Patrol.Type.DataCategory: instance Data.Aeson.Types.FromJSON.FromJSON Patrol.Type.DataCategory.DataCategory
+ Patrol.Type.DataCategory: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.DataCategory.DataCategory
+ Patrol.Type.DataCategory: instance GHC.Classes.Eq Patrol.Type.DataCategory.DataCategory
+ Patrol.Type.DataCategory: instance GHC.Classes.Ord Patrol.Type.DataCategory.DataCategory
+ Patrol.Type.DataCategory: instance GHC.Internal.Show.Show Patrol.Type.DataCategory.DataCategory
+ Patrol.Type.DataCategory: intoText :: DataCategory -> Text
+ Patrol.Type.DiscardedEvent: DiscardedEvent :: Text -> DataCategory -> Int -> DiscardedEvent
+ Patrol.Type.DiscardedEvent: [category] :: DiscardedEvent -> DataCategory
+ Patrol.Type.DiscardedEvent: [quantity] :: DiscardedEvent -> Int
+ Patrol.Type.DiscardedEvent: [reason] :: DiscardedEvent -> Text
+ Patrol.Type.DiscardedEvent: data DiscardedEvent
+ Patrol.Type.DiscardedEvent: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.DiscardedEvent.DiscardedEvent
+ Patrol.Type.DiscardedEvent: instance GHC.Classes.Eq Patrol.Type.DiscardedEvent.DiscardedEvent
+ Patrol.Type.DiscardedEvent: instance GHC.Internal.Show.Show Patrol.Type.DiscardedEvent.DiscardedEvent
+ Patrol.Type.Item: ClientReport :: ClientReport -> Item
Files
- README.md +19/−0
- patrol.cabal +7/−1
- source/library/Patrol.hs +6/−0
- source/library/Patrol/Client.hs +9/−0
- source/library/Patrol/Type/ClientReport.hs +30/−0
- source/library/Patrol/Type/DataCategory.hs +79/−0
- source/library/Patrol/Type/DiscardedEvent.hs +25/−0
- source/library/Patrol/Type/EventId.hs +10/−2
- source/library/Patrol/Type/Item.hs +19/−6
- source/test-suite/Patrol/Type/ClientReportSpec.hs +74/−0
- source/test-suite/Patrol/Type/DataCategorySpec.hs +82/−0
- source/test-suite/Patrol/Type/DiscardedEventSpec.hs +33/−0
- source/test-suite/Patrol/Type/ItemSpec.hs +19/−0
- source/test-suite/PatrolSpec.hs +6/−0
README.md view
@@ -12,6 +12,10 @@ The simplest way to report an exception is with `captureException`. It reads the DSN from the `SENTRY_DSN` environment variable. +Note: `captureException` itself can throw exceptions, for example if+the call to Sentry returns a non-success HTTP code or the network is+down.+ ```haskell import qualified Patrol @@ -30,6 +34,9 @@ Use `captureExceptionWith` to add tags, set the environment, attach user info, or otherwise modify the event before it is sent. +As with `captureException`, `captureExceptionWith` itself can throw+exceptions.+ ```haskell import qualified Data.Map as Map import qualified Data.Text as Text@@ -90,4 +97,16 @@ , (Text.pack "status_code", Text.pack (show (Http.statusCode status))) ] }+```++If you want the response handler to `respond` even if the call to+Sentry throws an exception, you can e.g. wrap the call in `catch` or+`try`, like++```haskell+import Control.Exception (try) -- or UnliftIO.Exception(try)++[...]++ _ <- try (Patrol.captureExceptionWith (modifyEvent request status) dsn err) :: IO (Either SomeException Patrol.Response) ```
patrol.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: patrol-version: 1.2.0.5+version: 1.3.0.0 synopsis: Sentry SDK description: Patrol is a Sentry SDK. build-type: Simple@@ -79,12 +79,15 @@ Patrol.Type.BreadcrumbType Patrol.Type.BrowserContext Patrol.Type.CError+ Patrol.Type.ClientReport Patrol.Type.ClientSdkInfo Patrol.Type.ClientSdkPackage Patrol.Type.Context+ Patrol.Type.DataCategory Patrol.Type.DebugImage Patrol.Type.DebugMeta Patrol.Type.DeviceContext+ Patrol.Type.DiscardedEvent Patrol.Type.Dsn Patrol.Type.Envelope Patrol.Type.ErrorType@@ -159,12 +162,15 @@ Patrol.Type.BreadcrumbTypeSpec Patrol.Type.BrowserContextSpec Patrol.Type.CErrorSpec+ Patrol.Type.ClientReportSpec Patrol.Type.ClientSdkInfoSpec Patrol.Type.ClientSdkPackageSpec Patrol.Type.ContextSpec+ Patrol.Type.DataCategorySpec Patrol.Type.DebugImageSpec Patrol.Type.DebugMetaSpec Patrol.Type.DeviceContextSpec+ Patrol.Type.DiscardedEventSpec Patrol.Type.DsnSpec Patrol.Type.EnvelopeSpec Patrol.Type.ErrorTypeSpec
source/library/Patrol.hs view
@@ -9,12 +9,15 @@ Patrol.Type.BreadcrumbType.BreadcrumbType, Patrol.Type.BrowserContext.BrowserContext, Patrol.Type.CError.CError,+ Patrol.Type.ClientReport.ClientReport, Patrol.Type.ClientSdkInfo.ClientSdkInfo, Patrol.Type.ClientSdkPackage.ClientSdkPackage, Patrol.Type.Context.Context,+ Patrol.Type.DataCategory.DataCategory, Patrol.Type.DebugImage.DebugImage, Patrol.Type.DebugMeta.DebugMeta, Patrol.Type.DeviceContext.DeviceContext,+ Patrol.Type.DiscardedEvent.DiscardedEvent, Patrol.Type.Dsn.Dsn, Patrol.Type.Envelope.Envelope, Patrol.Type.ErrorType.ErrorType,@@ -64,12 +67,15 @@ import qualified Patrol.Type.Breadcrumbs import qualified Patrol.Type.BrowserContext import qualified Patrol.Type.CError+import qualified Patrol.Type.ClientReport import qualified Patrol.Type.ClientSdkInfo import qualified Patrol.Type.ClientSdkPackage import qualified Patrol.Type.Context+import qualified Patrol.Type.DataCategory import qualified Patrol.Type.DebugImage import qualified Patrol.Type.DebugMeta import qualified Patrol.Type.DeviceContext+import qualified Patrol.Type.DiscardedEvent import qualified Patrol.Type.Dsn import qualified Patrol.Type.Envelope import qualified Patrol.Type.ErrorType
source/library/Patrol/Client.hs view
@@ -19,6 +19,8 @@ -- | Capture an exception by sending it to Sentry. The DSN is read from the -- @SENTRY_DSN@ environment variable. To customize the behavior, use -- 'captureExceptionWith'.+--+-- May throw an exception of the call to Sentry fails. captureException :: (Catch.Exception e, IO.MonadIO io, Catch.MonadThrow io) => e ->@@ -29,6 +31,13 @@ Dsn.fromText $ maybe Text.empty Text.pack maybeString captureExceptionWith pure dsn e +-- | Capture an exception by sending it to Sentry, possibly modifying+-- the event first.+--+-- Unlike `captureException`, this gets the DSN from an argument+-- instead of from environment variables.+--+-- May throw an exception of the call to Sentry fails. captureExceptionWith :: (Catch.Exception e, IO.MonadIO io, Catch.MonadThrow io) => -- | How to modify the 'Event.Event' before it is sent. Use @'pure'@ if you
+ source/library/Patrol/Type/ClientReport.hs view
@@ -0,0 +1,30 @@+module Patrol.Type.ClientReport where++import qualified Data.Aeson as Aeson+import qualified Data.Time as Time+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.DiscardedEvent as DiscardedEvent++-- | A client report envelope item, used to report locally-discarded telemetry+-- back to Sentry.+--+-- <https://develop.sentry.dev/sdk/telemetry/client-reports/>+data ClientReport = ClientReport+ { timestamp :: Maybe Time.UTCTime,+ discardedEvents :: [DiscardedEvent.DiscardedEvent]+ }+ deriving (Eq, Show)++instance Aeson.ToJSON ClientReport where+ toJSON clientReport =+ Aeson.intoObject+ [ Aeson.pair "timestamp" $ timestamp clientReport,+ Aeson.pair "discarded_events" $ discardedEvents clientReport+ ]++empty :: ClientReport+empty =+ ClientReport+ { timestamp = Nothing,+ discardedEvents = []+ }
+ source/library/Patrol/Type/DataCategory.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE OverloadedStrings #-}++module Patrol.Type.DataCategory where++import qualified Control.Monad.Catch as Catch+import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Data.Typeable as Typeable+import qualified Patrol.Exception.Problem as Problem++-- | The data category associated with discarded and/or rate-limited events.+--+-- <https://develop.sentry.dev/sdk/foundations/transport/rate-limiting/#definitions>+-- <https://develop.sentry.dev/sdk/telemetry/client-reports/#data-categories>+data DataCategory+ = Default+ | Error+ | Transaction+ | Monitor+ | Span+ | LogItem+ | Security+ | Attachment+ | Session+ | Profile+ | ProfileChunk+ | Replay+ | Feedback+ | TraceMetric+ | Internal+ deriving (Eq, Ord, Show)++instance Aeson.FromJSON DataCategory where+ parseJSON =+ let name = show $ Typeable.typeRep (Typeable.Proxy :: Typeable.Proxy DataCategory)+ in Aeson.withText name $ maybe (fail $ "invalid " <> name) pure . fromText++instance Aeson.ToJSON DataCategory where+ toJSON = Aeson.toJSON . intoText++-- | Render a 'DataCategory' to its canonical wire format.+intoText :: DataCategory -> Text.Text+intoText dataCategory = case dataCategory of+ Attachment -> "attachment"+ Default -> "default"+ Error -> "error"+ Feedback -> "feedback"+ Internal -> "internal"+ LogItem -> "log_item"+ Monitor -> "monitor"+ Profile -> "profile"+ ProfileChunk -> "profile_chunk"+ Replay -> "replay"+ Security -> "security"+ Session -> "session"+ Span -> "span"+ TraceMetric -> "trace_metric"+ Transaction -> "transaction"++-- | Attempt to parse the given string into a 'DataCategory'; the inverse of+-- 'intoText'.+fromText :: (Catch.MonadThrow m) => Text.Text -> m DataCategory+fromText text = case text of+ "attachment" -> pure Attachment+ "default" -> pure Default+ "error" -> pure Error+ "feedback" -> pure Feedback+ "internal" -> pure Internal+ "log_item" -> pure LogItem+ "monitor" -> pure Monitor+ "profile" -> pure Profile+ "profile_chunk" -> pure ProfileChunk+ "replay" -> pure Replay+ "security" -> pure Security+ "session" -> pure Session+ "span" -> pure Span+ "trace_metric" -> pure TraceMetric+ "transaction" -> pure Transaction+ _ -> Catch.throwM . Problem.Problem $ "invalid DataCategory: " <> show text
+ source/library/Patrol/Type/DiscardedEvent.hs view
@@ -0,0 +1,25 @@+module Patrol.Type.DiscardedEvent where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.DataCategory as DataCategory++-- | A single entry in a client report describing how many events were discarded+-- for a given reason and data category.+--+-- <https://develop.sentry.dev/sdk/telemetry/client-reports/#envelope-item-payload>+data DiscardedEvent = DiscardedEvent+ { reason :: Text.Text,+ category :: DataCategory.DataCategory,+ quantity :: Int+ }+ deriving (Eq, Show)++instance Aeson.ToJSON DiscardedEvent where+ toJSON discardedEvent =+ Aeson.intoObject+ [ Aeson.pair "reason" $ reason discardedEvent,+ Aeson.pair "category" $ category discardedEvent,+ Aeson.pair "quantity" $ quantity discardedEvent+ ]
source/library/Patrol/Type/EventId.hs view
@@ -4,13 +4,15 @@ import qualified Control.Monad.Catch as Catch import qualified Control.Monad.IO.Class as IO import qualified Data.Aeson as Aeson+import qualified Data.ByteString.Builder as Builder+import qualified Data.ByteString.Lazy as LazyByteString import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text import qualified Data.Text.Read as Text import qualified Data.Typeable as Typeable import qualified Data.UUID as Uuid import qualified Data.UUID.V4 as Uuid import qualified Patrol.Exception.Problem as Problem-import qualified Text.Printf as Printf newtype EventId = EventId Uuid.UUID@@ -36,10 +38,16 @@ random :: (IO.MonadIO io) => io EventId random = IO.liftIO $ fmap fromUuid Uuid.nextRandom +-- | Render an 'EventId' as 32 zero-padded lowercase hexadecimal digits.+--+-- __NOTE__: The output is ASCII, so 'Text.decodeLatin1' is total. intoText :: EventId -> Text.Text intoText eventId = let (lo, hi) = Uuid.toWords64 $ intoUuid eventId- in Text.pack $ Printf.printf "%016x%016x" lo hi+ in Text.decodeLatin1+ . LazyByteString.toStrict+ . Builder.toLazyByteString+ $ Builder.word64HexFixed lo <> Builder.word64HexFixed hi fromText :: (Catch.MonadThrow m) => Text.Text -> m EventId fromText t1 = do
source/library/Patrol/Type/Item.hs view
@@ -4,9 +4,10 @@ import qualified Data.Aeson as Aeson import qualified Data.Aeson.Key as Key-import qualified Data.ByteString as ByteString import qualified Data.ByteString.Builder as Builder import qualified Data.ByteString.Lazy as LazyByteString+import Data.Int (Int64)+import qualified Patrol.Type.ClientReport as ClientReport import qualified Patrol.Type.Event as Event -- | <https://develop.sentry.dev/sdk/data-model/envelope-items/>@@ -15,19 +16,31 @@ -- -- <https://develop.sentry.dev/sdk/envelopes/#event> Event Event.Event+ | -- | A 'Patrol.Type.ClientReport.ClientReport' item, used to report+ -- locally-discarded telemetry back to Sentry.+ --+ -- <https://develop.sentry.dev/sdk/telemetry/client-reports/>+ ClientReport ClientReport.ClientReport | -- | A sentinel item used to filter raw envelopes. Raw deriving (Eq, Show) serialize :: Item -> Builder.Builder serialize item = case item of- Event event ->- let payload = LazyByteString.toStrict $ Aeson.encode event- headers = buildHeaders "event" (ByteString.length payload)- in headers <> Builder.char7 '\n' <> Builder.byteString payload+ Event event -> envelopeItem "event" (Aeson.encode event)+ ClientReport clientReport -> envelopeItem "client_report" (Aeson.encode clientReport) Raw -> mempty where- buildHeaders :: Key.Key -> Int -> Builder.Builder+ -- Keep the encoded payload lazy: take the item-header length from the lazy+ -- chunks and stream them straight into the builder, rather than forcing a+ -- contiguous strict copy of every payload.+ envelopeItem :: Key.Key -> LazyByteString.ByteString -> Builder.Builder+ envelopeItem type_ payload =+ buildHeaders type_ (LazyByteString.length payload)+ <> Builder.char7 '\n'+ <> Builder.lazyByteString payload++ buildHeaders :: Key.Key -> Int64 -> Builder.Builder buildHeaders type_ length_ = Aeson.fromEncoding $ Aeson.pairs ("type" Aeson..= type_ <> "length" Aeson..= length_)
+ source/test-suite/Patrol/Type/ClientReportSpec.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.ClientReportSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.ByteString as ByteString+import qualified Data.ByteString.Builder as Builder+import qualified Data.ByteString.Char8 as Char8+import qualified Data.ByteString.Lazy as LazyByteString+import qualified Patrol.Type.ClientReport as ClientReport+import qualified Patrol.Type.DataCategory as DataCategory+import qualified Patrol.Type.DiscardedEvent as DiscardedEvent+import qualified Patrol.Type.Item as Item+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.ClientReport" $ do+ Hspec.describe "ToJSON" $ do+ Hspec.it "omits timestamp when absent" $ do+ let report =+ ClientReport.ClientReport+ { ClientReport.timestamp = Nothing,+ ClientReport.discardedEvents =+ [ DiscardedEvent.DiscardedEvent+ { DiscardedEvent.reason = "sample_rate",+ DiscardedEvent.category = DataCategory.Error,+ DiscardedEvent.quantity = 3+ }+ ]+ }+ Aeson.toJSON report+ `Hspec.shouldBe` [Aeson.aesonQQ| {"discarded_events": [{"reason": "sample_rate", "category": "error", "quantity": 3}]} |]++ Hspec.it "omits discarded_events when empty" $ do+ Aeson.toJSON ClientReport.empty+ `Hspec.shouldBe` [Aeson.aesonQQ| {} |]++ Hspec.describe "Item.serialize" $ do+ Hspec.it "frames a ClientReport item with the correct type header" $ do+ let report =+ ClientReport.ClientReport+ { ClientReport.timestamp = Nothing,+ ClientReport.discardedEvents =+ [ DiscardedEvent.DiscardedEvent+ { DiscardedEvent.reason = "sample_rate",+ DiscardedEvent.category = DataCategory.Error,+ DiscardedEvent.quantity = 3+ }+ ]+ }+ actual =+ LazyByteString.toStrict+ . Builder.toLazyByteString+ . Item.serialize+ $ Item.ClientReport report+ -- The serialized item is a one-line JSON header, a newline, then the+ -- payload. aeson emits no newlines, so the separator inserted by+ -- 'Item.serialize' is the only one and splitting on it is safe.+ (header, rest) = Char8.break (== '\n') actual+ payload = ByteString.drop 1 rest+ -- Assert the header framing and that it declares the payload's length.+ Aeson.decodeStrict header+ `Hspec.shouldBe` Just+ ( Aeson.object+ [ "type" Aeson..= ("client_report" :: String),+ "length" Aeson..= ByteString.length payload+ ]+ )+ -- Compare the payload as decoded JSON so the assertion does not depend on+ -- aeson's (semantically meaningless) object key ordering.+ Aeson.decodeStrict payload+ `Hspec.shouldBe` Just [Aeson.aesonQQ| {"discarded_events": [{"reason": "sample_rate", "category": "error", "quantity": 3}]} |]
+ source/test-suite/Patrol/Type/DataCategorySpec.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.DataCategorySpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import Data.Foldable (for_)+import qualified Patrol.Type.DataCategory as DataCategory+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.DataCategory" $ do+ Hspec.describe "ToJSON" $ do+ Hspec.it "works for Attachment" $ do+ Aeson.toJSON DataCategory.Attachment `Hspec.shouldBe` [Aeson.aesonQQ| "attachment" |]++ Hspec.it "works for Default" $ do+ Aeson.toJSON DataCategory.Default `Hspec.shouldBe` [Aeson.aesonQQ| "default" |]++ Hspec.it "works for Error" $ do+ Aeson.toJSON DataCategory.Error `Hspec.shouldBe` [Aeson.aesonQQ| "error" |]++ Hspec.it "works for Feedback" $ do+ Aeson.toJSON DataCategory.Feedback `Hspec.shouldBe` [Aeson.aesonQQ| "feedback" |]++ Hspec.it "works for Internal" $ do+ Aeson.toJSON DataCategory.Internal `Hspec.shouldBe` [Aeson.aesonQQ| "internal" |]++ Hspec.it "works for LogItem" $ do+ Aeson.toJSON DataCategory.LogItem `Hspec.shouldBe` [Aeson.aesonQQ| "log_item" |]++ Hspec.it "works for Monitor" $ do+ Aeson.toJSON DataCategory.Monitor `Hspec.shouldBe` [Aeson.aesonQQ| "monitor" |]++ Hspec.it "works for Profile" $ do+ Aeson.toJSON DataCategory.Profile `Hspec.shouldBe` [Aeson.aesonQQ| "profile" |]++ Hspec.it "works for ProfileChunk" $ do+ Aeson.toJSON DataCategory.ProfileChunk `Hspec.shouldBe` [Aeson.aesonQQ| "profile_chunk" |]++ Hspec.it "works for Replay" $ do+ Aeson.toJSON DataCategory.Replay `Hspec.shouldBe` [Aeson.aesonQQ| "replay" |]++ Hspec.it "works for Security" $ do+ Aeson.toJSON DataCategory.Security `Hspec.shouldBe` [Aeson.aesonQQ| "security" |]++ Hspec.it "works for Session" $ do+ Aeson.toJSON DataCategory.Session `Hspec.shouldBe` [Aeson.aesonQQ| "session" |]++ Hspec.it "works for Span" $ do+ Aeson.toJSON DataCategory.Span `Hspec.shouldBe` [Aeson.aesonQQ| "span" |]++ Hspec.it "works for TraceMetric" $ do+ Aeson.toJSON DataCategory.TraceMetric `Hspec.shouldBe` [Aeson.aesonQQ| "trace_metric" |]++ Hspec.it "works for Transaction" $ do+ Aeson.toJSON DataCategory.Transaction `Hspec.shouldBe` [Aeson.aesonQQ| "transaction" |]++ Hspec.describe "FromJSON" $ do+ Hspec.it "fails for an unknown token" $ do+ Aeson.fromJSON [Aeson.aesonQQ| "bogus" |] `Hspec.shouldBe` (Aeson.Error "invalid DataCategory" :: Aeson.Result DataCategory.DataCategory)++ Hspec.it "round-trips every category through ToJSON" $ do+ let categories =+ [ DataCategory.Attachment,+ DataCategory.Default,+ DataCategory.Error,+ DataCategory.Feedback,+ DataCategory.Internal,+ DataCategory.LogItem,+ DataCategory.Monitor,+ DataCategory.Profile,+ DataCategory.ProfileChunk,+ DataCategory.Replay,+ DataCategory.Security,+ DataCategory.Session,+ DataCategory.Span,+ DataCategory.TraceMetric,+ DataCategory.Transaction+ ]+ for_ categories $ \c ->+ Aeson.fromJSON (Aeson.toJSON c) `Hspec.shouldBe` Aeson.Success c
+ source/test-suite/Patrol/Type/DiscardedEventSpec.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.DiscardedEventSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Patrol.Type.DataCategory as DataCategory+import qualified Patrol.Type.DiscardedEvent as DiscardedEvent+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.DiscardedEvent" $ do+ Hspec.describe "ToJSON" $ do+ Hspec.it "serializes all fields" $ do+ let discardedEvent =+ DiscardedEvent.DiscardedEvent+ { DiscardedEvent.reason = "sample_rate",+ DiscardedEvent.category = DataCategory.Error,+ DiscardedEvent.quantity = 3+ }+ Aeson.toJSON discardedEvent+ `Hspec.shouldBe` [Aeson.aesonQQ| {"reason": "sample_rate", "category": "error", "quantity": 3} |]++ Hspec.it "serializes quantity of 0" $ do+ let discardedEvent =+ DiscardedEvent.DiscardedEvent+ { DiscardedEvent.reason = "before_send",+ DiscardedEvent.category = DataCategory.Transaction,+ DiscardedEvent.quantity = 0+ }+ Aeson.toJSON discardedEvent+ `Hspec.shouldBe` [Aeson.aesonQQ| {"reason": "before_send", "category": "transaction", "quantity": 0} |]
source/test-suite/Patrol/Type/ItemSpec.hs view
@@ -5,6 +5,7 @@ import qualified Data.ByteString as ByteString import qualified Data.ByteString.Builder as Builder import qualified Data.ByteString.Lazy as LazyByteString+import qualified Patrol.Type.ClientReport as ClientReport import qualified Patrol.Type.Event as Event import qualified Patrol.Type.Item as Item import qualified Test.Hspec as Hspec@@ -19,4 +20,22 @@ . Item.serialize $ Item.Event Event.empty let expected = "{\"type\":\"event\",\"length\":47}\n{\"event_id\":\"00000000000000000000000000000000\"}" :: ByteString.ByteString+ actual `Hspec.shouldBe` expected++ Hspec.it "works on ClientReport" $ do+ let actual =+ LazyByteString.toStrict+ . Builder.toLazyByteString+ . Item.serialize+ $ Item.ClientReport ClientReport.empty+ let expected = "{\"type\":\"client_report\",\"length\":2}\n{}" :: ByteString.ByteString+ actual `Hspec.shouldBe` expected++ Hspec.it "works on Raw" $ do+ let actual =+ LazyByteString.toStrict+ . Builder.toLazyByteString+ . Item.serialize+ $ Item.Raw+ let expected = "" :: ByteString.ByteString actual `Hspec.shouldBe` expected
source/test-suite/PatrolSpec.hs view
@@ -11,12 +11,15 @@ import qualified Patrol.Type.BreadcrumbsSpec import qualified Patrol.Type.BrowserContextSpec import qualified Patrol.Type.CErrorSpec+import qualified Patrol.Type.ClientReportSpec import qualified Patrol.Type.ClientSdkInfoSpec import qualified Patrol.Type.ClientSdkPackageSpec import qualified Patrol.Type.ContextSpec+import qualified Patrol.Type.DataCategorySpec import qualified Patrol.Type.DebugImageSpec import qualified Patrol.Type.DebugMetaSpec import qualified Patrol.Type.DeviceContextSpec+import qualified Patrol.Type.DiscardedEventSpec import qualified Patrol.Type.DsnSpec import qualified Patrol.Type.EnvelopeSpec import qualified Patrol.Type.ErrorTypeSpec@@ -70,12 +73,15 @@ Patrol.Type.BreadcrumbTypeSpec.spec Patrol.Type.BrowserContextSpec.spec Patrol.Type.CErrorSpec.spec+ Patrol.Type.ClientReportSpec.spec Patrol.Type.ClientSdkInfoSpec.spec Patrol.Type.ClientSdkPackageSpec.spec Patrol.Type.ContextSpec.spec+ Patrol.Type.DataCategorySpec.spec Patrol.Type.DebugImageSpec.spec Patrol.Type.DebugMetaSpec.spec Patrol.Type.DeviceContextSpec.spec+ Patrol.Type.DiscardedEventSpec.spec Patrol.Type.DsnSpec.spec Patrol.Type.EnvelopeSpec.spec Patrol.Type.HeadersSpec.spec