packages feed

patrol 0.0.4 → 0.1.0.1

raw patch · 127 files changed

+5290/−622 lines, 127 filesdep +exceptionsdep +hspecdep +patroldep ~aesondep ~basedep ~bytestringPVP ok

version bump matches the API change (PVP)

Dependencies added: exceptions, hspec, patrol

Dependency ranges changed: aeson, base, bytestring, containers, http-client, network-uri, text, time, uuid

API changes (from Hackage documentation)

- Patrol: data StackTrace
- Patrol: data Timestamp
- Patrol.Type.Dsn: fromString :: String -> Either String Dsn
- Patrol.Type.Dsn: toString :: Dsn -> String
- Patrol.Type.Dsn: toUri :: Dsn -> URI
- Patrol.Type.EventId: data EventId
- Patrol.Type.EventId: toUuid :: EventId -> UUID
- Patrol.Type.Exception: [stackTrace] :: Exception -> Maybe StackTrace
- Patrol.Type.Response: [id_] :: Response -> EventId
- Patrol.Type.StackTrace: StackTrace :: NonEmpty Frame -> StackTrace
- Patrol.Type.StackTrace: [frames] :: StackTrace -> NonEmpty Frame
- Patrol.Type.StackTrace: fromCallStack :: CallStack -> Maybe StackTrace
- Patrol.Type.StackTrace: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.StackTrace.StackTrace
- Patrol.Type.StackTrace: instance GHC.Classes.Eq Patrol.Type.StackTrace.StackTrace
- Patrol.Type.StackTrace: instance GHC.Show.Show Patrol.Type.StackTrace.StackTrace
- Patrol.Type.StackTrace: newtype StackTrace
- Patrol.Type.Timestamp: data Timestamp
- Patrol.Type.Timestamp: fromUtcTime :: UTCTime -> Timestamp
- Patrol.Type.Timestamp: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.Timestamp.Timestamp
- Patrol.Type.Timestamp: instance GHC.Classes.Eq Patrol.Type.Timestamp.Timestamp
- Patrol.Type.Timestamp: instance GHC.Show.Show Patrol.Type.Timestamp.Timestamp
- Patrol.Type.Timestamp: toUtcTime :: Timestamp -> UTCTime
- Patrol.Type.User: [id_] :: User -> Maybe Text
- Patrol.Utility.Json: pair :: (ToJSON value, KeyValue pair) => String -> value -> pair
- Patrol.Utility.Json: required :: FromJSON value => Object -> String -> Parser value
- Patrol.Utility.Maybe: note :: e -> Maybe a -> Either e a
+ Patrol: data AppContext
+ Patrol: data AppleDebugImage
+ Patrol: data Breadcrumb
+ Patrol: data BreadcrumbType
+ Patrol: data Breadcrumbs
+ Patrol: data BrowserContext
+ Patrol: data CError
+ Patrol: data ClientSdkInfo
+ Patrol: data ClientSdkPackage
+ Patrol: data Context
+ Patrol: data DebugImage
+ Patrol: data DebugMeta
+ Patrol: data DeviceContext
+ Patrol: data ErrorType
+ Patrol: data EventProcessingError
+ Patrol: data EventType
+ Patrol: data Exceptions
+ Patrol: data Geo
+ Patrol: data GpuContext
+ Patrol: data LogEntry
+ Patrol: data MachException
+ Patrol: data Mechanism
+ Patrol: data MechanismMeta
+ Patrol: data NativeDebugImage
+ Patrol: data NsError
+ Patrol: data OsContext
+ Patrol: data PosixSignal
+ Patrol: data Problem
+ Patrol: data ProguardDebugImage
+ Patrol: data RuntimeContext
+ Patrol: data SpanStatus
+ Patrol: data Stacktrace
+ Patrol: data SystemSdkInfo
+ Patrol: data Thread
+ Patrol: data Threads
+ Patrol: data TraceContext
+ Patrol: data TransactionInfo
+ Patrol: data TransactionSource
+ Patrol.Constant: applicationJson :: ByteString
+ Patrol.Constant: sentryVersion :: Text
+ Patrol.Constant: userAgent :: Text
+ Patrol.Constant: xSentryAuth :: HeaderName
+ Patrol.Exception.Problem: Problem :: String -> Problem
+ Patrol.Exception.Problem: instance GHC.Classes.Eq Patrol.Exception.Problem.Problem
+ Patrol.Exception.Problem: instance GHC.Exception.Type.Exception Patrol.Exception.Problem.Problem
+ Patrol.Exception.Problem: instance GHC.Show.Show Patrol.Exception.Problem.Problem
+ Patrol.Exception.Problem: newtype Problem
+ Patrol.Extra.Aeson: intoObject :: [Pair] -> Value
+ Patrol.Extra.Aeson: isEmpty :: Value -> Bool
+ Patrol.Extra.Aeson: pair :: ToJSON a => String -> a -> Pair
+ Patrol.Extra.List: insert :: Eq k => (k, v) -> [(k, v)] -> [(k, v)]
+ Patrol.Extra.List: insertAll :: Eq k => [(k, v)] -> [(k, v)] -> [(k, v)]
+ Patrol.Extra.List: insertBy :: (a -> a -> Bool) -> a -> [a] -> [a]
+ Patrol.Type.AppContext: AppContext :: Text -> Text -> Maybe Int -> Text -> Maybe UTCTime -> Text -> Text -> Text -> AppContext
+ Patrol.Type.AppContext: [appBuild] :: AppContext -> Text
+ Patrol.Type.AppContext: [appIdentifier] :: AppContext -> Text
+ Patrol.Type.AppContext: [appMemory] :: AppContext -> Maybe Int
+ Patrol.Type.AppContext: [appName] :: AppContext -> Text
+ Patrol.Type.AppContext: [appStartTime] :: AppContext -> Maybe UTCTime
+ Patrol.Type.AppContext: [appVersion] :: AppContext -> Text
+ Patrol.Type.AppContext: [buildType] :: AppContext -> Text
+ Patrol.Type.AppContext: [deviceAppHash] :: AppContext -> Text
+ Patrol.Type.AppContext: data AppContext
+ Patrol.Type.AppContext: empty :: AppContext
+ Patrol.Type.AppContext: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.AppContext.AppContext
+ Patrol.Type.AppContext: instance GHC.Classes.Eq Patrol.Type.AppContext.AppContext
+ Patrol.Type.AppContext: instance GHC.Show.Show Patrol.Type.AppContext.AppContext
+ Patrol.Type.AppleDebugImage: AppleDebugImage :: Text -> Maybe Int -> Maybe Int -> Text -> Maybe Int -> Text -> Text -> Text -> AppleDebugImage
+ Patrol.Type.AppleDebugImage: [arch] :: AppleDebugImage -> Text
+ Patrol.Type.AppleDebugImage: [cpuSubtype] :: AppleDebugImage -> Maybe Int
+ Patrol.Type.AppleDebugImage: [cpuType] :: AppleDebugImage -> Maybe Int
+ Patrol.Type.AppleDebugImage: [imageAddr] :: AppleDebugImage -> Text
+ Patrol.Type.AppleDebugImage: [imageSize] :: AppleDebugImage -> Maybe Int
+ Patrol.Type.AppleDebugImage: [imageVmaddr] :: AppleDebugImage -> Text
+ Patrol.Type.AppleDebugImage: [name] :: AppleDebugImage -> Text
+ Patrol.Type.AppleDebugImage: [uuid] :: AppleDebugImage -> Text
+ Patrol.Type.AppleDebugImage: data AppleDebugImage
+ Patrol.Type.AppleDebugImage: empty :: AppleDebugImage
+ Patrol.Type.AppleDebugImage: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.AppleDebugImage.AppleDebugImage
+ Patrol.Type.AppleDebugImage: instance GHC.Classes.Eq Patrol.Type.AppleDebugImage.AppleDebugImage
+ Patrol.Type.AppleDebugImage: instance GHC.Show.Show Patrol.Type.AppleDebugImage.AppleDebugImage
+ Patrol.Type.Breadcrumb: Breadcrumb :: Text -> Map Text Value -> Maybe EventId -> Maybe Level -> Text -> Maybe UTCTime -> Maybe BreadcrumbType -> Breadcrumb
+ Patrol.Type.Breadcrumb: [category] :: Breadcrumb -> Text
+ Patrol.Type.Breadcrumb: [data_] :: Breadcrumb -> Map Text Value
+ Patrol.Type.Breadcrumb: [eventId] :: Breadcrumb -> Maybe EventId
+ Patrol.Type.Breadcrumb: [level] :: Breadcrumb -> Maybe Level
+ Patrol.Type.Breadcrumb: [message] :: Breadcrumb -> Text
+ Patrol.Type.Breadcrumb: [timestamp] :: Breadcrumb -> Maybe UTCTime
+ Patrol.Type.Breadcrumb: [type_] :: Breadcrumb -> Maybe BreadcrumbType
+ Patrol.Type.Breadcrumb: data Breadcrumb
+ Patrol.Type.Breadcrumb: empty :: Breadcrumb
+ Patrol.Type.Breadcrumb: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.Breadcrumb.Breadcrumb
+ Patrol.Type.Breadcrumb: instance GHC.Classes.Eq Patrol.Type.Breadcrumb.Breadcrumb
+ Patrol.Type.Breadcrumb: instance GHC.Show.Show Patrol.Type.Breadcrumb.Breadcrumb
+ Patrol.Type.BreadcrumbType: Default :: BreadcrumbType
+ Patrol.Type.BreadcrumbType: Http :: BreadcrumbType
+ Patrol.Type.BreadcrumbType: Navigation :: BreadcrumbType
+ Patrol.Type.BreadcrumbType: data BreadcrumbType
+ Patrol.Type.BreadcrumbType: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.BreadcrumbType.BreadcrumbType
+ Patrol.Type.BreadcrumbType: instance GHC.Classes.Eq Patrol.Type.BreadcrumbType.BreadcrumbType
+ Patrol.Type.BreadcrumbType: instance GHC.Show.Show Patrol.Type.BreadcrumbType.BreadcrumbType
+ Patrol.Type.Breadcrumbs: Breadcrumbs :: [Breadcrumb] -> Breadcrumbs
+ Patrol.Type.Breadcrumbs: [values] :: Breadcrumbs -> [Breadcrumb]
+ Patrol.Type.Breadcrumbs: empty :: Breadcrumbs
+ Patrol.Type.Breadcrumbs: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.Breadcrumbs.Breadcrumbs
+ Patrol.Type.Breadcrumbs: instance GHC.Classes.Eq Patrol.Type.Breadcrumbs.Breadcrumbs
+ Patrol.Type.Breadcrumbs: instance GHC.Show.Show Patrol.Type.Breadcrumbs.Breadcrumbs
+ Patrol.Type.Breadcrumbs: newtype Breadcrumbs
+ Patrol.Type.BrowserContext: BrowserContext :: Text -> Text -> BrowserContext
+ Patrol.Type.BrowserContext: [name] :: BrowserContext -> Text
+ Patrol.Type.BrowserContext: [version] :: BrowserContext -> Text
+ Patrol.Type.BrowserContext: data BrowserContext
+ Patrol.Type.BrowserContext: empty :: BrowserContext
+ Patrol.Type.BrowserContext: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.BrowserContext.BrowserContext
+ Patrol.Type.BrowserContext: instance GHC.Classes.Eq Patrol.Type.BrowserContext.BrowserContext
+ Patrol.Type.BrowserContext: instance GHC.Show.Show Patrol.Type.BrowserContext.BrowserContext
+ Patrol.Type.CError: CError :: Text -> Maybe Int -> CError
+ Patrol.Type.CError: [name] :: CError -> Text
+ Patrol.Type.CError: [number] :: CError -> Maybe Int
+ Patrol.Type.CError: data CError
+ Patrol.Type.CError: empty :: CError
+ Patrol.Type.CError: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.CError.CError
+ Patrol.Type.CError: instance GHC.Classes.Eq Patrol.Type.CError.CError
+ Patrol.Type.CError: instance GHC.Show.Show Patrol.Type.CError.CError
+ Patrol.Type.ClientSdkInfo: ClientSdkInfo :: [Text] -> Text -> [ClientSdkPackage] -> Text -> ClientSdkInfo
+ Patrol.Type.ClientSdkInfo: [integrations] :: ClientSdkInfo -> [Text]
+ Patrol.Type.ClientSdkInfo: [name] :: ClientSdkInfo -> Text
+ Patrol.Type.ClientSdkInfo: [packages] :: ClientSdkInfo -> [ClientSdkPackage]
+ Patrol.Type.ClientSdkInfo: [version] :: ClientSdkInfo -> Text
+ Patrol.Type.ClientSdkInfo: data ClientSdkInfo
+ Patrol.Type.ClientSdkInfo: empty :: ClientSdkInfo
+ Patrol.Type.ClientSdkInfo: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.ClientSdkInfo.ClientSdkInfo
+ Patrol.Type.ClientSdkInfo: instance GHC.Classes.Eq Patrol.Type.ClientSdkInfo.ClientSdkInfo
+ Patrol.Type.ClientSdkInfo: instance GHC.Show.Show Patrol.Type.ClientSdkInfo.ClientSdkInfo
+ Patrol.Type.ClientSdkPackage: ClientSdkPackage :: Text -> Text -> ClientSdkPackage
+ Patrol.Type.ClientSdkPackage: [name] :: ClientSdkPackage -> Text
+ Patrol.Type.ClientSdkPackage: [version] :: ClientSdkPackage -> Text
+ Patrol.Type.ClientSdkPackage: data ClientSdkPackage
+ Patrol.Type.ClientSdkPackage: empty :: ClientSdkPackage
+ Patrol.Type.ClientSdkPackage: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.ClientSdkPackage.ClientSdkPackage
+ Patrol.Type.ClientSdkPackage: instance GHC.Classes.Eq Patrol.Type.ClientSdkPackage.ClientSdkPackage
+ Patrol.Type.ClientSdkPackage: instance GHC.Show.Show Patrol.Type.ClientSdkPackage.ClientSdkPackage
+ Patrol.Type.Context: App :: AppContext -> Context
+ Patrol.Type.Context: Browser :: BrowserContext -> Context
+ Patrol.Type.Context: Device :: DeviceContext -> Context
+ Patrol.Type.Context: Gpu :: GpuContext -> Context
+ Patrol.Type.Context: Os :: OsContext -> Context
+ Patrol.Type.Context: Other :: Map Text Value -> Context
+ Patrol.Type.Context: Runtime :: RuntimeContext -> Context
+ Patrol.Type.Context: Trace :: TraceContext -> Context
+ Patrol.Type.Context: data Context
+ Patrol.Type.Context: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.Context.Context
+ Patrol.Type.Context: instance GHC.Classes.Eq Patrol.Type.Context.Context
+ Patrol.Type.Context: instance GHC.Show.Show Patrol.Type.Context.Context
+ Patrol.Type.DebugImage: Apple :: AppleDebugImage -> DebugImage
+ Patrol.Type.DebugImage: Native :: NativeDebugImage -> DebugImage
+ Patrol.Type.DebugImage: Other :: Map Text Value -> DebugImage
+ Patrol.Type.DebugImage: Proguard :: ProguardDebugImage -> DebugImage
+ Patrol.Type.DebugImage: data DebugImage
+ Patrol.Type.DebugImage: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.DebugImage.DebugImage
+ Patrol.Type.DebugImage: instance GHC.Classes.Eq Patrol.Type.DebugImage.DebugImage
+ Patrol.Type.DebugImage: instance GHC.Show.Show Patrol.Type.DebugImage.DebugImage
+ Patrol.Type.DebugMeta: DebugMeta :: [DebugImage] -> Maybe SystemSdkInfo -> DebugMeta
+ Patrol.Type.DebugMeta: [images] :: DebugMeta -> [DebugImage]
+ Patrol.Type.DebugMeta: [sdkInfo] :: DebugMeta -> Maybe SystemSdkInfo
+ Patrol.Type.DebugMeta: data DebugMeta
+ Patrol.Type.DebugMeta: empty :: DebugMeta
+ Patrol.Type.DebugMeta: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.DebugMeta.DebugMeta
+ Patrol.Type.DebugMeta: instance GHC.Classes.Eq Patrol.Type.DebugMeta.DebugMeta
+ Patrol.Type.DebugMeta: instance GHC.Show.Show Patrol.Type.DebugMeta.DebugMeta
+ Patrol.Type.DeviceContext: DeviceContext :: Text -> Maybe Double -> Text -> Maybe UTCTime -> Text -> Maybe Bool -> Text -> Text -> Text -> Maybe Int -> Maybe Int -> Text -> Maybe Int -> Maybe Int -> Maybe Bool -> Text -> Maybe Int -> Text -> Text -> Text -> Maybe Bool -> Text -> Maybe Int -> Maybe Double -> Maybe Double -> Maybe Double -> Text -> Maybe Bool -> Maybe Int -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Text -> Maybe Int -> DeviceContext
+ Patrol.Type.DeviceContext: [arch] :: DeviceContext -> Text
+ Patrol.Type.DeviceContext: [batteryLevel] :: DeviceContext -> Maybe Double
+ Patrol.Type.DeviceContext: [batteryStatus] :: DeviceContext -> Text
+ Patrol.Type.DeviceContext: [bootTime] :: DeviceContext -> Maybe UTCTime
+ Patrol.Type.DeviceContext: [brand] :: DeviceContext -> Text
+ Patrol.Type.DeviceContext: [charging] :: DeviceContext -> Maybe Bool
+ Patrol.Type.DeviceContext: [cpuDescription] :: DeviceContext -> Text
+ Patrol.Type.DeviceContext: [deviceType] :: DeviceContext -> Text
+ Patrol.Type.DeviceContext: [deviceUniqueIdentifier] :: DeviceContext -> Text
+ Patrol.Type.DeviceContext: [externalFreeStorage] :: DeviceContext -> Maybe Int
+ Patrol.Type.DeviceContext: [externalStorageSize] :: DeviceContext -> Maybe Int
+ Patrol.Type.DeviceContext: [family] :: DeviceContext -> Text
+ Patrol.Type.DeviceContext: [freeMemory] :: DeviceContext -> Maybe Int
+ Patrol.Type.DeviceContext: [freeStorage] :: DeviceContext -> Maybe Int
+ Patrol.Type.DeviceContext: [lowMemory] :: DeviceContext -> Maybe Bool
+ Patrol.Type.DeviceContext: [manufacturer] :: DeviceContext -> Text
+ Patrol.Type.DeviceContext: [memorySize] :: DeviceContext -> Maybe Int
+ Patrol.Type.DeviceContext: [modelId] :: DeviceContext -> Text
+ Patrol.Type.DeviceContext: [model] :: DeviceContext -> Text
+ Patrol.Type.DeviceContext: [name] :: DeviceContext -> Text
+ Patrol.Type.DeviceContext: [online] :: DeviceContext -> Maybe Bool
+ Patrol.Type.DeviceContext: [orientation] :: DeviceContext -> Text
+ Patrol.Type.DeviceContext: [processorCount] :: DeviceContext -> Maybe Int
+ Patrol.Type.DeviceContext: [processorFrequency] :: DeviceContext -> Maybe Double
+ Patrol.Type.DeviceContext: [screenDensity] :: DeviceContext -> Maybe Double
+ Patrol.Type.DeviceContext: [screenDpi] :: DeviceContext -> Maybe Double
+ Patrol.Type.DeviceContext: [screenResolution] :: DeviceContext -> Text
+ Patrol.Type.DeviceContext: [simulator] :: DeviceContext -> Maybe Bool
+ Patrol.Type.DeviceContext: [storageSize] :: DeviceContext -> Maybe Int
+ Patrol.Type.DeviceContext: [supportsAccelerometer] :: DeviceContext -> Maybe Bool
+ Patrol.Type.DeviceContext: [supportsAudio] :: DeviceContext -> Maybe Bool
+ Patrol.Type.DeviceContext: [supportsGyroscope] :: DeviceContext -> Maybe Bool
+ Patrol.Type.DeviceContext: [supportsLocationService] :: DeviceContext -> Maybe Bool
+ Patrol.Type.DeviceContext: [supportsVibration] :: DeviceContext -> Maybe Bool
+ Patrol.Type.DeviceContext: [timezone] :: DeviceContext -> Text
+ Patrol.Type.DeviceContext: [usableMemory] :: DeviceContext -> Maybe Int
+ Patrol.Type.DeviceContext: data DeviceContext
+ Patrol.Type.DeviceContext: empty :: DeviceContext
+ Patrol.Type.DeviceContext: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.DeviceContext.DeviceContext
+ Patrol.Type.DeviceContext: instance GHC.Classes.Eq Patrol.Type.DeviceContext.DeviceContext
+ Patrol.Type.DeviceContext: instance GHC.Show.Show Patrol.Type.DeviceContext.DeviceContext
+ Patrol.Type.Dsn: intoAuthorization :: Dsn -> ByteString
+ Patrol.Type.Dsn: intoUri :: Dsn -> URI
+ Patrol.Type.ErrorType: ClockDrift :: ErrorType
+ Patrol.Type.ErrorType: FetchGenericError :: ErrorType
+ Patrol.Type.ErrorType: FetchInvalidEncoding :: ErrorType
+ Patrol.Type.ErrorType: FetchInvalidHttpCode :: ErrorType
+ Patrol.Type.ErrorType: FetchTimeout :: ErrorType
+ Patrol.Type.ErrorType: FetchTooLarge :: ErrorType
+ Patrol.Type.ErrorType: FutureTimestamp :: ErrorType
+ Patrol.Type.ErrorType: InvalidAttribute :: ErrorType
+ Patrol.Type.ErrorType: InvalidData :: ErrorType
+ Patrol.Type.ErrorType: InvalidEnvironment :: ErrorType
+ Patrol.Type.ErrorType: JsFetchTimeout :: ErrorType
+ Patrol.Type.ErrorType: JsGenericFetchError :: ErrorType
+ Patrol.Type.ErrorType: JsInvalidContent :: ErrorType
+ Patrol.Type.ErrorType: JsInvalidHttpCode :: ErrorType
+ Patrol.Type.ErrorType: JsInvalidSourceEncoding :: ErrorType
+ Patrol.Type.ErrorType: JsInvalidSourcemap :: ErrorType
+ Patrol.Type.ErrorType: JsInvalidSourcemapLocation :: ErrorType
+ Patrol.Type.ErrorType: JsMissingSource :: ErrorType
+ Patrol.Type.ErrorType: JsNoColumn :: ErrorType
+ Patrol.Type.ErrorType: JsTooLarge :: ErrorType
+ Patrol.Type.ErrorType: JsTooManyRemoteSources :: ErrorType
+ Patrol.Type.ErrorType: MissingAttribute :: ErrorType
+ Patrol.Type.ErrorType: NativeBadDsym :: ErrorType
+ Patrol.Type.ErrorType: NativeInternalFailure :: ErrorType
+ Patrol.Type.ErrorType: NativeMissingDsym :: ErrorType
+ Patrol.Type.ErrorType: NativeMissingOptionallyBundledDsym :: ErrorType
+ Patrol.Type.ErrorType: NativeMissingSymbol :: ErrorType
+ Patrol.Type.ErrorType: NativeMissingSystemDsym :: ErrorType
+ Patrol.Type.ErrorType: NativeNoCrashedThread :: ErrorType
+ Patrol.Type.ErrorType: NativeSimulatorFrame :: ErrorType
+ Patrol.Type.ErrorType: NativeSymbolicatorFailed :: ErrorType
+ Patrol.Type.ErrorType: NativeUnknownImage :: ErrorType
+ Patrol.Type.ErrorType: PastTimestamp :: ErrorType
+ Patrol.Type.ErrorType: ProguardMissingLineno :: ErrorType
+ Patrol.Type.ErrorType: ProguardMissingMapping :: ErrorType
+ Patrol.Type.ErrorType: RestrictedIp :: ErrorType
+ Patrol.Type.ErrorType: SecurityViolation :: ErrorType
+ Patrol.Type.ErrorType: TooLargeForCache :: ErrorType
+ Patrol.Type.ErrorType: UnknownError :: ErrorType
+ Patrol.Type.ErrorType: ValueTooLong :: ErrorType
+ Patrol.Type.ErrorType: data ErrorType
+ Patrol.Type.ErrorType: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.ErrorType.ErrorType
+ Patrol.Type.ErrorType: instance GHC.Classes.Eq Patrol.Type.ErrorType.ErrorType
+ Patrol.Type.ErrorType: instance GHC.Show.Show Patrol.Type.ErrorType.ErrorType
+ Patrol.Type.Event: [breadcrumbs] :: Event -> Maybe Breadcrumbs
+ Patrol.Type.Event: [contexts] :: Event -> Map Text Context
+ Patrol.Type.Event: [debugMeta] :: Event -> Maybe DebugMeta
+ Patrol.Type.Event: [errors] :: Event -> [EventProcessingError]
+ Patrol.Type.Event: [logentry] :: Event -> Maybe LogEntry
+ Patrol.Type.Event: [sdk] :: Event -> Maybe ClientSdkInfo
+ Patrol.Type.Event: [threads] :: Event -> Maybe Threads
+ Patrol.Type.Event: [timeSpent] :: Event -> Maybe NominalDiffTime
+ Patrol.Type.Event: [transactionInfo] :: Event -> Maybe TransactionInfo
+ Patrol.Type.Event: [type_] :: Event -> Maybe EventType
+ Patrol.Type.Event: [version] :: Event -> Text
+ Patrol.Type.Event: empty :: Event
+ Patrol.Type.Event: intoRequest :: MonadThrow m => Dsn -> Event -> m Request
+ Patrol.Type.EventId: EventId :: UUID -> EventId
+ Patrol.Type.EventId: empty :: EventId
+ Patrol.Type.EventId: fromText :: MonadThrow m => Text -> m EventId
+ Patrol.Type.EventId: intoText :: EventId -> Text
+ Patrol.Type.EventId: intoUuid :: EventId -> UUID
+ Patrol.Type.EventId: newtype EventId
+ Patrol.Type.EventId: random :: MonadIO io => io EventId
+ Patrol.Type.EventProcessingError: EventProcessingError :: Text -> ErrorType -> Value -> EventProcessingError
+ Patrol.Type.EventProcessingError: [name] :: EventProcessingError -> Text
+ Patrol.Type.EventProcessingError: [type_] :: EventProcessingError -> ErrorType
+ Patrol.Type.EventProcessingError: [value] :: EventProcessingError -> Value
+ Patrol.Type.EventProcessingError: data EventProcessingError
+ Patrol.Type.EventProcessingError: empty :: EventProcessingError
+ Patrol.Type.EventProcessingError: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.EventProcessingError.EventProcessingError
+ Patrol.Type.EventProcessingError: instance GHC.Classes.Eq Patrol.Type.EventProcessingError.EventProcessingError
+ Patrol.Type.EventProcessingError: instance GHC.Show.Show Patrol.Type.EventProcessingError.EventProcessingError
+ Patrol.Type.EventType: Csp :: EventType
+ Patrol.Type.EventType: Default :: EventType
+ Patrol.Type.EventType: Error :: EventType
+ Patrol.Type.EventType: Expectct :: EventType
+ Patrol.Type.EventType: Expectstaple :: EventType
+ Patrol.Type.EventType: Hpkp :: EventType
+ Patrol.Type.EventType: Transaction :: EventType
+ Patrol.Type.EventType: data EventType
+ Patrol.Type.EventType: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.EventType.EventType
+ Patrol.Type.EventType: instance GHC.Classes.Eq Patrol.Type.EventType.EventType
+ Patrol.Type.EventType: instance GHC.Show.Show Patrol.Type.EventType.EventType
+ Patrol.Type.Exception: [mechanism] :: Exception -> Maybe Mechanism
+ Patrol.Type.Exception: [stacktrace] :: Exception -> Maybe Stacktrace
+ Patrol.Type.Exception: [threadId] :: Exception -> Text
+ Patrol.Type.Exception: empty :: Exception
+ Patrol.Type.Exceptions: Exceptions :: [Exception] -> Exceptions
+ Patrol.Type.Exceptions: [values] :: Exceptions -> [Exception]
+ Patrol.Type.Exceptions: empty :: Exceptions
+ Patrol.Type.Exceptions: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.Exceptions.Exceptions
+ Patrol.Type.Exceptions: instance GHC.Classes.Eq Patrol.Type.Exceptions.Exceptions
+ Patrol.Type.Exceptions: instance GHC.Show.Show Patrol.Type.Exceptions.Exceptions
+ Patrol.Type.Exceptions: newtype Exceptions
+ Patrol.Type.Frame: [absPath] :: Frame -> Text
+ Patrol.Type.Frame: [addrMode] :: Frame -> Text
+ Patrol.Type.Frame: [contextLine] :: Frame -> Text
+ Patrol.Type.Frame: [imageAddr] :: Frame -> Text
+ Patrol.Type.Frame: [inApp] :: Frame -> Maybe Bool
+ Patrol.Type.Frame: [instructionAddr] :: Frame -> Text
+ Patrol.Type.Frame: [platform] :: Frame -> Maybe Platform
+ Patrol.Type.Frame: [postContext] :: Frame -> [Text]
+ Patrol.Type.Frame: [preContext] :: Frame -> [Text]
+ Patrol.Type.Frame: [rawFunction] :: Frame -> Text
+ Patrol.Type.Frame: [stackStart] :: Frame -> Maybe Bool
+ Patrol.Type.Frame: [symbolAddr] :: Frame -> Text
+ Patrol.Type.Frame: [vars] :: Frame -> Map Text Value
+ Patrol.Type.Frame: empty :: Frame
+ Patrol.Type.Geo: Geo :: Text -> Text -> Text -> Geo
+ Patrol.Type.Geo: [city] :: Geo -> Text
+ Patrol.Type.Geo: [countryCode] :: Geo -> Text
+ Patrol.Type.Geo: [region] :: Geo -> Text
+ Patrol.Type.Geo: data Geo
+ Patrol.Type.Geo: empty :: Geo
+ Patrol.Type.Geo: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.Geo.Geo
+ Patrol.Type.Geo: instance GHC.Classes.Eq Patrol.Type.Geo.Geo
+ Patrol.Type.Geo: instance GHC.Show.Show Patrol.Type.Geo.Geo
+ Patrol.Type.GpuContext: GpuContext :: Text -> Text -> Value -> Maybe Int -> Maybe Double -> Maybe Bool -> Text -> Text -> Maybe Bool -> Maybe Bool -> Maybe Bool -> Text -> Text -> Text -> GpuContext
+ Patrol.Type.GpuContext: [apiType] :: GpuContext -> Text
+ Patrol.Type.GpuContext: [graphicsShaderLevel] :: GpuContext -> Text
+ Patrol.Type.GpuContext: [id] :: GpuContext -> Value
+ Patrol.Type.GpuContext: [maxTextureSize] :: GpuContext -> Maybe Int
+ Patrol.Type.GpuContext: [memorySize] :: GpuContext -> Maybe Double
+ Patrol.Type.GpuContext: [multiThreadedRendering] :: GpuContext -> Maybe Bool
+ Patrol.Type.GpuContext: [name] :: GpuContext -> Text
+ Patrol.Type.GpuContext: [npotSupport] :: GpuContext -> Text
+ Patrol.Type.GpuContext: [supportsComputeShaders] :: GpuContext -> Maybe Bool
+ Patrol.Type.GpuContext: [supportsGeometryShaders] :: GpuContext -> Maybe Bool
+ Patrol.Type.GpuContext: [supportsRayTracing] :: GpuContext -> Maybe Bool
+ Patrol.Type.GpuContext: [vendorId] :: GpuContext -> Text
+ Patrol.Type.GpuContext: [vendorName] :: GpuContext -> Text
+ Patrol.Type.GpuContext: [version] :: GpuContext -> Text
+ Patrol.Type.GpuContext: data GpuContext
+ Patrol.Type.GpuContext: empty :: GpuContext
+ Patrol.Type.GpuContext: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.GpuContext.GpuContext
+ Patrol.Type.GpuContext: instance GHC.Classes.Eq Patrol.Type.GpuContext.GpuContext
+ Patrol.Type.GpuContext: instance GHC.Show.Show Patrol.Type.GpuContext.GpuContext
+ Patrol.Type.LogEntry: LogEntry :: Text -> Text -> Value -> LogEntry
+ Patrol.Type.LogEntry: [formatted] :: LogEntry -> Text
+ Patrol.Type.LogEntry: [message] :: LogEntry -> Text
+ Patrol.Type.LogEntry: [params] :: LogEntry -> Value
+ Patrol.Type.LogEntry: data LogEntry
+ Patrol.Type.LogEntry: empty :: LogEntry
+ Patrol.Type.LogEntry: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.LogEntry.LogEntry
+ Patrol.Type.LogEntry: instance GHC.Classes.Eq Patrol.Type.LogEntry.LogEntry
+ Patrol.Type.LogEntry: instance GHC.Show.Show Patrol.Type.LogEntry.LogEntry
+ Patrol.Type.MachException: MachException :: Maybe Int -> Maybe Int -> Text -> Maybe Int -> MachException
+ Patrol.Type.MachException: [code] :: MachException -> Maybe Int
+ Patrol.Type.MachException: [exception] :: MachException -> Maybe Int
+ Patrol.Type.MachException: [name] :: MachException -> Text
+ Patrol.Type.MachException: [subcode] :: MachException -> Maybe Int
+ Patrol.Type.MachException: data MachException
+ Patrol.Type.MachException: empty :: MachException
+ Patrol.Type.MachException: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.MachException.MachException
+ Patrol.Type.MachException: instance GHC.Classes.Eq Patrol.Type.MachException.MachException
+ Patrol.Type.MachException: instance GHC.Show.Show Patrol.Type.MachException.MachException
+ Patrol.Type.Mechanism: Mechanism :: Map Text Value -> Text -> Maybe Bool -> Text -> Maybe MechanismMeta -> Maybe Bool -> Text -> Mechanism
+ Patrol.Type.Mechanism: [data_] :: Mechanism -> Map Text Value
+ Patrol.Type.Mechanism: [description] :: Mechanism -> Text
+ Patrol.Type.Mechanism: [handled] :: Mechanism -> Maybe Bool
+ Patrol.Type.Mechanism: [helpLink] :: Mechanism -> Text
+ Patrol.Type.Mechanism: [meta] :: Mechanism -> Maybe MechanismMeta
+ Patrol.Type.Mechanism: [synthetic] :: Mechanism -> Maybe Bool
+ Patrol.Type.Mechanism: [type_] :: Mechanism -> Text
+ Patrol.Type.Mechanism: data Mechanism
+ Patrol.Type.Mechanism: empty :: Mechanism
+ Patrol.Type.Mechanism: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.Mechanism.Mechanism
+ Patrol.Type.Mechanism: instance GHC.Classes.Eq Patrol.Type.Mechanism.Mechanism
+ Patrol.Type.Mechanism: instance GHC.Show.Show Patrol.Type.Mechanism.Mechanism
+ Patrol.Type.MechanismMeta: MechanismMeta :: Maybe CError -> Maybe MachException -> Maybe NsError -> Maybe PosixSignal -> MechanismMeta
+ Patrol.Type.MechanismMeta: [errno] :: MechanismMeta -> Maybe CError
+ Patrol.Type.MechanismMeta: [machException] :: MechanismMeta -> Maybe MachException
+ Patrol.Type.MechanismMeta: [nsError] :: MechanismMeta -> Maybe NsError
+ Patrol.Type.MechanismMeta: [signal] :: MechanismMeta -> Maybe PosixSignal
+ Patrol.Type.MechanismMeta: data MechanismMeta
+ Patrol.Type.MechanismMeta: empty :: MechanismMeta
+ Patrol.Type.MechanismMeta: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.MechanismMeta.MechanismMeta
+ Patrol.Type.MechanismMeta: instance GHC.Classes.Eq Patrol.Type.MechanismMeta.MechanismMeta
+ Patrol.Type.MechanismMeta: instance GHC.Show.Show Patrol.Type.MechanismMeta.MechanismMeta
+ Patrol.Type.NativeDebugImage: NativeDebugImage :: Text -> Text -> Text -> Text -> Text -> Text -> Maybe Int -> Text -> Text -> NativeDebugImage
+ Patrol.Type.NativeDebugImage: [arch] :: NativeDebugImage -> Text
+ Patrol.Type.NativeDebugImage: [codeFile] :: NativeDebugImage -> Text
+ Patrol.Type.NativeDebugImage: [codeId] :: NativeDebugImage -> Text
+ Patrol.Type.NativeDebugImage: [debugFile] :: NativeDebugImage -> Text
+ Patrol.Type.NativeDebugImage: [debugId] :: NativeDebugImage -> Text
+ Patrol.Type.NativeDebugImage: [imageAddr] :: NativeDebugImage -> Text
+ Patrol.Type.NativeDebugImage: [imageSize] :: NativeDebugImage -> Maybe Int
+ Patrol.Type.NativeDebugImage: [imageVmaddr] :: NativeDebugImage -> Text
+ Patrol.Type.NativeDebugImage: [type_] :: NativeDebugImage -> Text
+ Patrol.Type.NativeDebugImage: data NativeDebugImage
+ Patrol.Type.NativeDebugImage: empty :: NativeDebugImage
+ Patrol.Type.NativeDebugImage: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.NativeDebugImage.NativeDebugImage
+ Patrol.Type.NativeDebugImage: instance GHC.Classes.Eq Patrol.Type.NativeDebugImage.NativeDebugImage
+ Patrol.Type.NativeDebugImage: instance GHC.Show.Show Patrol.Type.NativeDebugImage.NativeDebugImage
+ Patrol.Type.NsError: NsError :: Maybe Int -> Text -> NsError
+ Patrol.Type.NsError: [code] :: NsError -> Maybe Int
+ Patrol.Type.NsError: [domain] :: NsError -> Text
+ Patrol.Type.NsError: data NsError
+ Patrol.Type.NsError: empty :: NsError
+ Patrol.Type.NsError: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.NsError.NsError
+ Patrol.Type.NsError: instance GHC.Classes.Eq Patrol.Type.NsError.NsError
+ Patrol.Type.NsError: instance GHC.Show.Show Patrol.Type.NsError.NsError
+ Patrol.Type.OsContext: OsContext :: Text -> Text -> Text -> Text -> Maybe Bool -> Text -> OsContext
+ Patrol.Type.OsContext: [build] :: OsContext -> Text
+ Patrol.Type.OsContext: [kernelVersion] :: OsContext -> Text
+ Patrol.Type.OsContext: [name] :: OsContext -> Text
+ Patrol.Type.OsContext: [rawDescription] :: OsContext -> Text
+ Patrol.Type.OsContext: [rooted] :: OsContext -> Maybe Bool
+ Patrol.Type.OsContext: [version] :: OsContext -> Text
+ Patrol.Type.OsContext: data OsContext
+ Patrol.Type.OsContext: empty :: OsContext
+ Patrol.Type.OsContext: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.OsContext.OsContext
+ Patrol.Type.OsContext: instance GHC.Classes.Eq Patrol.Type.OsContext.OsContext
+ Patrol.Type.OsContext: instance GHC.Show.Show Patrol.Type.OsContext.OsContext
+ Patrol.Type.PosixSignal: PosixSignal :: Maybe Int -> Text -> Text -> Maybe Int -> PosixSignal
+ Patrol.Type.PosixSignal: [codeName] :: PosixSignal -> Text
+ Patrol.Type.PosixSignal: [code] :: PosixSignal -> Maybe Int
+ Patrol.Type.PosixSignal: [name] :: PosixSignal -> Text
+ Patrol.Type.PosixSignal: [number] :: PosixSignal -> Maybe Int
+ Patrol.Type.PosixSignal: data PosixSignal
+ Patrol.Type.PosixSignal: empty :: PosixSignal
+ Patrol.Type.PosixSignal: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.PosixSignal.PosixSignal
+ Patrol.Type.PosixSignal: instance GHC.Classes.Eq Patrol.Type.PosixSignal.PosixSignal
+ Patrol.Type.PosixSignal: instance GHC.Show.Show Patrol.Type.PosixSignal.PosixSignal
+ Patrol.Type.ProguardDebugImage: ProguardDebugImage :: Text -> ProguardDebugImage
+ Patrol.Type.ProguardDebugImage: [uuid] :: ProguardDebugImage -> Text
+ Patrol.Type.ProguardDebugImage: empty :: ProguardDebugImage
+ Patrol.Type.ProguardDebugImage: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.ProguardDebugImage.ProguardDebugImage
+ Patrol.Type.ProguardDebugImage: instance GHC.Classes.Eq Patrol.Type.ProguardDebugImage.ProguardDebugImage
+ Patrol.Type.ProguardDebugImage: instance GHC.Show.Show Patrol.Type.ProguardDebugImage.ProguardDebugImage
+ Patrol.Type.ProguardDebugImage: newtype ProguardDebugImage
+ Patrol.Type.Request: [fragment] :: Request -> Text
+ Patrol.Type.Request: [inferredContentType] :: Request -> Text
+ Patrol.Type.Request: empty :: Request
+ Patrol.Type.Response: [id] :: Response -> EventId
+ Patrol.Type.RuntimeContext: RuntimeContext :: Text -> Text -> Text -> Text -> RuntimeContext
+ Patrol.Type.RuntimeContext: [build] :: RuntimeContext -> Text
+ Patrol.Type.RuntimeContext: [name] :: RuntimeContext -> Text
+ Patrol.Type.RuntimeContext: [rawDescription] :: RuntimeContext -> Text
+ Patrol.Type.RuntimeContext: [version] :: RuntimeContext -> Text
+ Patrol.Type.RuntimeContext: data RuntimeContext
+ Patrol.Type.RuntimeContext: empty :: RuntimeContext
+ Patrol.Type.RuntimeContext: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.RuntimeContext.RuntimeContext
+ Patrol.Type.RuntimeContext: instance GHC.Classes.Eq Patrol.Type.RuntimeContext.RuntimeContext
+ Patrol.Type.RuntimeContext: instance GHC.Show.Show Patrol.Type.RuntimeContext.RuntimeContext
+ Patrol.Type.SpanStatus: Aborted :: SpanStatus
+ Patrol.Type.SpanStatus: AlreadyExists :: SpanStatus
+ Patrol.Type.SpanStatus: Cancelled :: SpanStatus
+ Patrol.Type.SpanStatus: DataLoss :: SpanStatus
+ Patrol.Type.SpanStatus: DeadlineExceeded :: SpanStatus
+ Patrol.Type.SpanStatus: FailedPrecondition :: SpanStatus
+ Patrol.Type.SpanStatus: InternalError :: SpanStatus
+ Patrol.Type.SpanStatus: InvalidArgument :: SpanStatus
+ Patrol.Type.SpanStatus: NotFound :: SpanStatus
+ Patrol.Type.SpanStatus: Ok :: SpanStatus
+ Patrol.Type.SpanStatus: OutOfRange :: SpanStatus
+ Patrol.Type.SpanStatus: PermissionDenied :: SpanStatus
+ Patrol.Type.SpanStatus: ResourceExhausted :: SpanStatus
+ Patrol.Type.SpanStatus: Unauthenticated :: SpanStatus
+ Patrol.Type.SpanStatus: Unavailable :: SpanStatus
+ Patrol.Type.SpanStatus: Unimplemented :: SpanStatus
+ Patrol.Type.SpanStatus: Unknown :: SpanStatus
+ Patrol.Type.SpanStatus: data SpanStatus
+ Patrol.Type.SpanStatus: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.SpanStatus.SpanStatus
+ Patrol.Type.SpanStatus: instance GHC.Classes.Eq Patrol.Type.SpanStatus.SpanStatus
+ Patrol.Type.SpanStatus: instance GHC.Show.Show Patrol.Type.SpanStatus.SpanStatus
+ Patrol.Type.Stacktrace: Stacktrace :: [Frame] -> Map Text Text -> Stacktrace
+ Patrol.Type.Stacktrace: [frames] :: Stacktrace -> [Frame]
+ Patrol.Type.Stacktrace: [registers] :: Stacktrace -> Map Text Text
+ Patrol.Type.Stacktrace: data Stacktrace
+ Patrol.Type.Stacktrace: empty :: Stacktrace
+ Patrol.Type.Stacktrace: fromCallStack :: CallStack -> Stacktrace
+ Patrol.Type.Stacktrace: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.Stacktrace.Stacktrace
+ Patrol.Type.Stacktrace: instance GHC.Classes.Eq Patrol.Type.Stacktrace.Stacktrace
+ Patrol.Type.Stacktrace: instance GHC.Show.Show Patrol.Type.Stacktrace.Stacktrace
+ Patrol.Type.SystemSdkInfo: SystemSdkInfo :: Text -> Maybe Int -> Maybe Int -> Maybe Int -> SystemSdkInfo
+ Patrol.Type.SystemSdkInfo: [sdkName] :: SystemSdkInfo -> Text
+ Patrol.Type.SystemSdkInfo: [versionMajor] :: SystemSdkInfo -> Maybe Int
+ Patrol.Type.SystemSdkInfo: [versionMinor] :: SystemSdkInfo -> Maybe Int
+ Patrol.Type.SystemSdkInfo: [versionPatchlevel] :: SystemSdkInfo -> Maybe Int
+ Patrol.Type.SystemSdkInfo: data SystemSdkInfo
+ Patrol.Type.SystemSdkInfo: empty :: SystemSdkInfo
+ Patrol.Type.SystemSdkInfo: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.SystemSdkInfo.SystemSdkInfo
+ Patrol.Type.SystemSdkInfo: instance GHC.Classes.Eq Patrol.Type.SystemSdkInfo.SystemSdkInfo
+ Patrol.Type.SystemSdkInfo: instance GHC.Show.Show Patrol.Type.SystemSdkInfo.SystemSdkInfo
+ Patrol.Type.Thread: Thread :: Maybe Bool -> Maybe Bool -> Text -> Text -> Maybe Stacktrace -> Thread
+ Patrol.Type.Thread: [crashed] :: Thread -> Maybe Bool
+ Patrol.Type.Thread: [current] :: Thread -> Maybe Bool
+ Patrol.Type.Thread: [id] :: Thread -> Text
+ Patrol.Type.Thread: [name] :: Thread -> Text
+ Patrol.Type.Thread: [stacktrace] :: Thread -> Maybe Stacktrace
+ Patrol.Type.Thread: data Thread
+ Patrol.Type.Thread: empty :: Thread
+ Patrol.Type.Thread: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.Thread.Thread
+ Patrol.Type.Thread: instance GHC.Classes.Eq Patrol.Type.Thread.Thread
+ Patrol.Type.Thread: instance GHC.Show.Show Patrol.Type.Thread.Thread
+ Patrol.Type.Threads: Threads :: [Thread] -> Threads
+ Patrol.Type.Threads: [values] :: Threads -> [Thread]
+ Patrol.Type.Threads: empty :: Threads
+ Patrol.Type.Threads: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.Threads.Threads
+ Patrol.Type.Threads: instance GHC.Classes.Eq Patrol.Type.Threads.Threads
+ Patrol.Type.Threads: instance GHC.Show.Show Patrol.Type.Threads.Threads
+ Patrol.Type.Threads: newtype Threads
+ Patrol.Type.TraceContext: TraceContext :: Maybe Int -> Text -> Text -> Text -> Maybe SpanStatus -> Text -> TraceContext
+ Patrol.Type.TraceContext: [exclusiveTime] :: TraceContext -> Maybe Int
+ Patrol.Type.TraceContext: [op] :: TraceContext -> Text
+ Patrol.Type.TraceContext: [parentSpanId] :: TraceContext -> Text
+ Patrol.Type.TraceContext: [spanId] :: TraceContext -> Text
+ Patrol.Type.TraceContext: [status] :: TraceContext -> Maybe SpanStatus
+ Patrol.Type.TraceContext: [traceId] :: TraceContext -> Text
+ Patrol.Type.TraceContext: data TraceContext
+ Patrol.Type.TraceContext: empty :: TraceContext
+ Patrol.Type.TraceContext: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.TraceContext.TraceContext
+ Patrol.Type.TraceContext: instance GHC.Classes.Eq Patrol.Type.TraceContext.TraceContext
+ Patrol.Type.TraceContext: instance GHC.Show.Show Patrol.Type.TraceContext.TraceContext
+ Patrol.Type.TransactionInfo: TransactionInfo :: Text -> Maybe TransactionSource -> TransactionInfo
+ Patrol.Type.TransactionInfo: [original] :: TransactionInfo -> Text
+ Patrol.Type.TransactionInfo: [source] :: TransactionInfo -> Maybe TransactionSource
+ Patrol.Type.TransactionInfo: data TransactionInfo
+ Patrol.Type.TransactionInfo: empty :: TransactionInfo
+ Patrol.Type.TransactionInfo: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.TransactionInfo.TransactionInfo
+ Patrol.Type.TransactionInfo: instance GHC.Classes.Eq Patrol.Type.TransactionInfo.TransactionInfo
+ Patrol.Type.TransactionInfo: instance GHC.Show.Show Patrol.Type.TransactionInfo.TransactionInfo
+ Patrol.Type.TransactionSource: Component :: TransactionSource
+ Patrol.Type.TransactionSource: Custom :: TransactionSource
+ Patrol.Type.TransactionSource: Route :: TransactionSource
+ Patrol.Type.TransactionSource: Task :: TransactionSource
+ Patrol.Type.TransactionSource: Unknown :: TransactionSource
+ Patrol.Type.TransactionSource: Url :: TransactionSource
+ Patrol.Type.TransactionSource: View :: TransactionSource
+ Patrol.Type.TransactionSource: data TransactionSource
+ Patrol.Type.TransactionSource: instance Data.Aeson.Types.ToJSON.ToJSON Patrol.Type.TransactionSource.TransactionSource
+ Patrol.Type.TransactionSource: instance GHC.Classes.Eq Patrol.Type.TransactionSource.TransactionSource
+ Patrol.Type.TransactionSource: instance GHC.Show.Show Patrol.Type.TransactionSource.TransactionSource
+ Patrol.Type.User: [data_] :: User -> Map Text Value
+ Patrol.Type.User: [geo] :: User -> Maybe Geo
+ Patrol.Type.User: [id] :: User -> Text
+ Patrol.Type.User: [name] :: User -> Text
+ Patrol.Type.User: [segment] :: User -> Text
+ Patrol.Type.User: empty :: User
+ Patrol.Version: string :: String
+ Patrol.Version: text :: Text
+ Patrol.Version: version :: Version
- Patrol.Client: store :: Manager -> Dsn -> Event -> IO EventId
+ Patrol.Client: store :: (MonadIO io, MonadThrow io) => Manager -> Dsn -> Event -> io Response
- Patrol.Type.Dsn: Dsn :: Text -> Text -> Maybe Text -> Text -> Maybe Text -> Text -> Text -> Dsn
+ Patrol.Type.Dsn: Dsn :: Text -> Text -> Text -> Text -> Maybe Natural -> Text -> Text -> Dsn
- Patrol.Type.Dsn: [port] :: Dsn -> Maybe Text
+ Patrol.Type.Dsn: [port] :: Dsn -> Maybe Natural
- Patrol.Type.Dsn: [secretKey] :: Dsn -> Maybe Text
+ Patrol.Type.Dsn: [secretKey] :: Dsn -> Text
- Patrol.Type.Dsn: fromUri :: URI -> Either String Dsn
+ Patrol.Type.Dsn: fromUri :: MonadThrow m => URI -> m Dsn
- Patrol.Type.Event: Event :: Maybe Text -> Maybe Text -> EventId -> Maybe [Exception] -> Maybe Object -> Maybe [Text] -> Maybe Level -> Maybe Text -> Maybe (Map Text Text) -> Platform -> Maybe Text -> Maybe Request -> Maybe Text -> Maybe (Map Text Text) -> Timestamp -> Maybe Text -> Maybe User -> Event
+ Patrol.Type.Event: Event :: Maybe Breadcrumbs -> Map Text Context -> Maybe DebugMeta -> Text -> Text -> [EventProcessingError] -> EventId -> Maybe Exceptions -> Map Text Value -> [Text] -> Maybe Level -> Maybe LogEntry -> Text -> Map Text Text -> Maybe Platform -> Text -> Maybe Request -> Maybe ClientSdkInfo -> Text -> Map Text Text -> Maybe Threads -> Maybe NominalDiffTime -> Maybe UTCTime -> Text -> Maybe TransactionInfo -> Maybe EventType -> Maybe User -> Text -> Event
- Patrol.Type.Event: [dist] :: Event -> Maybe Text
+ Patrol.Type.Event: [dist] :: Event -> Text
- Patrol.Type.Event: [environment] :: Event -> Maybe Text
+ Patrol.Type.Event: [environment] :: Event -> Text
- Patrol.Type.Event: [exception] :: Event -> Maybe [Exception]
+ Patrol.Type.Event: [exception] :: Event -> Maybe Exceptions
- Patrol.Type.Event: [extra] :: Event -> Maybe Object
+ Patrol.Type.Event: [extra] :: Event -> Map Text Value
- Patrol.Type.Event: [fingerprint] :: Event -> Maybe [Text]
+ Patrol.Type.Event: [fingerprint] :: Event -> [Text]
- Patrol.Type.Event: [logger] :: Event -> Maybe Text
+ Patrol.Type.Event: [logger] :: Event -> Text
- Patrol.Type.Event: [modules] :: Event -> Maybe (Map Text Text)
+ Patrol.Type.Event: [modules] :: Event -> Map Text Text
- Patrol.Type.Event: [platform] :: Event -> Platform
+ Patrol.Type.Event: [platform] :: Event -> Maybe Platform
- Patrol.Type.Event: [release] :: Event -> Maybe Text
+ Patrol.Type.Event: [release] :: Event -> Text
- Patrol.Type.Event: [serverName] :: Event -> Maybe Text
+ Patrol.Type.Event: [serverName] :: Event -> Text
- Patrol.Type.Event: [tags] :: Event -> Maybe (Map Text Text)
+ Patrol.Type.Event: [tags] :: Event -> Map Text Text
- Patrol.Type.Event: [timestamp] :: Event -> Timestamp
+ Patrol.Type.Event: [timestamp] :: Event -> Maybe UTCTime
- Patrol.Type.Event: [transaction] :: Event -> Maybe Text
+ Patrol.Type.Event: [transaction] :: Event -> Text
- Patrol.Type.Exception: Exception :: Maybe Text -> Maybe StackTrace -> Text -> Text -> Exception
+ Patrol.Type.Exception: Exception :: Maybe Mechanism -> Text -> Maybe Stacktrace -> Text -> Text -> Text -> Exception
- Patrol.Type.Exception: [module_] :: Exception -> Maybe Text
+ Patrol.Type.Exception: [module_] :: Exception -> Text
- Patrol.Type.Frame: Frame :: Maybe Int -> Maybe Text -> Text -> Maybe Int -> Maybe Text -> Maybe Text -> Frame
+ Patrol.Type.Frame: Frame :: Text -> Text -> Maybe Int -> Text -> Text -> Text -> Text -> Maybe Bool -> Text -> Maybe Int -> Text -> Text -> Maybe Platform -> [Text] -> [Text] -> Text -> Maybe Bool -> Text -> Map Text Value -> Frame
- Patrol.Type.Frame: [filename] :: Frame -> Maybe Text
+ Patrol.Type.Frame: [filename] :: Frame -> Text
- Patrol.Type.Frame: [module_] :: Frame -> Maybe Text
+ Patrol.Type.Frame: [module_] :: Frame -> Text
- Patrol.Type.Frame: [package] :: Frame -> Maybe Text
+ Patrol.Type.Frame: [package] :: Frame -> Text
- Patrol.Type.Frame: fromSrcLoc :: String -> SrcLoc -> Frame
+ Patrol.Type.Frame: fromSrcLoc :: SrcLoc -> Frame
- Patrol.Type.Request: Request :: Maybe (Map Text Text) -> Maybe Value -> Maybe (Map Text Text) -> Maybe (Map Text Text) -> Maybe Text -> Maybe (Map Text Text) -> Maybe Text -> Request
+ Patrol.Type.Request: Request :: Map Text Text -> Value -> Map Text Value -> Text -> Map Text Text -> Text -> Text -> Map Text Text -> Text -> Request
- Patrol.Type.Request: [cookies] :: Request -> Maybe (Map Text Text)
+ Patrol.Type.Request: [cookies] :: Request -> Map Text Text
- Patrol.Type.Request: [data_] :: Request -> Maybe Value
+ Patrol.Type.Request: [data_] :: Request -> Value
- Patrol.Type.Request: [env] :: Request -> Maybe (Map Text Text)
+ Patrol.Type.Request: [env] :: Request -> Map Text Value
- Patrol.Type.Request: [headers] :: Request -> Maybe (Map Text Text)
+ Patrol.Type.Request: [headers] :: Request -> Map Text Text
- Patrol.Type.Request: [method] :: Request -> Maybe Text
+ Patrol.Type.Request: [method] :: Request -> Text
- Patrol.Type.Request: [queryString] :: Request -> Maybe (Map Text Text)
+ Patrol.Type.Request: [queryString] :: Request -> Map Text Text
- Patrol.Type.Request: [url] :: Request -> Maybe Text
+ Patrol.Type.Request: [url] :: Request -> Text
- Patrol.Type.User: User :: Maybe Text -> Maybe Text -> Maybe Text -> Maybe Text -> User
+ Patrol.Type.User: User :: Map Text Value -> Text -> Maybe Geo -> Text -> Text -> Text -> Text -> Text -> User
- Patrol.Type.User: [email] :: User -> Maybe Text
+ Patrol.Type.User: [email] :: User -> Text
- Patrol.Type.User: [ipAddress] :: User -> Maybe Text
+ Patrol.Type.User: [ipAddress] :: User -> Text
- Patrol.Type.User: [username] :: User -> Maybe Text
+ Patrol.Type.User: [username] :: User -> Text

Files

CHANGELOG.markdown view
@@ -1,4 +1,4 @@ # Change log -Patrol uses [Semantic Versioning](https://semver.org/spec/v2.0.0.html).+Patrol uses the [Package Versioning Policy](https://pvp.haskell.org). The change log is available through [the releases](https://github.com/tfausak/patrol/releases) on GitHub.
− LICENSE.markdown
@@ -1,13 +0,0 @@-Copyright 2022 Taylor Fausak--Permission to use, copy, modify, and/or distribute this software for any-purpose with or without fee is hereby granted, provided that the above-copyright notice and this permission notice appear in all copies.--THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH-REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND-FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,-INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM-LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR-OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR-PERFORMANCE OF THIS SOFTWARE.
+ LICENSE.txt view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2022 Taylor Fausak++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
patrol.cabal view
@@ -1,72 +1,184 @@-cabal-version: 2.0+cabal-version: 2.2  name: patrol-version: 0.0.4+version: 0.1.0.1 synopsis: Sentry SDK description: Patrol is a Sentry SDK.  build-type: Simple category: Exceptions-extra-source-files:-  CHANGELOG.markdown-  README.markdown-license-file: LICENSE.markdown-license: ISC+extra-source-files: CHANGELOG.markdown README.markdown+license-file: LICENSE.txt+license: MIT maintainer: Taylor Fausak  source-repository head   location: https://github.com/tfausak/patrol   type: git -library-  autogen-modules: Paths_patrol+flag pedantic+  default: False+  manual: True++common library   build-depends:-    base >= 4.13.0 && < 4.17-    , aeson >= 1.4.6 && < 1.6 || >= 2.0.3 && < 2.2-    , bytestring >= 0.10.10 && < 0.12+    , aeson >= 2.1 && < 2.2+    , base >= 4.15 && < 4.18+    , bytestring >= 0.10.12 && < 0.12     , case-insensitive >= 1.2.1 && < 1.3-    , containers >= 0.6.2 && < 0.7-    , http-client >= 0.6.4 && < 0.8+    , containers >= 0.6.4 && < 0.7+    , exceptions >= 0.10.4 && < 0.11+    , http-client >= 0.7.13 && < 0.8     , http-types >= 0.12.3 && < 0.13-    , network-uri >= 2.6.2 && < 2.7-    , text >= 1.2.4 && < 1.3 || >= 2.0 && < 2.1-    , time >= 1.9.3 && < 1.12-    , uuid >= 1.3.13 && < 1.4-  default-extensions: NamedFieldPuns+    , network-uri >= 2.6.4 && < 2.7+    , text >= 1.2.4 && < 1.3 || >= 2.0.1 && < 2.1+    , time >= 1.9.3 && < 1.13+    , uuid >= 1.3.15 && < 1.4   default-language: Haskell2010+  ghc-options:+    -Weverything+    -Wno-all-missed-specialisations+    -Wno-implicit-prelude+    -Wno-missed-specialisations+    -Wno-missing-deriving-strategies+    -Wno-missing-export-lists+    -Wno-missing-safe-haskell-mode+    -Wno-prepositive-qualified-module+    -Wno-safe+    -Wno-unsafe++  if impl(ghc >= 9.2)+    ghc-options: -Wno-missing-kind-signatures++  if flag(pedantic)+    ghc-options: -Werror++common executable+  import: library++  build-depends: patrol+  ghc-options: -rtsopts -threaded++library+  import: library++  autogen-modules: Paths_patrol   exposed-modules:     Patrol     Patrol.Client-    Patrol.Utility.Json-    Patrol.Utility.Maybe+    Patrol.Constant+    Patrol.Exception.Problem+    Patrol.Extra.Aeson+    Patrol.Extra.List+    Patrol.Type.AppContext+    Patrol.Type.AppleDebugImage+    Patrol.Type.Breadcrumb+    Patrol.Type.Breadcrumbs+    Patrol.Type.BreadcrumbType+    Patrol.Type.BrowserContext+    Patrol.Type.CError+    Patrol.Type.ClientSdkInfo+    Patrol.Type.ClientSdkPackage+    Patrol.Type.Context+    Patrol.Type.DebugImage+    Patrol.Type.DebugMeta+    Patrol.Type.DeviceContext     Patrol.Type.Dsn+    Patrol.Type.ErrorType     Patrol.Type.Event     Patrol.Type.EventId+    Patrol.Type.EventProcessingError+    Patrol.Type.EventType     Patrol.Type.Exception+    Patrol.Type.Exceptions     Patrol.Type.Frame+    Patrol.Type.Geo+    Patrol.Type.GpuContext     Patrol.Type.Level+    Patrol.Type.LogEntry+    Patrol.Type.MachException+    Patrol.Type.Mechanism+    Patrol.Type.MechanismMeta+    Patrol.Type.NativeDebugImage+    Patrol.Type.NsError+    Patrol.Type.OsContext     Patrol.Type.Platform+    Patrol.Type.PosixSignal+    Patrol.Type.ProguardDebugImage     Patrol.Type.Request     Patrol.Type.Response-    Patrol.Type.StackTrace-    Patrol.Type.Timestamp+    Patrol.Type.RuntimeContext+    Patrol.Type.SpanStatus+    Patrol.Type.Stacktrace+    Patrol.Type.SystemSdkInfo+    Patrol.Type.Thread+    Patrol.Type.Threads+    Patrol.Type.TraceContext+    Patrol.Type.TransactionInfo+    Patrol.Type.TransactionSource     Patrol.Type.User-  ghc-options:-    -Weverything-    -Wno-all-missed-specialisations-    -Wno-implicit-prelude-    -Wno-missing-deriving-strategies-    -Wno-missing-exported-signatures-    -Wno-safe-    -Wno-unsafe-  hs-source-dirs: src/lib+    Patrol.Version+  hs-source-dirs: source/library   other-modules: Paths_patrol -  if impl(ghc >= 8.10)-    ghc-options:-      -Wno-missing-safe-haskell-mode-      -Wno-prepositive-qualified-module+test-suite patrol-test-suite+  import: executable -  if impl(ghc >= 9.2)-    ghc-options:-      -Wno-missing-kind-signatures+  build-depends:+    , hspec >= 2.10.6 && < 2.11+  hs-source-dirs: source/test-suite+  main-is: Main.hs+  other-modules:+    Patrol.ClientSpec+    Patrol.ConstantSpec+    Patrol.Extra.AesonSpec+    Patrol.Extra.ListSpec+    Patrol.Type.AppContextSpec+    Patrol.Type.AppleDebugImageSpec+    Patrol.Type.BreadcrumbSpec+    Patrol.Type.BreadcrumbsSpec+    Patrol.Type.BreadcrumbTypeSpec+    Patrol.Type.BrowserContextSpec+    Patrol.Type.CErrorSpec+    Patrol.Type.ClientSdkInfoSpec+    Patrol.Type.ClientSdkPackageSpec+    Patrol.Type.ContextSpec+    Patrol.Type.DebugImageSpec+    Patrol.Type.DebugMetaSpec+    Patrol.Type.DeviceContextSpec+    Patrol.Type.DsnSpec+    Patrol.Type.ErrorTypeSpec+    Patrol.Type.EventIdSpec+    Patrol.Type.EventProcessingErrorSpec+    Patrol.Type.EventSpec+    Patrol.Type.EventTypeSpec+    Patrol.Type.ExceptionSpec+    Patrol.Type.ExceptionsSpec+    Patrol.Type.FrameSpec+    Patrol.Type.GeoSpec+    Patrol.Type.GpuContextSpec+    Patrol.Type.LevelSpec+    Patrol.Type.LogEntrySpec+    Patrol.Type.MachExceptionSpec+    Patrol.Type.MechanismMetaSpec+    Patrol.Type.MechanismSpec+    Patrol.Type.NativeDebugImageSpec+    Patrol.Type.NsErrorSpec+    Patrol.Type.OsContextSpec+    Patrol.Type.PlatformSpec+    Patrol.Type.PosixSignalSpec+    Patrol.Type.ProguardDebugImageSpec+    Patrol.Type.RequestSpec+    Patrol.Type.ResponseSpec+    Patrol.Type.RuntimeContextSpec+    Patrol.Type.SpanStatusSpec+    Patrol.Type.StacktraceSpec+    Patrol.Type.SystemSdkInfoSpec+    Patrol.Type.ThreadSpec+    Patrol.Type.ThreadsSpec+    Patrol.Type.TraceContextSpec+    Patrol.Type.TransactionInfoSpec+    Patrol.Type.TransactionSourceSpec+    Patrol.Type.UserSpec+    PatrolSpec+  type: exitcode-stdio-1.0
+ source/library/Patrol.hs view
@@ -0,0 +1,100 @@+module Patrol+  ( Patrol.Exception.Problem.Problem,+    Patrol.Type.AppContext.AppContext,+    Patrol.Type.AppleDebugImage.AppleDebugImage,+    Patrol.Type.Breadcrumb.Breadcrumb,+    Patrol.Type.Breadcrumbs.Breadcrumbs,+    Patrol.Type.BreadcrumbType.BreadcrumbType,+    Patrol.Type.BrowserContext.BrowserContext,+    Patrol.Type.CError.CError,+    Patrol.Type.ClientSdkInfo.ClientSdkInfo,+    Patrol.Type.ClientSdkPackage.ClientSdkPackage,+    Patrol.Type.Context.Context,+    Patrol.Type.DebugImage.DebugImage,+    Patrol.Type.DebugMeta.DebugMeta,+    Patrol.Type.DeviceContext.DeviceContext,+    Patrol.Type.Dsn.Dsn,+    Patrol.Type.ErrorType.ErrorType,+    Patrol.Type.Event.Event,+    Patrol.Type.EventId.EventId,+    Patrol.Type.EventProcessingError.EventProcessingError,+    Patrol.Type.EventType.EventType,+    Patrol.Type.Exception.Exception,+    Patrol.Type.Exceptions.Exceptions,+    Patrol.Type.Frame.Frame,+    Patrol.Type.Geo.Geo,+    Patrol.Type.GpuContext.GpuContext,+    Patrol.Type.Level.Level,+    Patrol.Type.LogEntry.LogEntry,+    Patrol.Type.MachException.MachException,+    Patrol.Type.Mechanism.Mechanism,+    Patrol.Type.MechanismMeta.MechanismMeta,+    Patrol.Type.NativeDebugImage.NativeDebugImage,+    Patrol.Type.NsError.NsError,+    Patrol.Type.OsContext.OsContext,+    Patrol.Type.Platform.Platform,+    Patrol.Type.PosixSignal.PosixSignal,+    Patrol.Type.ProguardDebugImage.ProguardDebugImage,+    Patrol.Type.Request.Request,+    Patrol.Type.Response.Response,+    Patrol.Type.RuntimeContext.RuntimeContext,+    Patrol.Type.SpanStatus.SpanStatus,+    Patrol.Type.Stacktrace.Stacktrace,+    Patrol.Type.SystemSdkInfo.SystemSdkInfo,+    Patrol.Type.Thread.Thread,+    Patrol.Type.Threads.Threads,+    Patrol.Type.TraceContext.TraceContext,+    Patrol.Type.TransactionInfo.TransactionInfo,+    Patrol.Type.TransactionSource.TransactionSource,+    Patrol.Type.User.User,+  )+where++import qualified Patrol.Exception.Problem+import qualified Patrol.Type.AppContext+import qualified Patrol.Type.AppleDebugImage+import qualified Patrol.Type.Breadcrumb+import qualified Patrol.Type.BreadcrumbType+import qualified Patrol.Type.Breadcrumbs+import qualified Patrol.Type.BrowserContext+import qualified Patrol.Type.CError+import qualified Patrol.Type.ClientSdkInfo+import qualified Patrol.Type.ClientSdkPackage+import qualified Patrol.Type.Context+import qualified Patrol.Type.DebugImage+import qualified Patrol.Type.DebugMeta+import qualified Patrol.Type.DeviceContext+import qualified Patrol.Type.Dsn+import qualified Patrol.Type.ErrorType+import qualified Patrol.Type.Event+import qualified Patrol.Type.EventId+import qualified Patrol.Type.EventProcessingError+import qualified Patrol.Type.EventType+import qualified Patrol.Type.Exception+import qualified Patrol.Type.Exceptions+import qualified Patrol.Type.Frame+import qualified Patrol.Type.Geo+import qualified Patrol.Type.GpuContext+import qualified Patrol.Type.Level+import qualified Patrol.Type.LogEntry+import qualified Patrol.Type.MachException+import qualified Patrol.Type.Mechanism+import qualified Patrol.Type.MechanismMeta+import qualified Patrol.Type.NativeDebugImage+import qualified Patrol.Type.NsError+import qualified Patrol.Type.OsContext+import qualified Patrol.Type.Platform+import qualified Patrol.Type.PosixSignal+import qualified Patrol.Type.ProguardDebugImage+import qualified Patrol.Type.Request+import qualified Patrol.Type.Response+import qualified Patrol.Type.RuntimeContext+import qualified Patrol.Type.SpanStatus+import qualified Patrol.Type.Stacktrace+import qualified Patrol.Type.SystemSdkInfo+import qualified Patrol.Type.Thread+import qualified Patrol.Type.Threads+import qualified Patrol.Type.TraceContext+import qualified Patrol.Type.TransactionInfo+import qualified Patrol.Type.TransactionSource+import qualified Patrol.Type.User
+ source/library/Patrol/Client.hs view
@@ -0,0 +1,23 @@+module Patrol.Client where++import qualified Control.Monad.Catch as Catch+import qualified Control.Monad.IO.Class as IO+import qualified Data.Aeson as Aeson+import qualified Network.HTTP.Client as Client+import qualified Patrol.Exception.Problem as Problem+import qualified Patrol.Type.Dsn as Dsn+import qualified Patrol.Type.Event as Event+import qualified Patrol.Type.Response as Response++store ::+  (IO.MonadIO io, Catch.MonadThrow io) =>+  Client.Manager ->+  Dsn.Dsn ->+  Event.Event ->+  io Response.Response+store manager dsn event = do+  request <- Event.intoRequest dsn event+  response <- IO.liftIO $ Client.httpLbs request manager+  either (Catch.throwM . Problem.Problem . mappend "invalid response body: ") pure+    . Aeson.eitherDecode+    $ Client.responseBody response
+ source/library/Patrol/Constant.hs view
@@ -0,0 +1,20 @@+module Patrol.Constant where++import qualified Data.ByteString as ByteString+import qualified Data.CaseInsensitive as CI+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Network.HTTP.Types as Http+import qualified Patrol.Version as Version++applicationJson :: ByteString.ByteString+applicationJson = Text.encodeUtf8 $ Text.pack "application/json"++sentryVersion :: Text.Text+sentryVersion = Text.singleton '7'++userAgent :: Text.Text+userAgent = Text.pack "patrol/" <> Version.text++xSentryAuth :: Http.HeaderName+xSentryAuth = CI.mk . Text.encodeUtf8 $ Text.pack "X-Sentry-Auth"
+ source/library/Patrol/Exception/Problem.hs view
@@ -0,0 +1,9 @@+module Patrol.Exception.Problem where++import qualified Control.Monad.Catch as Catch++newtype Problem+  = Problem String+  deriving (Eq, Show)++instance Catch.Exception Problem
+ source/library/Patrol/Extra/Aeson.hs view
@@ -0,0 +1,20 @@+module Patrol.Extra.Aeson where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.Key as Key+import qualified Data.Aeson.Types as Aeson+import qualified Data.Text as Text++intoObject :: [Aeson.Pair] -> Aeson.Value+intoObject = Aeson.object . filter (not . isEmpty . snd)++isEmpty :: Aeson.Value -> Bool+isEmpty value = case value of+  Aeson.Array array -> null array+  Aeson.Null -> True+  Aeson.Object object -> null object+  Aeson.String string -> Text.null string+  _ -> False++pair :: Aeson.ToJSON a => String -> a -> Aeson.Pair+pair = (Aeson..=) . Key.fromString
+ source/library/Patrol/Extra/List.hs view
@@ -0,0 +1,16 @@+module Patrol.Extra.List where++import qualified Data.Function as Function++insertAll :: Eq k => [(k, v)] -> [(k, v)] -> [(k, v)]+insertAll = flip $ foldr insert++insert :: Eq k => (k, v) -> [(k, v)] -> [(k, v)]+insert = insertBy $ Function.on (==) fst++insertBy :: (a -> a -> Bool) -> a -> [a] -> [a]+insertBy p x xs = case xs of+  [] -> [x]+  h : t+    | p x h -> h : t+    | otherwise -> h : insertBy p x t
+ source/library/Patrol/Type/AppContext.hs view
@@ -0,0 +1,45 @@+module Patrol.Type.AppContext where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Data.Time as Time+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#appcontext>+data AppContext = AppContext+  { appBuild :: Text.Text,+    appIdentifier :: Text.Text,+    appMemory :: Maybe Int,+    appName :: Text.Text,+    appStartTime :: Maybe Time.UTCTime,+    appVersion :: Text.Text,+    buildType :: Text.Text,+    deviceAppHash :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON AppContext where+  toJSON appContext =+    Aeson.intoObject+      [ Aeson.pair "app_build" $ appBuild appContext,+        Aeson.pair "app_identifier" $ appIdentifier appContext,+        Aeson.pair "app_memory" $ appMemory appContext,+        Aeson.pair "app_name" $ appName appContext,+        Aeson.pair "app_start_time" $ appStartTime appContext,+        Aeson.pair "app_version" $ appVersion appContext,+        Aeson.pair "build_type" $ buildType appContext,+        Aeson.pair "device_app_hash" $ deviceAppHash appContext+      ]++empty :: AppContext+empty =+  AppContext+    { appBuild = Text.empty,+      appIdentifier = Text.empty,+      appMemory = Nothing,+      appName = Text.empty,+      appStartTime = Nothing,+      appVersion = Text.empty,+      buildType = Text.empty,+      deviceAppHash = Text.empty+    }
+ source/library/Patrol/Type/AppleDebugImage.hs view
@@ -0,0 +1,44 @@+module Patrol.Type.AppleDebugImage where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#appledebugimage>+data AppleDebugImage = AppleDebugImage+  { arch :: Text.Text,+    cpuSubtype :: Maybe Int,+    cpuType :: Maybe Int,+    imageAddr :: Text.Text,+    imageSize :: Maybe Int,+    imageVmaddr :: Text.Text,+    name :: Text.Text,+    uuid :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON AppleDebugImage where+  toJSON appleDebugImage =+    Aeson.intoObject+      [ Aeson.pair "arch" $ arch appleDebugImage,+        Aeson.pair "cpu_subtype" $ cpuSubtype appleDebugImage,+        Aeson.pair "cpu_type" $ cpuType appleDebugImage,+        Aeson.pair "image_addr" $ imageAddr appleDebugImage,+        Aeson.pair "image_size" $ imageSize appleDebugImage,+        Aeson.pair "image_vmaddr" $ imageVmaddr appleDebugImage,+        Aeson.pair "name" $ name appleDebugImage,+        Aeson.pair "uuid" $ uuid appleDebugImage+      ]++empty :: AppleDebugImage+empty =+  AppleDebugImage+    { arch = Text.empty,+      cpuSubtype = Nothing,+      cpuType = Nothing,+      imageAddr = Text.empty,+      imageSize = Nothing,+      imageVmaddr = Text.empty,+      name = Text.empty,+      uuid = Text.empty+    }
+ source/library/Patrol/Type/Breadcrumb.hs view
@@ -0,0 +1,46 @@+module Patrol.Type.Breadcrumb where++import qualified Data.Aeson as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Data.Time as Time+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.BreadcrumbType as BreadcrumbType+import qualified Patrol.Type.EventId as EventId+import qualified Patrol.Type.Level as Level++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#breadcrumb>+data Breadcrumb = Breadcrumb+  { category :: Text.Text,+    data_ :: Map.Map Text.Text Aeson.Value,+    eventId :: Maybe EventId.EventId,+    level :: Maybe Level.Level,+    message :: Text.Text,+    timestamp :: Maybe Time.UTCTime,+    type_ :: Maybe BreadcrumbType.BreadcrumbType+  }+  deriving (Eq, Show)++instance Aeson.ToJSON Breadcrumb where+  toJSON breadcrumb =+    Aeson.intoObject+      [ Aeson.pair "category" $ category breadcrumb,+        Aeson.pair "data" $ data_ breadcrumb,+        Aeson.pair "event_id" $ eventId breadcrumb,+        Aeson.pair "level" $ level breadcrumb,+        Aeson.pair "message" $ message breadcrumb,+        Aeson.pair "timestamp" $ timestamp breadcrumb,+        Aeson.pair "type" $ type_ breadcrumb+      ]++empty :: Breadcrumb+empty =+  Breadcrumb+    { category = Text.empty,+      data_ = Map.empty,+      eventId = Nothing,+      level = Nothing,+      message = Text.empty,+      timestamp = Nothing,+      type_ = Nothing+    }
+ source/library/Patrol/Type/BreadcrumbType.hs view
@@ -0,0 +1,15 @@+module Patrol.Type.BreadcrumbType where++import qualified Data.Aeson as Aeson++data BreadcrumbType+  = Default+  | Http+  | Navigation+  deriving (Eq, Show)++instance Aeson.ToJSON BreadcrumbType where+  toJSON breadcrumbType = Aeson.toJSON $ case breadcrumbType of+    Default -> "default"+    Http -> "http"+    Navigation -> "navigation"
+ source/library/Patrol/Type/Breadcrumbs.hs view
@@ -0,0 +1,23 @@+module Patrol.Type.Breadcrumbs where++import qualified Data.Aeson as Aeson+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.Breadcrumb as Breadcrumb++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#breadcrumbs>+newtype Breadcrumbs = Breadcrumbs+  { values :: [Breadcrumb.Breadcrumb]+  }+  deriving (Eq, Show)++instance Aeson.ToJSON Breadcrumbs where+  toJSON breadcrumbs =+    Aeson.intoObject+      [ Aeson.pair "values" $ values breadcrumbs+      ]++empty :: Breadcrumbs+empty =+  Breadcrumbs+    { values = []+    }
+ source/library/Patrol/Type/BrowserContext.hs view
@@ -0,0 +1,26 @@+module Patrol.Type.BrowserContext where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#browsercontext>+data BrowserContext = BrowserContext+  { name :: Text.Text,+    version :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON BrowserContext where+  toJSON browserContext =+    Aeson.intoObject+      [ Aeson.pair "name" $ name browserContext,+        Aeson.pair "version" $ version browserContext+      ]++empty :: BrowserContext+empty =+  BrowserContext+    { name = Text.empty,+      version = Text.empty+    }
+ source/library/Patrol/Type/CError.hs view
@@ -0,0 +1,26 @@+module Patrol.Type.CError where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#cerror>+data CError = CError+  { name :: Text.Text,+    number :: Maybe Int+  }+  deriving (Eq, Show)++instance Aeson.ToJSON CError where+  toJSON cError =+    Aeson.intoObject+      [ Aeson.pair "name" $ name cError,+        Aeson.pair "number" $ number cError+      ]++empty :: CError+empty =+  CError+    { name = Text.empty,+      number = Nothing+    }
+ source/library/Patrol/Type/ClientSdkInfo.hs view
@@ -0,0 +1,33 @@+module Patrol.Type.ClientSdkInfo where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.ClientSdkPackage as ClientSdkPackage++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#clientsdkinfo>+data ClientSdkInfo = ClientSdkInfo+  { integrations :: [Text.Text],+    name :: Text.Text,+    packages :: [ClientSdkPackage.ClientSdkPackage],+    version :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON ClientSdkInfo where+  toJSON clientSdkInfo =+    Aeson.intoObject+      [ Aeson.pair "integrations" $ integrations clientSdkInfo,+        Aeson.pair "name" $ name clientSdkInfo,+        Aeson.pair "packages" $ packages clientSdkInfo,+        Aeson.pair "version" $ version clientSdkInfo+      ]++empty :: ClientSdkInfo+empty =+  ClientSdkInfo+    { integrations = [],+      name = Text.empty,+      packages = [],+      version = Text.empty+    }
+ source/library/Patrol/Type/ClientSdkPackage.hs view
@@ -0,0 +1,26 @@+module Patrol.Type.ClientSdkPackage where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#clientsdkpackage>+data ClientSdkPackage = ClientSdkPackage+  { name :: Text.Text,+    version :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON ClientSdkPackage where+  toJSON clientSdkPackage =+    Aeson.intoObject+      [ Aeson.pair "name" $ name clientSdkPackage,+        Aeson.pair "version" $ version clientSdkPackage+      ]++empty :: ClientSdkPackage+empty =+  ClientSdkPackage+    { name = Text.empty,+      version = Text.empty+    }
+ source/library/Patrol/Type/Context.hs view
@@ -0,0 +1,34 @@+module Patrol.Type.Context where++import qualified Data.Aeson as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Patrol.Type.AppContext as AppContext+import qualified Patrol.Type.BrowserContext as BrowserContext+import qualified Patrol.Type.DeviceContext as DeviceContext+import qualified Patrol.Type.GpuContext as GpuContext+import qualified Patrol.Type.OsContext as OsContext+import qualified Patrol.Type.RuntimeContext as RuntimeContext+import qualified Patrol.Type.TraceContext as TraceContext++data Context+  = App AppContext.AppContext+  | Browser BrowserContext.BrowserContext+  | Device DeviceContext.DeviceContext+  | Gpu GpuContext.GpuContext+  | Os OsContext.OsContext+  | Runtime RuntimeContext.RuntimeContext+  | Trace TraceContext.TraceContext+  | Other (Map.Map Text.Text Aeson.Value)+  deriving (Eq, Show)++instance Aeson.ToJSON Context where+  toJSON context = case context of+    App appContext -> Aeson.toJSON appContext+    Browser browserContext -> Aeson.toJSON browserContext+    Device deviceContext -> Aeson.toJSON deviceContext+    Gpu gpuContext -> Aeson.toJSON gpuContext+    Os osContext -> Aeson.toJSON osContext+    Runtime runtimeContext -> Aeson.toJSON runtimeContext+    Trace traceContext -> Aeson.toJSON traceContext+    Other other -> Aeson.toJSON other
+ source/library/Patrol/Type/DebugImage.hs view
@@ -0,0 +1,22 @@+module Patrol.Type.DebugImage where++import qualified Data.Aeson as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Patrol.Type.AppleDebugImage as AppleDebugImage+import qualified Patrol.Type.NativeDebugImage as NativeDebugImage+import qualified Patrol.Type.ProguardDebugImage as ProguardDebugImage++data DebugImage+  = Apple AppleDebugImage.AppleDebugImage+  | Native NativeDebugImage.NativeDebugImage+  | Proguard ProguardDebugImage.ProguardDebugImage+  | Other (Map.Map Text.Text Aeson.Value)+  deriving (Eq, Show)++instance Aeson.ToJSON DebugImage where+  toJSON debugImage = case debugImage of+    Apple appleDebugImage -> Aeson.toJSON appleDebugImage+    Native nativeDebugImage -> Aeson.toJSON nativeDebugImage+    Proguard proguardDebugImage -> Aeson.toJSON proguardDebugImage+    Other other -> Aeson.toJSON other
+ source/library/Patrol/Type/DebugMeta.hs view
@@ -0,0 +1,27 @@+module Patrol.Type.DebugMeta where++import qualified Data.Aeson as Aeson+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.DebugImage as DebugImage+import qualified Patrol.Type.SystemSdkInfo as SystemSdkInfo++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#debugmeta>+data DebugMeta = DebugMeta+  { images :: [DebugImage.DebugImage],+    sdkInfo :: Maybe SystemSdkInfo.SystemSdkInfo+  }+  deriving (Eq, Show)++instance Aeson.ToJSON DebugMeta where+  toJSON debugMeta =+    Aeson.intoObject+      [ Aeson.pair "images" $ images debugMeta,+        Aeson.pair "sdk_info" $ sdkInfo debugMeta+      ]++empty :: DebugMeta+empty =+  DebugMeta+    { images = [],+      sdkInfo = Nothing+    }
+ source/library/Patrol/Type/DeviceContext.hs view
@@ -0,0 +1,129 @@+module Patrol.Type.DeviceContext where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Data.Time as Time+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#devicecontext>+data DeviceContext = DeviceContext+  { arch :: Text.Text,+    batteryLevel :: Maybe Double,+    batteryStatus :: Text.Text,+    bootTime :: Maybe Time.UTCTime,+    brand :: Text.Text,+    charging :: Maybe Bool,+    cpuDescription :: Text.Text,+    deviceType :: Text.Text,+    deviceUniqueIdentifier :: Text.Text,+    externalFreeStorage :: Maybe Int,+    externalStorageSize :: Maybe Int,+    family :: Text.Text,+    freeMemory :: Maybe Int,+    freeStorage :: Maybe Int,+    lowMemory :: Maybe Bool,+    manufacturer :: Text.Text,+    memorySize :: Maybe Int,+    model :: Text.Text,+    modelId :: Text.Text,+    name :: Text.Text,+    online :: Maybe Bool,+    orientation :: Text.Text,+    processorCount :: Maybe Int,+    processorFrequency :: Maybe Double,+    screenDensity :: Maybe Double,+    screenDpi :: Maybe Double,+    screenResolution :: Text.Text,+    simulator :: Maybe Bool,+    storageSize :: Maybe Int,+    supportsAccelerometer :: Maybe Bool,+    supportsAudio :: Maybe Bool,+    supportsGyroscope :: Maybe Bool,+    supportsLocationService :: Maybe Bool,+    supportsVibration :: Maybe Bool,+    timezone :: Text.Text,+    usableMemory :: Maybe Int+  }+  deriving (Eq, Show)++instance Aeson.ToJSON DeviceContext where+  toJSON deviceContext =+    Aeson.intoObject+      [ Aeson.pair "arch" $ arch deviceContext,+        Aeson.pair "battery_level" $ batteryLevel deviceContext,+        Aeson.pair "battery_status" $ batteryStatus deviceContext,+        Aeson.pair "boot_time" $ bootTime deviceContext,+        Aeson.pair "brand" $ brand deviceContext,+        Aeson.pair "charging" $ charging deviceContext,+        Aeson.pair "cpu_description" $ cpuDescription deviceContext,+        Aeson.pair "device_type" $ deviceType deviceContext,+        Aeson.pair "device_unique_identifier" $ deviceUniqueIdentifier deviceContext,+        Aeson.pair "external_free_storage" $ externalFreeStorage deviceContext,+        Aeson.pair "external_storage_size" $ externalStorageSize deviceContext,+        Aeson.pair "family" $ family deviceContext,+        Aeson.pair "free_memory" $ freeMemory deviceContext,+        Aeson.pair "free_storage" $ freeStorage deviceContext,+        Aeson.pair "low_memory" $ lowMemory deviceContext,+        Aeson.pair "manufacturer" $ manufacturer deviceContext,+        Aeson.pair "memory_size" $ memorySize deviceContext,+        Aeson.pair "model" $ model deviceContext,+        Aeson.pair "model_id" $ modelId deviceContext,+        Aeson.pair "name" $ name deviceContext,+        Aeson.pair "online" $ online deviceContext,+        Aeson.pair "orientation" $ orientation deviceContext,+        Aeson.pair "processor_count" $ processorCount deviceContext,+        Aeson.pair "processor_frequency" $ processorFrequency deviceContext,+        Aeson.pair "screen_density" $ screenDensity deviceContext,+        Aeson.pair "screen_dpi" $ screenDpi deviceContext,+        Aeson.pair "screen_resolution" $ screenResolution deviceContext,+        Aeson.pair "simulator" $ simulator deviceContext,+        Aeson.pair "storage_size" $ storageSize deviceContext,+        Aeson.pair "supports_accelerometer" $ supportsAccelerometer deviceContext,+        Aeson.pair "supports_audio" $ supportsAudio deviceContext,+        Aeson.pair "supports_gyroscope" $ supportsGyroscope deviceContext,+        Aeson.pair "supports_location_service" $ supportsLocationService deviceContext,+        Aeson.pair "supports_vibration" $ supportsVibration deviceContext,+        Aeson.pair "timezone" $ timezone deviceContext,+        Aeson.pair "usable_memory" $ usableMemory deviceContext+      ]++empty :: DeviceContext+empty =+  DeviceContext+    { arch = Text.empty,+      batteryLevel = Nothing,+      batteryStatus = Text.empty,+      bootTime = Nothing,+      brand = Text.empty,+      charging = Nothing,+      cpuDescription = Text.empty,+      deviceType = Text.empty,+      deviceUniqueIdentifier = Text.empty,+      externalFreeStorage = Nothing,+      externalStorageSize = Nothing,+      family = Text.empty,+      freeMemory = Nothing,+      freeStorage = Nothing,+      lowMemory = Nothing,+      manufacturer = Text.empty,+      memorySize = Nothing,+      model = Text.empty,+      modelId = Text.empty,+      name = Text.empty,+      online = Nothing,+      orientation = Text.empty,+      processorCount = Nothing,+      processorFrequency = Nothing,+      screenDensity = Nothing,+      screenDpi = Nothing,+      screenResolution = Text.empty,+      simulator = Nothing,+      storageSize = Nothing,+      supportsAccelerometer = Nothing,+      supportsAudio = Nothing,+      supportsGyroscope = Nothing,+      supportsLocationService = Nothing,+      supportsVibration = Nothing,+      timezone = Text.empty,+      usableMemory = Nothing+    }
+ source/library/Patrol/Type/Dsn.hs view
@@ -0,0 +1,91 @@+module Patrol.Type.Dsn where++import qualified Control.Monad as Monad+import qualified Control.Monad.Catch as Catch+import qualified Data.ByteString as ByteString+import qualified Data.Maybe as Maybe+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Network.URI as Uri+import qualified Numeric.Natural as Natural+import qualified Patrol.Constant as Constant+import qualified Patrol.Exception.Problem as Problem+import qualified Text.Read as Read++data Dsn = Dsn+  { protocol :: Text.Text,+    publicKey :: Text.Text,+    secretKey :: Text.Text,+    host :: Text.Text,+    port :: Maybe Natural.Natural,+    path :: Text.Text,+    projectId :: Text.Text+  }+  deriving (Eq, Show)++fromUri :: Catch.MonadThrow m => Uri.URI -> m Dsn+fromUri uri = do+  theProtocol <- maybe (Catch.throwM $ Problem.Problem "invalid scheme") pure . Text.stripSuffix (Text.singleton ':') . Text.pack $ Uri.uriScheme uri+  uriAuth <- maybe (Catch.throwM $ Problem.Problem "missing authority") pure $ Uri.uriAuthority uri+  userInfo <- maybe (Catch.throwM $ Problem.Problem "invalid user information") pure . Text.stripSuffix (Text.singleton '@') . Text.pack $ Uri.uriUserInfo uriAuth+  let (thePublicKey, theSecretKey) = fmap (Text.drop 1) $ Text.breakOn (Text.singleton ':') userInfo+      theHost = Text.pack $ Uri.uriRegName uriAuth+  maybePort <- case Text.stripPrefix (Text.singleton ':') . Text.pack $ Uri.uriPort uriAuth of+    Nothing -> pure Nothing+    Just text -> maybe (Catch.throwM $ Problem.Problem "invalid port") (pure . Just) . Read.readMaybe $ Text.unpack text+  let (thePath, theProjectId) = Text.breakOnEnd (Text.singleton '/') . Text.pack $ Uri.uriPath uri+  Monad.unless (null $ Uri.uriQuery uri) . Catch.throwM $ Problem.Problem "unexpected query"+  Monad.unless (null $ Uri.uriFragment uri) . Catch.throwM $ Problem.Problem "unexpected fragment"+  pure+    Dsn+      { protocol = theProtocol,+        publicKey = thePublicKey,+        secretKey = theSecretKey,+        host = theHost,+        port = maybePort,+        path = thePath,+        projectId = theProjectId+      }++intoUri :: Dsn -> Uri.URI+intoUri dsn =+  Uri.URI+    { Uri.uriScheme = mconcat [Text.unpack $ protocol dsn, ":"],+      Uri.uriAuthority =+        Just+          Uri.URIAuth+            { Uri.uriUserInfo =+                mconcat+                  [ Text.unpack $ publicKey dsn,+                    case secretKey dsn of+                      x+                        | Text.null x -> ""+                        | otherwise -> mconcat [":", Text.unpack x],+                    "@"+                  ],+              Uri.uriRegName = Text.unpack $ host dsn,+              Uri.uriPort = case port dsn of+                Nothing -> ""+                Just x -> mconcat [":", show x]+            },+      Uri.uriPath =+        mconcat+          [ Text.unpack $ path dsn,+            Text.unpack $ projectId dsn+          ],+      Uri.uriQuery = "",+      Uri.uriFragment = ""+    }++intoAuthorization :: Dsn -> ByteString.ByteString+intoAuthorization dsn =+  Text.encodeUtf8+    . (Text.pack "Sentry " <>)+    . Text.intercalate (Text.singleton ',')+    $ Maybe.mapMaybe+      (\(k, v) -> if Text.null v then Nothing else Just (Text.pack k <> Text.singleton '=' <> v))+      [ ("sentry_version", Constant.sentryVersion),+        ("sentry_client", Constant.userAgent),+        ("sentry_key", publicKey dsn),+        ("sentry_secret", secretKey dsn)+      ]
+ source/library/Patrol/Type/ErrorType.hs view
@@ -0,0 +1,89 @@+module Patrol.Type.ErrorType where++import qualified Data.Aeson as Aeson++data ErrorType+  = ClockDrift+  | FetchGenericError+  | FetchInvalidEncoding+  | FetchInvalidHttpCode+  | FetchTimeout+  | FetchTooLarge+  | FutureTimestamp+  | InvalidAttribute+  | InvalidData+  | InvalidEnvironment+  | JsFetchTimeout+  | JsGenericFetchError+  | JsInvalidContent+  | JsInvalidHttpCode+  | JsInvalidSourceEncoding+  | JsInvalidSourcemap+  | JsInvalidSourcemapLocation+  | JsMissingSource+  | JsNoColumn+  | JsTooLarge+  | JsTooManyRemoteSources+  | MissingAttribute+  | NativeBadDsym+  | NativeInternalFailure+  | NativeMissingDsym+  | NativeMissingOptionallyBundledDsym+  | NativeMissingSymbol+  | NativeMissingSystemDsym+  | NativeNoCrashedThread+  | NativeSimulatorFrame+  | NativeSymbolicatorFailed+  | NativeUnknownImage+  | PastTimestamp+  | ProguardMissingLineno+  | ProguardMissingMapping+  | RestrictedIp+  | SecurityViolation+  | TooLargeForCache+  | UnknownError+  | ValueTooLong+  deriving (Eq, Show)++instance Aeson.ToJSON ErrorType where+  toJSON errorType = Aeson.toJSON $ case errorType of+    ClockDrift -> "clock_drift"+    FetchGenericError -> "fetch_generic_error"+    FetchInvalidEncoding -> "fetch_invalid_source_encoding"+    FetchInvalidHttpCode -> "fetch_invalid_http_code"+    FetchTimeout -> "fetch_timeout"+    FetchTooLarge -> "fetch_too_large"+    FutureTimestamp -> "future_timestamp"+    InvalidAttribute -> "invalid_attribute"+    InvalidData -> "invalid_data"+    InvalidEnvironment -> "invalid_environment"+    JsFetchTimeout -> "js_fetch_timeout"+    JsGenericFetchError -> "js_generic_fetch_error"+    JsInvalidContent -> "js_invalid_content"+    JsInvalidHttpCode -> "js_invalid_http_code"+    JsInvalidSourceEncoding -> "js_invalid_source_encoding"+    JsInvalidSourcemap -> "js_invalid_source"+    JsInvalidSourcemapLocation -> "js_invalid_sourcemap_location"+    JsMissingSource -> "js_no_source"+    JsNoColumn -> "js_no_column"+    JsTooLarge -> "js_too_large"+    JsTooManyRemoteSources -> "js_too_many_sources"+    MissingAttribute -> "missing_attribute"+    NativeBadDsym -> "native_bad_dsym"+    NativeInternalFailure -> "native_internal_failure"+    NativeMissingDsym -> "native_missing_dsym"+    NativeMissingOptionallyBundledDsym -> "native_optionally_bundled_dsym"+    NativeMissingSymbol -> "native_missing_symbol"+    NativeMissingSystemDsym -> "native_missing_system_dsym"+    NativeNoCrashedThread -> "native_no_crashed_thread"+    NativeSimulatorFrame -> "native_simulator_frame"+    NativeSymbolicatorFailed -> "native_symbolicator_failed"+    NativeUnknownImage -> "native_unknown_image"+    PastTimestamp -> "past_timestamp"+    ProguardMissingLineno -> "proguard_missing_lineno"+    ProguardMissingMapping -> "proguard_missing_mapping"+    RestrictedIp -> "restricted_ip"+    SecurityViolation -> "security_violation"+    TooLargeForCache -> "too_large_for_cache"+    UnknownError -> "unknown_error"+    ValueTooLong -> "value_too_long"
+ source/library/Patrol/Type/Event.hs view
@@ -0,0 +1,174 @@+module Patrol.Type.Event where++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.Lazy as LazyByteString+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Data.Time as Time+import qualified Network.HTTP.Client as Client+import qualified Network.HTTP.Types as Http+import qualified Patrol.Constant as Constant+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Extra.List as List+import qualified Patrol.Type.Breadcrumbs as Breadcrumbs+import qualified Patrol.Type.ClientSdkInfo as ClientSdkInfo+import qualified Patrol.Type.Context as Context+import qualified Patrol.Type.DebugMeta as DebugMeta+import qualified Patrol.Type.Dsn as Dsn+import qualified Patrol.Type.EventId as EventId+import qualified Patrol.Type.EventProcessingError as EventProcessingError+import qualified Patrol.Type.EventType as EventType+import qualified Patrol.Type.Exceptions as Exceptions+import qualified Patrol.Type.Level as Level+import qualified Patrol.Type.LogEntry as LogEntry+import qualified Patrol.Type.Platform as Platform+import qualified Patrol.Type.Request as Request+import qualified Patrol.Type.Threads as Threads+import qualified Patrol.Type.TransactionInfo as TransactionInfo+import qualified Patrol.Type.User as User++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#event>+data Event = Event+  { breadcrumbs :: Maybe Breadcrumbs.Breadcrumbs,+    contexts :: Map.Map Text.Text Context.Context,+    debugMeta :: Maybe DebugMeta.DebugMeta,+    dist :: Text.Text,+    environment :: Text.Text,+    errors :: [EventProcessingError.EventProcessingError],+    eventId :: EventId.EventId,+    exception :: Maybe Exceptions.Exceptions,+    extra :: Map.Map Text.Text Aeson.Value,+    fingerprint :: [Text.Text],+    level :: Maybe Level.Level,+    logentry :: Maybe LogEntry.LogEntry,+    logger :: Text.Text,+    modules :: Map.Map Text.Text Text.Text,+    platform :: Maybe Platform.Platform,+    release :: Text.Text,+    request :: Maybe Request.Request,+    sdk :: Maybe ClientSdkInfo.ClientSdkInfo,+    serverName :: Text.Text,+    tags :: Map.Map Text.Text Text.Text,+    threads :: Maybe Threads.Threads,+    timeSpent :: Maybe Time.NominalDiffTime,+    timestamp :: Maybe Time.UTCTime,+    transaction :: Text.Text,+    transactionInfo :: Maybe TransactionInfo.TransactionInfo,+    type_ :: Maybe EventType.EventType,+    user :: Maybe User.User,+    version :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON Event where+  toJSON event =+    Aeson.intoObject+      [ Aeson.pair "breadcrumbs" $ breadcrumbs event,+        Aeson.pair "contexts" $ contexts event,+        Aeson.pair "debug_meta" $ debugMeta event,+        Aeson.pair "dist" $ dist event,+        Aeson.pair "environment" $ environment event,+        Aeson.pair "errors" $ errors event,+        Aeson.pair "exception" $ exception event,+        Aeson.pair "extra" $ extra event,+        Aeson.pair "event_id" $ eventId event,+        Aeson.pair "fingerprint" $ fingerprint event,+        Aeson.pair "level" $ level event,+        Aeson.pair "logentry" $ logentry event,+        Aeson.pair "logger" $ logger event,+        Aeson.pair "modules" $ modules event,+        Aeson.pair "platform" $ platform event,+        Aeson.pair "release" $ release event,+        Aeson.pair "request" $ request event,+        Aeson.pair "sdk" $ sdk event,+        Aeson.pair "server_name" $ serverName event,+        Aeson.pair "tags" $ tags event,+        Aeson.pair "threads" $ threads event,+        Aeson.pair "time_spent" $ timeSpent event,+        Aeson.pair "timestamp" $ timestamp event,+        Aeson.pair "transaction" $ transaction event,+        Aeson.pair "transaction_info" $ transactionInfo event,+        Aeson.pair "type" $ type_ event,+        Aeson.pair "user" $ user event,+        Aeson.pair "version" $ version event+      ]++empty :: Event+empty =+  Event+    { breadcrumbs = Nothing,+      contexts = Map.empty,+      debugMeta = Nothing,+      dist = Text.empty,+      environment = Text.empty,+      errors = [],+      eventId = EventId.empty,+      exception = Nothing,+      extra = Map.empty,+      fingerprint = [],+      level = Nothing,+      logentry = Nothing,+      logger = Text.empty,+      modules = Map.empty,+      platform = Nothing,+      release = Text.empty,+      request = Nothing,+      sdk = Nothing,+      serverName = Text.empty,+      tags = Map.empty,+      threads = Nothing,+      timeSpent = Nothing,+      timestamp = Nothing,+      transaction = Text.empty,+      transactionInfo = Nothing,+      type_ = Nothing,+      user = Nothing,+      version = Text.empty+    }++new :: IO.MonadIO io => io Event+new = do+  theEventId <- EventId.random+  theTimestamp <- IO.liftIO Time.getCurrentTime+  pure+    empty+      { environment = Text.pack "production",+        eventId = theEventId,+        level = Just Level.Error,+        platform = Just Platform.Haskell,+        timestamp = Just theTimestamp,+        type_ = Just EventType.Default,+        version = Constant.sentryVersion+      }++intoRequest :: Catch.MonadThrow m => Dsn.Dsn -> Event -> m Client.Request+intoRequest dsn event = do+  theRequest <-+    Client.parseUrlThrow+      . Text.unpack+      $ mconcat+        [ Dsn.protocol dsn,+          Text.pack "://",+          Dsn.host dsn,+          maybe Text.empty (Text.pack . (:) ':' . show) $ Dsn.port dsn,+          Dsn.path dsn,+          Text.pack "api/",+          Dsn.projectId dsn,+          Text.pack "/store/"+        ]+  let oldHeaders = Client.requestHeaders theRequest+      authorization = Dsn.intoAuthorization dsn+      newHeaders =+        [ (Http.hContentType, Constant.applicationJson),+          (Http.hUserAgent, Text.encodeUtf8 Constant.userAgent),+          (Constant.xSentryAuth, authorization)+        ]+  pure+    theRequest+      { Client.method = Http.methodPost,+        Client.requestBody = Client.RequestBodyBS . LazyByteString.toStrict $ Aeson.encode event,+        Client.requestHeaders = List.insertAll newHeaders oldHeaders+      }
+ source/library/Patrol/Type/EventId.hs view
@@ -0,0 +1,61 @@+module Patrol.Type.EventId where++import qualified Control.Monad as Monad+import qualified Control.Monad.Catch as Catch+import qualified Control.Monad.IO.Class as IO+import qualified Data.Aeson as Aeson+import qualified Data.Text 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+  deriving (Eq, Show)++instance Aeson.FromJSON EventId where+  parseJSON =+    let name = show $ Typeable.typeRep (Typeable.Proxy :: Typeable.Proxy EventId)+     in Aeson.withText name $ maybe (fail $ "invalid " <> name) pure . fromText++instance Aeson.ToJSON EventId where+  toJSON = Aeson.toJSON . intoText++empty :: EventId+empty = fromUuid Uuid.nil++fromUuid :: Uuid.UUID -> EventId+fromUuid = EventId++intoUuid :: EventId -> Uuid.UUID+intoUuid (EventId uuid) = uuid++random :: IO.MonadIO io => io EventId+random = IO.liftIO $ fmap fromUuid Uuid.nextRandom++intoText :: EventId -> Text.Text+intoText eventId =+  let (lo, hi) = Uuid.toWords64 $ intoUuid eventId+   in Text.pack $ Printf.printf "%016x%016x" lo hi++fromText :: Catch.MonadThrow m => Text.Text -> m EventId+fromText t1 = do+  let parse :: (Catch.MonadThrow n, Integral a) => Int -> Text.Text -> n (a, Text.Text)+      parse size text = do+        let (before, after) = Text.splitAt size text+        case Text.compareLength before size of+          GT -> Catch.throwM $ Problem.Problem "impossible"+          LT -> Catch.throwM $ Problem.Problem "not enough input"+          EQ -> case Text.hexadecimal before of+            Left _ -> Catch.throwM $ Problem.Problem "invalid hexadecimal"+            Right (integral, leftover) ->+              if Text.null leftover+                then pure (integral, after)+                else Catch.throwM $ Problem.Problem "invalid hexadecimal"+  (lo, t2) <- parse 16 t1+  (hi, t3) <- parse 16 t2+  Monad.unless (Text.null t3) . Catch.throwM $ Problem.Problem "too much input"+  pure . fromUuid $ Uuid.fromWords64 lo hi
+ source/library/Patrol/Type/EventProcessingError.hs view
@@ -0,0 +1,30 @@+module Patrol.Type.EventProcessingError where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.ErrorType as ErrorType++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#eventprocessingerror>+data EventProcessingError = EventProcessingError+  { name :: Text.Text,+    type_ :: ErrorType.ErrorType,+    value :: Aeson.Value+  }+  deriving (Eq, Show)++instance Aeson.ToJSON EventProcessingError where+  toJSON eventProcessingError =+    Aeson.intoObject+      [ Aeson.pair "name" $ name eventProcessingError,+        Aeson.pair "type" $ type_ eventProcessingError,+        Aeson.pair "value" $ value eventProcessingError+      ]++empty :: EventProcessingError+empty =+  EventProcessingError+    { name = Text.empty,+      type_ = ErrorType.UnknownError,+      value = Aeson.Null+    }
+ source/library/Patrol/Type/EventType.hs view
@@ -0,0 +1,24 @@+module Patrol.Type.EventType where++import qualified Data.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#eventtype>+data EventType+  = Csp+  | Default+  | Error+  | Expectct+  | Expectstaple+  | Hpkp+  | Transaction+  deriving (Eq, Show)++instance Aeson.ToJSON EventType where+  toJSON eventType = Aeson.toJSON $ case eventType of+    Csp -> "csp"+    Default -> "default"+    Error -> "error"+    Expectct -> "expectct"+    Expectstaple -> "expectstaple"+    Hpkp -> "hpkp"+    Transaction -> "transaction"
+ source/library/Patrol/Type/Exception.hs view
@@ -0,0 +1,49 @@+module Patrol.Type.Exception 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.Extra.Aeson as Aeson+import qualified Patrol.Type.Mechanism as Mechanism+import qualified Patrol.Type.Stacktrace as Stacktrace++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#valueclass>+data Exception = Exception+  { mechanism :: Maybe Mechanism.Mechanism,+    module_ :: Text.Text,+    stacktrace :: Maybe Stacktrace.Stacktrace,+    threadId :: Text.Text,+    type_ :: Text.Text,+    value :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON Exception where+  toJSON exception =+    Aeson.intoObject+      [ Aeson.pair "mechanism" $ mechanism exception,+        Aeson.pair "module" $ module_ exception,+        Aeson.pair "stacktrace" $ stacktrace exception,+        Aeson.pair "thread_id" $ threadId exception,+        Aeson.pair "type" $ type_ exception,+        Aeson.pair "value" $ value exception+      ]++empty :: Exception+empty =+  Exception+    { mechanism = Nothing,+      module_ = Text.empty,+      stacktrace = Nothing,+      threadId = Text.empty,+      type_ = Text.empty,+      value = Text.empty+    }++fromSomeException :: Catch.SomeException -> Exception+fromSomeException (Catch.SomeException e) =+  empty+    { type_ = Text.pack . show $ Typeable.typeOf e,+      value = Text.pack $ Catch.displayException e+    }
+ source/library/Patrol/Type/Exceptions.hs view
@@ -0,0 +1,23 @@+module Patrol.Type.Exceptions where++import qualified Data.Aeson as Aeson+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.Exception as Exception++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#exception>+newtype Exceptions = Exceptions+  { values :: [Exception.Exception]+  }+  deriving (Eq, Show)++instance Aeson.ToJSON Exceptions where+  toJSON exceptions =+    Aeson.intoObject+      [ Aeson.pair "values" $ values exceptions+      ]++empty :: Exceptions+empty =+  Exceptions+    { values = []+    }
+ source/library/Patrol/Type/Frame.hs view
@@ -0,0 +1,90 @@+module Patrol.Type.Frame where++import qualified Data.Aeson as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified GHC.Stack as Stack+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.Platform as Platform++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#frame>+data Frame = Frame+  { absPath :: Text.Text,+    addrMode :: Text.Text,+    colno :: Maybe Int,+    contextLine :: Text.Text,+    filename :: Text.Text,+    function :: Text.Text,+    imageAddr :: Text.Text,+    inApp :: Maybe Bool,+    instructionAddr :: Text.Text,+    lineno :: Maybe Int,+    module_ :: Text.Text,+    package :: Text.Text,+    platform :: Maybe Platform.Platform,+    postContext :: [Text.Text],+    preContext :: [Text.Text],+    rawFunction :: Text.Text,+    stackStart :: Maybe Bool,+    symbolAddr :: Text.Text,+    vars :: Map.Map Text.Text Aeson.Value+  }+  deriving (Eq, Show)++instance Aeson.ToJSON Frame where+  toJSON frame =+    Aeson.intoObject+      [ Aeson.pair "abs_path" $ absPath frame,+        Aeson.pair "addr_mode" $ addrMode frame,+        Aeson.pair "colno" $ colno frame,+        Aeson.pair "context_line" $ contextLine frame,+        Aeson.pair "filename" $ filename frame,+        Aeson.pair "function" $ function frame,+        Aeson.pair "image_addr" $ imageAddr frame,+        Aeson.pair "in_app" $ inApp frame,+        Aeson.pair "instruction_addr" $ instructionAddr frame,+        Aeson.pair "lineno" $ lineno frame,+        Aeson.pair "module" $ module_ frame,+        Aeson.pair "package" $ package frame,+        Aeson.pair "platform" $ platform frame,+        Aeson.pair "post_context" $ postContext frame,+        Aeson.pair "pre_context" $ preContext frame,+        Aeson.pair "raw_function" $ rawFunction frame,+        Aeson.pair "stack_start" $ stackStart frame,+        Aeson.pair "symbol_addr" $ symbolAddr frame,+        Aeson.pair "vars" $ vars frame+      ]++empty :: Frame+empty =+  Frame+    { absPath = Text.empty,+      addrMode = Text.empty,+      colno = Nothing,+      contextLine = Text.empty,+      filename = Text.empty,+      function = Text.empty,+      imageAddr = Text.empty,+      inApp = Nothing,+      instructionAddr = Text.empty,+      lineno = Nothing,+      module_ = Text.empty,+      package = Text.empty,+      platform = Nothing,+      postContext = [],+      preContext = [],+      rawFunction = Text.empty,+      stackStart = Nothing,+      symbolAddr = Text.empty,+      vars = Map.empty+    }++fromSrcLoc :: Stack.SrcLoc -> Frame+fromSrcLoc srcLoc =+  empty+    { colno = Just $ Stack.srcLocStartCol srcLoc,+      filename = Text.pack $ Stack.srcLocFile srcLoc,+      lineno = Just $ Stack.srcLocStartLine srcLoc,+      module_ = Text.pack $ Stack.srcLocModule srcLoc,+      package = Text.pack $ Stack.srcLocPackage srcLoc+    }
+ source/library/Patrol/Type/Geo.hs view
@@ -0,0 +1,29 @@+module Patrol.Type.Geo where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#geo>+data Geo = Geo+  { city :: Text.Text,+    countryCode :: Text.Text,+    region :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON Geo where+  toJSON geo =+    Aeson.intoObject+      [ Aeson.pair "city" $ city geo,+        Aeson.pair "country_code" $ countryCode geo,+        Aeson.pair "region" $ region geo+      ]++empty :: Geo+empty =+  Geo+    { city = Text.empty,+      countryCode = Text.empty,+      region = Text.empty+    }
+ source/library/Patrol/Type/GpuContext.hs view
@@ -0,0 +1,62 @@+module Patrol.Type.GpuContext where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#gpucontext>+data GpuContext = GpuContext+  { apiType :: Text.Text,+    graphicsShaderLevel :: Text.Text,+    id :: Aeson.Value,+    maxTextureSize :: Maybe Int,+    memorySize :: Maybe Double,+    multiThreadedRendering :: Maybe Bool,+    name :: Text.Text,+    npotSupport :: Text.Text,+    supportsComputeShaders :: Maybe Bool,+    supportsGeometryShaders :: Maybe Bool,+    supportsRayTracing :: Maybe Bool,+    vendorId :: Text.Text,+    vendorName :: Text.Text,+    version :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON GpuContext where+  toJSON gpuContext =+    Aeson.intoObject+      [ Aeson.pair "api_type" $ apiType gpuContext,+        Aeson.pair "graphics_shader_level" $ graphicsShaderLevel gpuContext,+        Aeson.pair "id" $ Patrol.Type.GpuContext.id gpuContext,+        Aeson.pair "max_texture_size" $ maxTextureSize gpuContext,+        Aeson.pair "memory_size" $ memorySize gpuContext,+        Aeson.pair "multi_threaded_rendering" $ multiThreadedRendering gpuContext,+        Aeson.pair "name" $ name gpuContext,+        Aeson.pair "npot_support" $ npotSupport gpuContext,+        Aeson.pair "supports_compute_shaders" $ supportsComputeShaders gpuContext,+        Aeson.pair "supports_geometry_shaders" $ supportsGeometryShaders gpuContext,+        Aeson.pair "supports_ray_tracing" $ supportsRayTracing gpuContext,+        Aeson.pair "vendor_id" $ vendorId gpuContext,+        Aeson.pair "vendor_name" $ vendorName gpuContext,+        Aeson.pair "version" $ version gpuContext+      ]++empty :: GpuContext+empty =+  GpuContext+    { apiType = Text.empty,+      graphicsShaderLevel = Text.empty,+      Patrol.Type.GpuContext.id = Aeson.Null,+      maxTextureSize = Nothing,+      memorySize = Nothing,+      multiThreadedRendering = Nothing,+      name = Text.empty,+      npotSupport = Text.empty,+      supportsComputeShaders = Nothing,+      supportsGeometryShaders = Nothing,+      supportsRayTracing = Nothing,+      vendorId = Text.empty,+      vendorName = Text.empty,+      version = Text.empty+    }
+ source/library/Patrol/Type/Level.hs view
@@ -0,0 +1,20 @@+module Patrol.Type.Level where++import qualified Data.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#level>+data Level+  = Debug+  | Info+  | Warning+  | Error+  | Fatal+  deriving (Eq, Show)++instance Aeson.ToJSON Level where+  toJSON level = Aeson.toJSON $ case level of+    Debug -> "debug"+    Info -> "info"+    Warning -> "warning"+    Error -> "error"+    Fatal -> "fatal"
+ source/library/Patrol/Type/LogEntry.hs view
@@ -0,0 +1,29 @@+module Patrol.Type.LogEntry where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#logentry>+data LogEntry = LogEntry+  { formatted :: Text.Text,+    message :: Text.Text,+    params :: Aeson.Value+  }+  deriving (Eq, Show)++instance Aeson.ToJSON LogEntry where+  toJSON logEntry =+    Aeson.intoObject+      [ Aeson.pair "formatted" $ formatted logEntry,+        Aeson.pair "message" $ message logEntry,+        Aeson.pair "params" $ params logEntry+      ]++empty :: LogEntry+empty =+  LogEntry+    { formatted = Text.empty,+      message = Text.empty,+      params = Aeson.Null+    }
+ source/library/Patrol/Type/MachException.hs view
@@ -0,0 +1,32 @@+module Patrol.Type.MachException where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#machexception>+data MachException = MachException+  { code :: Maybe Int,+    exception :: Maybe Int,+    name :: Text.Text,+    subcode :: Maybe Int+  }+  deriving (Eq, Show)++instance Aeson.ToJSON MachException where+  toJSON machException =+    Aeson.intoObject+      [ Aeson.pair "code" $ code machException,+        Aeson.pair "exception" $ exception machException,+        Aeson.pair "name" $ name machException,+        Aeson.pair "subcode" $ subcode machException+      ]++empty :: MachException+empty =+  MachException+    { code = Nothing,+      exception = Nothing,+      name = Text.empty,+      subcode = Nothing+    }
+ source/library/Patrol/Type/Mechanism.hs view
@@ -0,0 +1,43 @@+module Patrol.Type.Mechanism where++import qualified Data.Aeson as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.MechanismMeta as MechanismMeta++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#mechanism>+data Mechanism = Mechanism+  { data_ :: Map.Map Text.Text Aeson.Value,+    description :: Text.Text,+    handled :: Maybe Bool,+    helpLink :: Text.Text,+    meta :: Maybe MechanismMeta.MechanismMeta,+    synthetic :: Maybe Bool,+    type_ :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON Mechanism where+  toJSON mechanism =+    Aeson.intoObject+      [ Aeson.pair "data" $ data_ mechanism,+        Aeson.pair "description" $ description mechanism,+        Aeson.pair "handled" $ handled mechanism,+        Aeson.pair "help_link" $ helpLink mechanism,+        Aeson.pair "meta" $ meta mechanism,+        Aeson.pair "synthetic" $ synthetic mechanism,+        Aeson.pair "type" $ type_ mechanism+      ]++empty :: Mechanism+empty =+  Mechanism+    { data_ = Map.empty,+      description = Text.empty,+      handled = Nothing,+      helpLink = Text.empty,+      meta = Nothing,+      synthetic = Nothing,+      type_ = Text.empty+    }
+ source/library/Patrol/Type/MechanismMeta.hs view
@@ -0,0 +1,35 @@+module Patrol.Type.MechanismMeta where++import qualified Data.Aeson as Aeson+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.CError as CError+import qualified Patrol.Type.MachException as MachException+import qualified Patrol.Type.NsError as NsError+import qualified Patrol.Type.PosixSignal as PosixSignal++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#mechanismmeta>+data MechanismMeta = MechanismMeta+  { errno :: Maybe CError.CError,+    machException :: Maybe MachException.MachException,+    nsError :: Maybe NsError.NsError,+    signal :: Maybe PosixSignal.PosixSignal+  }+  deriving (Eq, Show)++instance Aeson.ToJSON MechanismMeta where+  toJSON mechanismMeta =+    Aeson.intoObject+      [ Aeson.pair "errno" $ errno mechanismMeta,+        Aeson.pair "mach_exception" $ machException mechanismMeta,+        Aeson.pair "ns_error" $ nsError mechanismMeta,+        Aeson.pair "signal" $ signal mechanismMeta+      ]++empty :: MechanismMeta+empty =+  MechanismMeta+    { errno = Nothing,+      machException = Nothing,+      nsError = Nothing,+      signal = Nothing+    }
+ source/library/Patrol/Type/NativeDebugImage.hs view
@@ -0,0 +1,47 @@+module Patrol.Type.NativeDebugImage where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#nativedebugimage>+data NativeDebugImage = NativeDebugImage+  { arch :: Text.Text,+    codeFile :: Text.Text,+    codeId :: Text.Text,+    debugFile :: Text.Text,+    debugId :: Text.Text,+    imageAddr :: Text.Text,+    imageSize :: Maybe Int,+    imageVmaddr :: Text.Text,+    type_ :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON NativeDebugImage where+  toJSON nativeDebugImage =+    Aeson.intoObject+      [ Aeson.pair "arch" $ arch nativeDebugImage,+        Aeson.pair "code_file" $ codeFile nativeDebugImage,+        Aeson.pair "code_id" $ codeId nativeDebugImage,+        Aeson.pair "debug_file" $ debugFile nativeDebugImage,+        Aeson.pair "debug_id" $ debugId nativeDebugImage,+        Aeson.pair "image_addr" $ imageAddr nativeDebugImage,+        Aeson.pair "image_size" $ imageSize nativeDebugImage,+        Aeson.pair "image_vmaddr" $ imageVmaddr nativeDebugImage,+        Aeson.pair "type" $ type_ nativeDebugImage+      ]++empty :: NativeDebugImage+empty =+  NativeDebugImage+    { arch = Text.empty,+      codeFile = Text.empty,+      codeId = Text.empty,+      debugFile = Text.empty,+      debugId = Text.empty,+      imageAddr = Text.empty,+      imageSize = Nothing,+      imageVmaddr = Text.empty,+      type_ = Text.empty+    }
+ source/library/Patrol/Type/NsError.hs view
@@ -0,0 +1,26 @@+module Patrol.Type.NsError where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#nserror>+data NsError = NsError+  { code :: Maybe Int,+    domain :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON NsError where+  toJSON nsError =+    Aeson.intoObject+      [ Aeson.pair "code" $ code nsError,+        Aeson.pair "domain" $ domain nsError+      ]++empty :: NsError+empty =+  NsError+    { code = Nothing,+      domain = Text.empty+    }
+ source/library/Patrol/Type/OsContext.hs view
@@ -0,0 +1,38 @@+module Patrol.Type.OsContext where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#oscontext>+data OsContext = OsContext+  { build :: Text.Text,+    kernelVersion :: Text.Text,+    name :: Text.Text,+    rawDescription :: Text.Text,+    rooted :: Maybe Bool,+    version :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON OsContext where+  toJSON osContext =+    Aeson.intoObject+      [ Aeson.pair "build" $ build osContext,+        Aeson.pair "kernel_version" $ kernelVersion osContext,+        Aeson.pair "name" $ name osContext,+        Aeson.pair "raw_description" $ rawDescription osContext,+        Aeson.pair "rooted" $ rooted osContext,+        Aeson.pair "version" $ version osContext+      ]++empty :: OsContext+empty =+  OsContext+    { build = Text.empty,+      kernelVersion = Text.empty,+      name = Text.empty,+      rawDescription = Text.empty,+      rooted = Nothing,+      version = Text.empty+    }
+ source/library/Patrol/Type/Platform.hs view
@@ -0,0 +1,47 @@+module Patrol.Type.Platform where++import qualified Data.Aeson as Aeson++data Platform+  = As3+  | C+  | Cfml+  | Cocoa+  | Csharp+  | Elixir+  | Haskell+  | Go+  | Groovy+  | Java+  | Javascript+  | Native+  | Node+  | Objc+  | Other+  | Perl+  | Php+  | Python+  | Ruby+  deriving (Eq, Show)++instance Aeson.ToJSON Platform where+  toJSON platform = Aeson.toJSON $ case platform of+    As3 -> "as3"+    C -> "c"+    Cfml -> "cfml"+    Cocoa -> "cocoa"+    Csharp -> "csharp"+    Elixir -> "elixir"+    Haskell -> "haskell"+    Go -> "go"+    Groovy -> "groovy"+    Java -> "java"+    Javascript -> "javascript"+    Native -> "native"+    Node -> "node"+    Objc -> "objc"+    Other -> "other"+    Perl -> "perl"+    Php -> "php"+    Python -> "python"+    Ruby -> "ruby"
+ source/library/Patrol/Type/PosixSignal.hs view
@@ -0,0 +1,32 @@+module Patrol.Type.PosixSignal where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#posixsignal>+data PosixSignal = PosixSignal+  { code :: Maybe Int,+    codeName :: Text.Text,+    name :: Text.Text,+    number :: Maybe Int+  }+  deriving (Eq, Show)++instance Aeson.ToJSON PosixSignal where+  toJSON posixSignal =+    Aeson.intoObject+      [ Aeson.pair "code" $ code posixSignal,+        Aeson.pair "code_name" $ codeName posixSignal,+        Aeson.pair "name" $ name posixSignal,+        Aeson.pair "number" $ number posixSignal+      ]++empty :: PosixSignal+empty =+  PosixSignal+    { code = Nothing,+      codeName = Text.empty,+      name = Text.empty,+      number = Nothing+    }
+ source/library/Patrol/Type/ProguardDebugImage.hs view
@@ -0,0 +1,23 @@+module Patrol.Type.ProguardDebugImage where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#proguarddebugimage>+newtype ProguardDebugImage = ProguardDebugImage+  { uuid :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON ProguardDebugImage where+  toJSON proguardDebugImage =+    Aeson.intoObject+      [ Aeson.pair "uuid" $ uuid proguardDebugImage+      ]++empty :: ProguardDebugImage+empty =+  ProguardDebugImage+    { uuid = Text.empty+    }
+ source/library/Patrol/Type/Request.hs view
@@ -0,0 +1,48 @@+module Patrol.Type.Request where++import qualified Data.Aeson as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#request>+data Request = Request+  { cookies :: Map.Map Text.Text Text.Text,+    data_ :: Aeson.Value,+    env :: Map.Map Text.Text Aeson.Value,+    fragment :: Text.Text,+    headers :: Map.Map Text.Text Text.Text,+    inferredContentType :: Text.Text,+    method :: Text.Text,+    queryString :: Map.Map Text.Text Text.Text,+    url :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON Request where+  toJSON request =+    Aeson.intoObject+      [ Aeson.pair "cookies" $ cookies request,+        Aeson.pair "data" $ data_ request,+        Aeson.pair "env" $ env request,+        Aeson.pair "fragment" $ fragment request,+        Aeson.pair "headers" $ headers request,+        Aeson.pair "inferred_content_type" $ inferredContentType request,+        Aeson.pair "method" $ method request,+        Aeson.pair "query_string" $ queryString request,+        Aeson.pair "url" $ url request+      ]++empty :: Request+empty =+  Request+    { cookies = Map.empty,+      data_ = Aeson.Null,+      env = Map.empty,+      fragment = Text.empty,+      headers = Map.empty,+      inferredContentType = Text.empty,+      method = Text.empty,+      queryString = Map.empty,+      url = Text.empty+    }
+ source/library/Patrol/Type/Response.hs view
@@ -0,0 +1,21 @@+module Patrol.Type.Response where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.Key as Key+import qualified Data.Typeable as Typeable+import qualified Patrol.Type.EventId as EventId++newtype Response = Response+  { id :: EventId.EventId+  }+  deriving (Eq, Show)++instance Aeson.FromJSON Response where+  parseJSON =+    let name = show $ Typeable.typeRep (Typeable.Proxy :: Typeable.Proxy Response)+     in Aeson.withObject name $ \object -> do+          theId <- object Aeson..: Key.fromString "id"+          pure+            Response+              { Patrol.Type.Response.id = theId+              }
+ source/library/Patrol/Type/RuntimeContext.hs view
@@ -0,0 +1,32 @@+module Patrol.Type.RuntimeContext where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#runtimecontext>+data RuntimeContext = RuntimeContext+  { build :: Text.Text,+    name :: Text.Text,+    rawDescription :: Text.Text,+    version :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON RuntimeContext where+  toJSON runtimeContext =+    Aeson.intoObject+      [ Aeson.pair "build" $ build runtimeContext,+        Aeson.pair "name" $ name runtimeContext,+        Aeson.pair "raw_description" $ rawDescription runtimeContext,+        Aeson.pair "version" $ version runtimeContext+      ]++empty :: RuntimeContext+empty =+  RuntimeContext+    { build = Text.empty,+      name = Text.empty,+      rawDescription = Text.empty,+      version = Text.empty+    }
+ source/library/Patrol/Type/SpanStatus.hs view
@@ -0,0 +1,44 @@+module Patrol.Type.SpanStatus where++import qualified Data.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#spanstatus>+data SpanStatus+  = Aborted+  | AlreadyExists+  | Cancelled+  | DataLoss+  | DeadlineExceeded+  | FailedPrecondition+  | InternalError+  | InvalidArgument+  | NotFound+  | Ok+  | OutOfRange+  | PermissionDenied+  | ResourceExhausted+  | Unauthenticated+  | Unavailable+  | Unimplemented+  | Unknown+  deriving (Eq, Show)++instance Aeson.ToJSON SpanStatus where+  toJSON spanStatus = Aeson.toJSON $ case spanStatus of+    Aborted -> "aborted"+    AlreadyExists -> "already_exists"+    Cancelled -> "cancelled"+    DataLoss -> "data_loss"+    DeadlineExceeded -> "deadline_exceeded"+    FailedPrecondition -> "failed_precondition"+    InternalError -> "internal_error"+    InvalidArgument -> "invalid_argument"+    NotFound -> "not_found"+    Ok -> "ok"+    OutOfRange -> "out_of_range"+    PermissionDenied -> "permission_denied"+    ResourceExhausted -> "resource_exhausted"+    Unauthenticated -> "unauthenticated"+    Unavailable -> "unavailable"+    Unimplemented -> "unimplemented"+    Unknown -> "unknown"
+ source/library/Patrol/Type/Stacktrace.hs view
@@ -0,0 +1,37 @@+module Patrol.Type.Stacktrace where++import qualified Data.Aeson as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified GHC.Stack as Stack+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.Frame as Frame++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#stacktrace>+data Stacktrace = Stacktrace+  { frames :: [Frame.Frame],+    registers :: Map.Map Text.Text Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON Stacktrace where+  toJSON stacktrace =+    Aeson.intoObject+      [ Aeson.pair "frames" $ frames stacktrace,+        Aeson.pair "registers" $ registers stacktrace+      ]++empty :: Stacktrace+empty =+  Stacktrace+    { frames = [],+      registers = Map.empty+    }++fromCallStack :: Stack.CallStack -> Stacktrace+fromCallStack =+  let intoFrame string srcLoc =+        (Frame.fromSrcLoc srcLoc)+          { Frame.function = Text.pack string+          }+   in flip Stacktrace Map.empty . fmap (uncurry intoFrame) . reverse . Stack.getCallStack
+ source/library/Patrol/Type/SystemSdkInfo.hs view
@@ -0,0 +1,32 @@+module Patrol.Type.SystemSdkInfo where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#systemsdkinfo>+data SystemSdkInfo = SystemSdkInfo+  { sdkName :: Text.Text,+    versionMajor :: Maybe Int,+    versionMinor :: Maybe Int,+    versionPatchlevel :: Maybe Int+  }+  deriving (Eq, Show)++instance Aeson.ToJSON SystemSdkInfo where+  toJSON systemSdkInfo =+    Aeson.intoObject+      [ Aeson.pair "sdk_name" $ sdkName systemSdkInfo,+        Aeson.pair "version_major" $ versionMajor systemSdkInfo,+        Aeson.pair "version_minor" $ versionMinor systemSdkInfo,+        Aeson.pair "version_patchlevel" $ versionPatchlevel systemSdkInfo+      ]++empty :: SystemSdkInfo+empty =+  SystemSdkInfo+    { sdkName = Text.empty,+      versionMajor = Nothing,+      versionMinor = Nothing,+      versionPatchlevel = Nothing+    }
+ source/library/Patrol/Type/Thread.hs view
@@ -0,0 +1,36 @@+module Patrol.Type.Thread where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.Stacktrace as Stacktrace++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#thread>+data Thread = Thread+  { crashed :: Maybe Bool,+    current :: Maybe Bool,+    id :: Text.Text,+    name :: Text.Text,+    stacktrace :: Maybe Stacktrace.Stacktrace+  }+  deriving (Eq, Show)++instance Aeson.ToJSON Thread where+  toJSON thread =+    Aeson.intoObject+      [ Aeson.pair "crashed" $ crashed thread,+        Aeson.pair "current" $ current thread,+        Aeson.pair "id" $ Patrol.Type.Thread.id thread,+        Aeson.pair "name" $ name thread,+        Aeson.pair "stacktrace" $ stacktrace thread+      ]++empty :: Thread+empty =+  Thread+    { crashed = Nothing,+      current = Nothing,+      Patrol.Type.Thread.id = Text.empty,+      name = Text.empty,+      stacktrace = Nothing+    }
+ source/library/Patrol/Type/Threads.hs view
@@ -0,0 +1,23 @@+module Patrol.Type.Threads where++import qualified Data.Aeson as Aeson+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.Thread as Thread++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#threads>+newtype Threads = Threads+  { values :: [Thread.Thread]+  }+  deriving (Eq, Show)++instance Aeson.ToJSON Threads where+  toJSON threads =+    Aeson.intoObject+      [ Aeson.pair "values" $ values threads+      ]++empty :: Threads+empty =+  Threads+    { values = []+    }
+ source/library/Patrol/Type/TraceContext.hs view
@@ -0,0 +1,39 @@+module Patrol.Type.TraceContext where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.SpanStatus as SpanStatus++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#tracecontext>+data TraceContext = TraceContext+  { exclusiveTime :: Maybe Int,+    op :: Text.Text,+    parentSpanId :: Text.Text,+    spanId :: Text.Text,+    status :: Maybe SpanStatus.SpanStatus,+    traceId :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON TraceContext where+  toJSON traceContext =+    Aeson.intoObject+      [ Aeson.pair "exclusive_time" $ exclusiveTime traceContext,+        Aeson.pair "op" $ op traceContext,+        Aeson.pair "parent_span_id" $ parentSpanId traceContext,+        Aeson.pair "span_id" $ spanId traceContext,+        Aeson.pair "status" $ status traceContext,+        Aeson.pair "trace_id" $ traceId traceContext+      ]++empty :: TraceContext+empty =+  TraceContext+    { exclusiveTime = Nothing,+      op = Text.empty,+      parentSpanId = Text.empty,+      spanId = Text.empty,+      status = Nothing,+      traceId = Text.empty+    }
+ source/library/Patrol/Type/TransactionInfo.hs view
@@ -0,0 +1,27 @@+module Patrol.Type.TransactionInfo where++import qualified Data.Aeson as Aeson+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.TransactionSource as TransactionSource++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#transactioninfo>+data TransactionInfo = TransactionInfo+  { original :: Text.Text,+    source :: Maybe TransactionSource.TransactionSource+  }+  deriving (Eq, Show)++instance Aeson.ToJSON TransactionInfo where+  toJSON transactionInfo =+    Aeson.intoObject+      [ Aeson.pair "original" $ original transactionInfo,+        Aeson.pair "source" $ source transactionInfo+      ]++empty :: TransactionInfo+empty =+  TransactionInfo+    { original = Text.empty,+      source = Nothing+    }
+ source/library/Patrol/Type/TransactionSource.hs view
@@ -0,0 +1,24 @@+module Patrol.Type.TransactionSource where++import qualified Data.Aeson as Aeson++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#transactionsource>+data TransactionSource+  = Component+  | Custom+  | Route+  | Task+  | Url+  | Unknown+  | View+  deriving (Eq, Show)++instance Aeson.ToJSON TransactionSource where+  toJSON transactionSource = Aeson.toJSON $ case transactionSource of+    Component -> "component"+    Custom -> "custom"+    Route -> "route"+    Task -> "task"+    Url -> "url"+    Unknown -> "unknown"+    View -> "view"
+ source/library/Patrol/Type/User.hs view
@@ -0,0 +1,46 @@+module Patrol.Type.User where++import qualified Data.Aeson as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Patrol.Extra.Aeson as Aeson+import qualified Patrol.Type.Geo as Geo++-- | <https://develop.sentry.dev/sdk/event-payloads/types/#user>+data User = User+  { data_ :: Map.Map Text.Text Aeson.Value,+    email :: Text.Text,+    geo :: Maybe Geo.Geo,+    id :: Text.Text,+    ipAddress :: Text.Text,+    name :: Text.Text,+    segment :: Text.Text,+    username :: Text.Text+  }+  deriving (Eq, Show)++instance Aeson.ToJSON User where+  toJSON user =+    Aeson.intoObject+      [ Aeson.pair "data" $ data_ user,+        Aeson.pair "email" $ email user,+        Aeson.pair "geo" $ geo user,+        Aeson.pair "id" $ Patrol.Type.User.id user,+        Aeson.pair "ip_address" $ ipAddress user,+        Aeson.pair "name" $ name user,+        Aeson.pair "segment" $ segment user,+        Aeson.pair "username" $ username user+      ]++empty :: User+empty =+  User+    { data_ = Map.empty,+      email = Text.empty,+      geo = Nothing,+      Patrol.Type.User.id = Text.empty,+      ipAddress = Text.empty,+      name = Text.empty,+      segment = Text.empty,+      username = Text.empty+    }
+ source/library/Patrol/Version.hs view
@@ -0,0 +1,14 @@+module Patrol.Version where++import qualified Data.Text as Text+import qualified Data.Version as Version+import qualified Paths_patrol as Package++string :: String+string = Version.showVersion version++text :: Text.Text+text = Text.pack string++version :: Version.Version+version = Package.version
+ source/test-suite/Main.hs view
@@ -0,0 +1,5 @@+import qualified PatrolSpec+import qualified Test.Hspec as Hspec++main :: IO ()+main = Hspec.hspec $ Hspec.parallel PatrolSpec.spec
+ source/test-suite/Patrol/ClientSpec.hs view
@@ -0,0 +1,8 @@+module Patrol.ClientSpec where++import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Client" $ do+  Hspec.it "needs tests" $ do+    Hspec.pending
+ source/test-suite/Patrol/ConstantSpec.hs view
@@ -0,0 +1,25 @@+module Patrol.ConstantSpec where++import qualified Data.CaseInsensitive as CI+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Patrol.Constant as Constant+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Constant" $ do+  Hspec.describe "applicationJson" $ do+    Hspec.it "is correct" $ do+      Constant.applicationJson `Hspec.shouldBe` Text.encodeUtf8 (Text.pack "application/json")++  Hspec.describe "sentryVersion" $ do+    Hspec.it "is correct" $ do+      Constant.sentryVersion `Hspec.shouldBe` Text.singleton '7'++  Hspec.describe "userAgent" $ do+    Hspec.it "is correct" $ do+      Constant.userAgent `Hspec.shouldSatisfy` Text.isPrefixOf (Text.pack "patrol/")++  Hspec.describe "xSentryAuth" $ do+    Hspec.it "is correct" $ do+      Constant.xSentryAuth `Hspec.shouldBe` CI.mk (Text.encodeUtf8 $ Text.pack "X-Sentry-Auth")
+ source/test-suite/Patrol/Extra/AesonSpec.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Extra.AesonSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.Key as Key+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Map as Map+import qualified Patrol.Extra.Aeson as Extra+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Extra.Aeson" $ do+  Hspec.describe "intoObject" $ do+    Hspec.it "works with an empty object" $ do+      Extra.intoObject [] `Hspec.shouldBe` [Aeson.aesonQQ| {} |]++    Hspec.it "strips null" $ do+      Extra.intoObject [Extra.pair "k" Aeson.Null] `Hspec.shouldBe` [Aeson.aesonQQ| {} |]++    Hspec.it "does not strip false" $ do+      Extra.intoObject [Extra.pair "k" False] `Hspec.shouldBe` [Aeson.aesonQQ| { "k": false } |]++    Hspec.it "does not strip zero" $ do+      Extra.intoObject [Extra.pair "k" (0 :: Int)] `Hspec.shouldBe` [Aeson.aesonQQ| { "k": 0 } |]++    Hspec.it "strips empty string" $ do+      Extra.intoObject [Extra.pair "k" ""] `Hspec.shouldBe` [Aeson.aesonQQ| {} |]++    Hspec.it "strips empty array" $ do+      Extra.intoObject [Extra.pair "k" (mempty :: [Int])] `Hspec.shouldBe` [Aeson.aesonQQ| {} |]++    Hspec.it "strips empty object" $ do+      Extra.intoObject [Extra.pair "k" (mempty :: Map.Map String Int)] `Hspec.shouldBe` [Aeson.aesonQQ| {} |]++  Hspec.describe "isEmpty" $ do+    Hspec.it "is true for null" $ do+      Aeson.Null `Hspec.shouldSatisfy` Extra.isEmpty++    Hspec.it "is false for false" $ do+      Aeson.Bool False `Hspec.shouldNotSatisfy` Extra.isEmpty++    Hspec.it "is false for zero" $ do+      Aeson.Number 0 `Hspec.shouldNotSatisfy` Extra.isEmpty++    Hspec.it "is true for an empty string" $ do+      Aeson.String mempty `Hspec.shouldSatisfy` Extra.isEmpty++    Hspec.it "is true for an empty array" $ do+      Aeson.Array mempty `Hspec.shouldSatisfy` Extra.isEmpty++    Hspec.it "is true for an empty object" $ do+      Aeson.Object mempty `Hspec.shouldSatisfy` Extra.isEmpty++  Hspec.describe "pair" $ do+    Hspec.it "works" $ do+      Extra.pair "key" False `Hspec.shouldBe` (Key.fromString "key", Aeson.Bool False)
+ source/test-suite/Patrol/Extra/ListSpec.hs view
@@ -0,0 +1,59 @@+module Patrol.Extra.ListSpec where++import qualified Data.Char as Char+import qualified Data.Function as Function+import qualified Patrol.Extra.List as Extra+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Extra.List" $ do+  Hspec.describe "insertAll" $ do+    Hspec.it "handles empty lists" $ do+      Extra.insertAll [] [] `Hspec.shouldBe` ([] :: [(Char, Bool)])++    Hspec.it "inserts into an empty list" $ do+      Extra.insertAll [('a', False)] [] `Hspec.shouldBe` [('a', False)]++    Hspec.it "handles an empty list of insertions" $ do+      Extra.insertAll [] [('a', True)] `Hspec.shouldBe` [('a', True)]++    Hspec.it "prefers the original element" $ do+      Extra.insertAll [('a', False)] [('a', True)] `Hspec.shouldBe` [('a', True)]++    Hspec.it "handles duplicate insertions" $ do+      Extra.insertAll [('a', False), ('a', True)] [] `Hspec.shouldBe` [('a', True)]++    Hspec.it "inserts into a non-empty list" $ do+      Extra.insertAll [('a', False)] [('b', True)] `Hspec.shouldBe` [('b', True), ('a', False)]++    Hspec.it "does not modify the original list" $ do+      Extra.insertAll [] [('a', False), ('a', True)] `Hspec.shouldBe` [('a', False), ('a', True)]++  Hspec.describe "insert" $ do+    Hspec.it "inserts into an empty list" $ do+      Extra.insert ('a', False) [] `Hspec.shouldBe` [('a', False)]++    Hspec.it "prefers the original element" $ do+      Extra.insert ('a', False) [('a', True)] `Hspec.shouldBe` [('a', True)]++    Hspec.it "inserts into a non-empty list" $ do+      Extra.insert ('a', False) [('b', True)] `Hspec.shouldBe` [('b', True), ('a', False)]++  Hspec.describe "insertBy" $ do+    Hspec.it "inserts into an empty list" $ do+      Extra.insertBy (==) 'a' "" `Hspec.shouldBe` "a"++    Hspec.it "does not insert a duplicate" $ do+      Extra.insertBy (==) 'a' "a" `Hspec.shouldBe` "a"++    Hspec.it "does not insert a duplicate earlier" $ do+      Extra.insertBy (==) 'a' "ab" `Hspec.shouldBe` "ab"++    Hspec.it "does not insert a duplicate later" $ do+      Extra.insertBy (==) 'a' "ba" `Hspec.shouldBe` "ba"++    Hspec.it "prefers the original element" $ do+      Extra.insertBy (Function.on (==) Char.toLower) 'a' "A" `Hspec.shouldBe` "A"++    Hspec.it "inserts into a non-empty list" $ do+      Extra.insertBy (==) 'a' "b" `Hspec.shouldBe` "ba"
+ source/test-suite/Patrol/Type/AppContextSpec.hs view
@@ -0,0 +1,58 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.AppContextSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Data.Time as Time+import qualified Patrol.Type.AppContext as AppContext+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.AppContext" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let appContext = AppContext.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON appContext `Hspec.shouldBe` json++    Hspec.it "works with an app build" $ do+      let appContext = AppContext.empty {AppContext.appBuild = Text.pack "example-app-build"}+          json = [Aeson.aesonQQ| { "app_build": "example-app-build" } |]+      Aeson.toJSON appContext `Hspec.shouldBe` json++    Hspec.it "works with an app identifier" $ do+      let appContext = AppContext.empty {AppContext.appIdentifier = Text.pack "example-app-identifier"}+          json = [Aeson.aesonQQ| { "app_identifier": "example-app-identifier" } |]+      Aeson.toJSON appContext `Hspec.shouldBe` json++    Hspec.it "works with an app memory" $ do+      let appContext = AppContext.empty {AppContext.appMemory = Just 0}+          json = [Aeson.aesonQQ| { "app_memory": 0 } |]+      Aeson.toJSON appContext `Hspec.shouldBe` json++    Hspec.it "works with an app name" $ do+      let appContext = AppContext.empty {AppContext.appName = Text.pack "example-app-name"}+          json = [Aeson.aesonQQ| { "app_name": "example-app-name" } |]+      Aeson.toJSON appContext `Hspec.shouldBe` json++    Hspec.it "works with an app start time" $ do+      let appContext = AppContext.empty {AppContext.appStartTime = Just $ Time.UTCTime (Time.fromGregorian 1970 1 1) 0}+          json = [Aeson.aesonQQ| { "app_start_time": "1970-01-01T00:00:00Z" } |]+      Aeson.toJSON appContext `Hspec.shouldBe` json++    Hspec.it "works with an app version" $ do+      let appContext = AppContext.empty {AppContext.appVersion = Text.pack "example-app-version"}+          json = [Aeson.aesonQQ| { "app_version": "example-app-version" } |]+      Aeson.toJSON appContext `Hspec.shouldBe` json++    Hspec.it "works with a build type" $ do+      let appContext = AppContext.empty {AppContext.buildType = Text.pack "example-build-type"}+          json = [Aeson.aesonQQ| { "build_type": "example-build-type" } |]+      Aeson.toJSON appContext `Hspec.shouldBe` json++    Hspec.it "works with a device app hash" $ do+      let appContext = AppContext.empty {AppContext.deviceAppHash = Text.pack "example-device-app-hash"}+          json = [Aeson.aesonQQ| { "device_app_hash": "example-device-app-hash" } |]+      Aeson.toJSON appContext `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/AppleDebugImageSpec.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.AppleDebugImageSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.AppleDebugImage as AppleDebugImage+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.AppleDebugImage" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let appleDebugImage = AppleDebugImage.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON appleDebugImage `Hspec.shouldBe` json++    Hspec.it "works with an architecture" $ do+      let appleDebugImage = AppleDebugImage.empty {AppleDebugImage.arch = Text.pack "example-arch"}+          json = [Aeson.aesonQQ| { "arch": "example-arch" } |]+      Aeson.toJSON appleDebugImage `Hspec.shouldBe` json++    Hspec.it "works with a CPU subtype" $ do+      let appleDebugImage = AppleDebugImage.empty {AppleDebugImage.cpuSubtype = Just 0}+          json = [Aeson.aesonQQ| { "cpu_subtype": 0 } |]+      Aeson.toJSON appleDebugImage `Hspec.shouldBe` json++    Hspec.it "works with a CPU type" $ do+      let appleDebugImage = AppleDebugImage.empty {AppleDebugImage.cpuType = Just 0}+          json = [Aeson.aesonQQ| { "cpu_type": 0 } |]+      Aeson.toJSON appleDebugImage `Hspec.shouldBe` json++    Hspec.it "works with an image address" $ do+      let appleDebugImage = AppleDebugImage.empty {AppleDebugImage.imageAddr = Text.pack "example-image-addr"}+          json = [Aeson.aesonQQ| { "image_addr": "example-image-addr" } |]+      Aeson.toJSON appleDebugImage `Hspec.shouldBe` json++    Hspec.it "works with an image size" $ do+      let appleDebugImage = AppleDebugImage.empty {AppleDebugImage.imageSize = Just 0}+          json = [Aeson.aesonQQ| { "image_size": 0 } |]+      Aeson.toJSON appleDebugImage `Hspec.shouldBe` json++    Hspec.it "works with an image VM address" $ do+      let appleDebugImage = AppleDebugImage.empty {AppleDebugImage.imageVmaddr = Text.pack "example-image-vmaddr"}+          json = [Aeson.aesonQQ| { "image_vmaddr": "example-image-vmaddr" } |]+      Aeson.toJSON appleDebugImage `Hspec.shouldBe` json++    Hspec.it "works with a name" $ do+      let appleDebugImage = AppleDebugImage.empty {AppleDebugImage.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON appleDebugImage `Hspec.shouldBe` json++    Hspec.it "works with a UUID" $ do+      let appleDebugImage = AppleDebugImage.empty {AppleDebugImage.uuid = Text.pack "example-uuid"}+          json = [Aeson.aesonQQ| { "uuid": "example-uuid" } |]+      Aeson.toJSON appleDebugImage `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/BreadcrumbSpec.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.BreadcrumbSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Data.Time as Time+import qualified Patrol.Type.Breadcrumb as Breadcrumb+import qualified Patrol.Type.BreadcrumbType as BreadcrumbType+import qualified Patrol.Type.EventId as EventId+import qualified Patrol.Type.Level as Level+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Breadcrumb" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let breadcrumb = Breadcrumb.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON breadcrumb `Hspec.shouldBe` json++    Hspec.it "works with a category" $ do+      let breadcrumb = Breadcrumb.empty {Breadcrumb.category = Text.pack "example-category"}+          json = [Aeson.aesonQQ| { "category": "example-category" } |]+      Aeson.toJSON breadcrumb `Hspec.shouldBe` json++    Hspec.it "works with some data" $ do+      let breadcrumb = Breadcrumb.empty {Breadcrumb.data_ = Map.singleton (Text.pack "example-data") (Aeson.Bool True)}+          json = [Aeson.aesonQQ| { "data": { "example-data": true } } |]+      Aeson.toJSON breadcrumb `Hspec.shouldBe` json++    Hspec.it "works with an event ID" $ do+      let breadcrumb = Breadcrumb.empty {Breadcrumb.eventId = Just EventId.empty}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000" } |]+      Aeson.toJSON breadcrumb `Hspec.shouldBe` json++    Hspec.it "works with a level" $ do+      let breadcrumb = Breadcrumb.empty {Breadcrumb.level = Just Level.Debug}+          json = [Aeson.aesonQQ| { "level": "debug" } |]+      Aeson.toJSON breadcrumb `Hspec.shouldBe` json++    Hspec.it "works with a message" $ do+      let breadcrumb = Breadcrumb.empty {Breadcrumb.message = Text.pack "example-message"}+          json = [Aeson.aesonQQ| { "message": "example-message" } |]+      Aeson.toJSON breadcrumb `Hspec.shouldBe` json++    Hspec.it "works with a timestamp" $ do+      let breadcrumb = Breadcrumb.empty {Breadcrumb.timestamp = Just $ Time.UTCTime (Time.fromGregorian 1970 1 1) 0}+          json = [Aeson.aesonQQ| { "timestamp": "1970-01-01T00:00:00Z" } |]+      Aeson.toJSON breadcrumb `Hspec.shouldBe` json++    Hspec.it "works with a type" $ do+      let breadcrumb = Breadcrumb.empty {Breadcrumb.type_ = Just BreadcrumbType.Default}+          json = [Aeson.aesonQQ| { "type": "default" } |]+      Aeson.toJSON breadcrumb `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/BreadcrumbTypeSpec.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.BreadcrumbTypeSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Patrol.Type.BreadcrumbType as BreadcrumbType+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.BreadcrumbType" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works for Default" $ do+      Aeson.toJSON BreadcrumbType.Default `Hspec.shouldBe` [Aeson.aesonQQ| "default" |]++    Hspec.it "works for Http" $ do+      Aeson.toJSON BreadcrumbType.Http `Hspec.shouldBe` [Aeson.aesonQQ| "http" |]++    Hspec.it "works for Navigation" $ do+      Aeson.toJSON BreadcrumbType.Navigation `Hspec.shouldBe` [Aeson.aesonQQ| "navigation" |]
+ source/test-suite/Patrol/Type/BreadcrumbsSpec.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.BreadcrumbsSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.Breadcrumb as Breadcrumb+import qualified Patrol.Type.Breadcrumbs as Breadcrumbs+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Breadcrumbs" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let breadcrumbs = Breadcrumbs.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON breadcrumbs `Hspec.shouldBe` json++    Hspec.it "works with a value" $ do+      let breadcrumb = Breadcrumb.empty {Breadcrumb.category = Text.pack "example-category"}+          breadcrumbs = Breadcrumbs.empty {Breadcrumbs.values = [breadcrumb]}+          json = [Aeson.aesonQQ| { "values": [ { "category": "example-category" } ] } |]+      Aeson.toJSON breadcrumbs `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/BrowserContextSpec.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.BrowserContextSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.BrowserContext as BrowserContext+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.BrowserContext" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let browserContext = BrowserContext.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON browserContext `Hspec.shouldBe` json++    Hspec.it "works with a name" $ do+      let browserContext = BrowserContext.empty {BrowserContext.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON browserContext `Hspec.shouldBe` json++    Hspec.it "works with a version" $ do+      let browserContext = BrowserContext.empty {BrowserContext.version = Text.pack "example-version"}+          json = [Aeson.aesonQQ| { "version": "example-version" } |]+      Aeson.toJSON browserContext `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/CErrorSpec.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.CErrorSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.CError as CError+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.CError" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let cError = CError.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON cError `Hspec.shouldBe` json++    Hspec.it "works with a name" $ do+      let cError = CError.empty {CError.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON cError `Hspec.shouldBe` json++    Hspec.it "works with a number" $ do+      let cError = CError.empty {CError.number = Just 0}+          json = [Aeson.aesonQQ| { "number": 0 } |]+      Aeson.toJSON cError `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/ClientSdkInfoSpec.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.ClientSdkInfoSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.ClientSdkInfo as ClientSdkInfo+import qualified Patrol.Type.ClientSdkPackage as ClientSdkPackage+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.ClientSdkInfo" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let clientSdkInfo = ClientSdkInfo.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON clientSdkInfo `Hspec.shouldBe` json++    Hspec.it "works with an integration" $ do+      let clientSdkInfo = ClientSdkInfo.empty {ClientSdkInfo.integrations = [Text.pack "example-integration"]}+          json = [Aeson.aesonQQ| { "integrations": [ "example-integration" ] } |]+      Aeson.toJSON clientSdkInfo `Hspec.shouldBe` json++    Hspec.it "works with a name" $ do+      let clientSdkInfo = ClientSdkInfo.empty {ClientSdkInfo.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON clientSdkInfo `Hspec.shouldBe` json++    Hspec.it "works with a package" $ do+      let clientSdkPackage = ClientSdkPackage.empty {ClientSdkPackage.name = Text.pack "example-name"}+          clientSdkInfo = ClientSdkInfo.empty {ClientSdkInfo.packages = [clientSdkPackage]}+          json = [Aeson.aesonQQ| { "packages": [ { "name": "example-name" } ] } |]+      Aeson.toJSON clientSdkInfo `Hspec.shouldBe` json++    Hspec.it "works with a version" $ do+      let clientSdkInfo = ClientSdkInfo.empty {ClientSdkInfo.version = Text.pack "example-version"}+          json = [Aeson.aesonQQ| { "version": "example-version" } |]+      Aeson.toJSON clientSdkInfo `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/ClientSdkPackageSpec.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.ClientSdkPackageSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.ClientSdkPackage as ClientSdkPackage+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.ClientSdkPackage" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let clientSdkPackage = ClientSdkPackage.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON clientSdkPackage `Hspec.shouldBe` json++    Hspec.it "works with a name" $ do+      let clientSdkPackage = ClientSdkPackage.empty {ClientSdkPackage.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON clientSdkPackage `Hspec.shouldBe` json++    Hspec.it "works with a version" $ do+      let clientSdkPackage = ClientSdkPackage.empty {ClientSdkPackage.version = Text.pack "example-version"}+          json = [Aeson.aesonQQ| { "version": "example-version" } |]+      Aeson.toJSON clientSdkPackage `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/ContextSpec.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.ContextSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Patrol.Type.AppContext as AppContext+import qualified Patrol.Type.BrowserContext as BrowserContext+import qualified Patrol.Type.Context as Context+import qualified Patrol.Type.DeviceContext as DeviceContext+import qualified Patrol.Type.GpuContext as GpuContext+import qualified Patrol.Type.OsContext as OsContext+import qualified Patrol.Type.RuntimeContext as RuntimeContext+import qualified Patrol.Type.TraceContext as TraceContext+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Context" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works with app" $ do+      let context = Context.App AppContext.empty {AppContext.appBuild = Text.pack "example-app-build"}+          json = [Aeson.aesonQQ| { "app_build": "example-app-build" } |]+      Aeson.toJSON context `Hspec.shouldBe` json++    Hspec.it "works with browser" $ do+      let context = Context.Browser BrowserContext.empty {BrowserContext.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON context `Hspec.shouldBe` json++    Hspec.it "works with device" $ do+      let context = Context.Device DeviceContext.empty {DeviceContext.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON context `Hspec.shouldBe` json++    Hspec.it "works with gpu" $ do+      let context = Context.Gpu GpuContext.empty {GpuContext.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON context `Hspec.shouldBe` json++    Hspec.it "works with os" $ do+      let context = Context.Os OsContext.empty {OsContext.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON context `Hspec.shouldBe` json++    Hspec.it "works with runtime" $ do+      let context = Context.Runtime RuntimeContext.empty {RuntimeContext.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON context `Hspec.shouldBe` json++    Hspec.it "works with trace" $ do+      let context = Context.Trace TraceContext.empty {TraceContext.op = Text.pack "example-op"}+          json = [Aeson.aesonQQ| { "op": "example-op" } |]+      Aeson.toJSON context `Hspec.shouldBe` json++    Hspec.it "works with other" $ do+      let context = Context.Other . Map.singleton (Text.pack "example-key") $ Aeson.Bool True+          json = [Aeson.aesonQQ| { "example-key": true } |]+      Aeson.toJSON context `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/DebugImageSpec.hs view
@@ -0,0 +1,39 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.DebugImageSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Patrol.Type.AppleDebugImage as AppleDebugImage+import qualified Patrol.Type.DebugImage as DebugImage+import qualified Patrol.Type.NativeDebugImage as NativeDebugImage+import qualified Patrol.Type.ProguardDebugImage as ProguardDebugImage+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.DebugImage" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works with apple" $ do+      let appleDebugImage = AppleDebugImage.empty {AppleDebugImage.arch = Text.pack "example-arch"}+          debugImage = DebugImage.Apple appleDebugImage+          json = [Aeson.aesonQQ| { "arch": "example-arch" } |]+      Aeson.toJSON debugImage `Hspec.shouldBe` json++    Hspec.it "works with native" $ do+      let nativeDebugImage = NativeDebugImage.empty {NativeDebugImage.arch = Text.pack "example-arch"}+          debugImage = DebugImage.Native nativeDebugImage+          json = [Aeson.aesonQQ| { "arch": "example-arch" } |]+      Aeson.toJSON debugImage `Hspec.shouldBe` json++    Hspec.it "works with proguard" $ do+      let proguardDebugImage = ProguardDebugImage.empty {ProguardDebugImage.uuid = Text.pack "example-uuid"}+          debugImage = DebugImage.Proguard proguardDebugImage+          json = [Aeson.aesonQQ| { "uuid": "example-uuid" } |]+      Aeson.toJSON debugImage `Hspec.shouldBe` json++    Hspec.it "works with other" $ do+      let debugImage = DebugImage.Other . Map.singleton (Text.pack "example-key") $ Aeson.Bool True+          json = [Aeson.aesonQQ| { "example-key": true } |]+      Aeson.toJSON debugImage `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/DebugMetaSpec.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.DebugMetaSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Patrol.Type.DebugImage as DebugImage+import qualified Patrol.Type.DebugMeta as DebugMeta+import qualified Patrol.Type.SystemSdkInfo as SystemSdkInfo+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.DebugMeta" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let debugMeta = DebugMeta.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON debugMeta `Hspec.shouldBe` json++    Hspec.it "works with an image" $ do+      let image = DebugImage.Other . Map.singleton (Text.pack "example-image") $ Aeson.Bool True+          debugMeta = DebugMeta.empty {DebugMeta.images = [image]}+          json = [Aeson.aesonQQ| { "images": [ { "example-image": true } ] } |]+      Aeson.toJSON debugMeta `Hspec.shouldBe` json++    Hspec.it "works with some SDK info" $ do+      let systemSdkInfo = SystemSdkInfo.empty {SystemSdkInfo.versionMajor = Just 0}+          debugMeta = DebugMeta.empty {DebugMeta.sdkInfo = Just systemSdkInfo}+          json = [Aeson.aesonQQ| { "sdk_info": { "version_major": 0 } } |]+      Aeson.toJSON debugMeta `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/DeviceContextSpec.hs view
@@ -0,0 +1,198 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.DeviceContextSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Data.Time as Time+import qualified Patrol.Type.DeviceContext as DeviceContext+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.DeviceContext" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let deviceContext = DeviceContext.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with arch" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.arch = Text.pack "example-arch"}+          json = [Aeson.aesonQQ| { "arch": "example-arch" } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with batteryLevel" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.batteryLevel = Just 0.1}+          json = [Aeson.aesonQQ| { "battery_level": 0.1 } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with batteryStatus" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.batteryStatus = Text.pack "example-batteryStatus"}+          json = [Aeson.aesonQQ| { "battery_status": "example-batteryStatus" } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with bootTime" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.bootTime = Just $ Time.UTCTime (Time.fromGregorian 1970 1 1) 0}+          json = [Aeson.aesonQQ| { "boot_time": "1970-01-01T00:00:00Z" } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with brand" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.brand = Text.pack "example-brand"}+          json = [Aeson.aesonQQ| { "brand": "example-brand" } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with charging" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.charging = Just True}+          json = [Aeson.aesonQQ| { "charging": true } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with cpuDescription" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.cpuDescription = Text.pack "example-cpuDescription"}+          json = [Aeson.aesonQQ| { "cpu_description": "example-cpuDescription" } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with deviceType" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.deviceType = Text.pack "example-deviceType"}+          json = [Aeson.aesonQQ| { "device_type": "example-deviceType" } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with deviceUniqueIdentifier" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.deviceUniqueIdentifier = Text.pack "example-deviceUniqueIdentifier"}+          json = [Aeson.aesonQQ| { "device_unique_identifier": "example-deviceUniqueIdentifier" } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with externalFreeStorage" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.externalFreeStorage = Just 0}+          json = [Aeson.aesonQQ| { "external_free_storage": 0 } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with externalStorageSize" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.externalStorageSize = Just 0}+          json = [Aeson.aesonQQ| { "external_storage_size": 0 } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with family" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.family = Text.pack "example-family"}+          json = [Aeson.aesonQQ| { "family": "example-family" } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with freeMemory" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.freeMemory = Just 0}+          json = [Aeson.aesonQQ| { "free_memory": 0 } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with freeStorage" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.freeStorage = Just 0}+          json = [Aeson.aesonQQ| { "free_storage": 0 } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with lowMemory" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.lowMemory = Just True}+          json = [Aeson.aesonQQ| { "low_memory": true } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with manufacturer" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.manufacturer = Text.pack "example-manufacturer"}+          json = [Aeson.aesonQQ| { "manufacturer": "example-manufacturer" } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with memorySize" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.memorySize = Just 0}+          json = [Aeson.aesonQQ| { "memory_size": 0 } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with model" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.model = Text.pack "example-model"}+          json = [Aeson.aesonQQ| { "model": "example-model" } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with modelId" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.modelId = Text.pack "example-modelId"}+          json = [Aeson.aesonQQ| { "model_id": "example-modelId" } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with name" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with online" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.online = Just True}+          json = [Aeson.aesonQQ| { "online": true } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with orientation" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.orientation = Text.pack "example-orientation"}+          json = [Aeson.aesonQQ| { "orientation": "example-orientation" } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with processorCount" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.processorCount = Just 0}+          json = [Aeson.aesonQQ| { "processor_count": 0 } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with processorFrequency" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.processorFrequency = Just 0.1}+          json = [Aeson.aesonQQ| { "processor_frequency": 0.1 } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with screenDensity" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.screenDensity = Just 0.1}+          json = [Aeson.aesonQQ| { "screen_density": 0.1 } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with screenDpi" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.screenDpi = Just 0.1}+          json = [Aeson.aesonQQ| { "screen_dpi": 0.1 } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with screenResolution" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.screenResolution = Text.pack "example-screenResolution"}+          json = [Aeson.aesonQQ| { "screen_resolution": "example-screenResolution" } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with simulator" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.simulator = Just True}+          json = [Aeson.aesonQQ| { "simulator": true } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with storageSize" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.storageSize = Just 0}+          json = [Aeson.aesonQQ| { "storage_size": 0 } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with supportsAccelerometer" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.supportsAccelerometer = Just True}+          json = [Aeson.aesonQQ| { "supports_accelerometer": true } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with supportsAudio" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.supportsAudio = Just True}+          json = [Aeson.aesonQQ| { "supports_audio": true } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with supportsGyroscope" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.supportsGyroscope = Just True}+          json = [Aeson.aesonQQ| { "supports_gyroscope": true } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with supportsLocationService" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.supportsLocationService = Just True}+          json = [Aeson.aesonQQ| { "supports_location_service": true } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with supportsVibration" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.supportsVibration = Just True}+          json = [Aeson.aesonQQ| { "supports_vibration": true } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with timezone" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.timezone = Text.pack "example-timezone"}+          json = [Aeson.aesonQQ| { "timezone": "example-timezone" } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json++    Hspec.it "works with usableMemory" $ do+      let deviceContext = DeviceContext.empty {DeviceContext.usableMemory = Just 0}+          json = [Aeson.aesonQQ| { "usable_memory": 0 } |]+      Aeson.toJSON deviceContext `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/DsnSpec.hs view
@@ -0,0 +1,86 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.DsnSpec where++import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Network.URI.Static as Uri+import qualified Patrol.Type.Dsn as Dsn+import qualified Patrol.Version as Version+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Dsn" $ do+  Hspec.describe "fromUri" $ do+    Hspec.it "fails with an invalid DSN" $ do+      Dsn.fromUri [Uri.uri|a:|] `Hspec.shouldBe` Nothing++    Hspec.it "succeeds with a minimal DSN" $ do+      let dsn =+            Dsn.Dsn+              { Dsn.protocol = Text.singleton 'a',+                Dsn.publicKey = Text.singleton 'b',+                Dsn.secretKey = Text.empty,+                Dsn.host = Text.singleton 'c',+                Dsn.port = Nothing,+                Dsn.path = Text.singleton '/',+                Dsn.projectId = Text.singleton 'd'+              }+      Dsn.fromUri [Uri.uri|a://b@c/d|] `Hspec.shouldBe` Just dsn++    Hspec.it "succeeds with a maximal DSN" $ do+      let dsn =+            Dsn.Dsn+              { Dsn.protocol = Text.singleton 'a',+                Dsn.publicKey = Text.singleton 'b',+                Dsn.secretKey = Text.singleton 'c',+                Dsn.host = Text.singleton 'd',+                Dsn.port = Just 5,+                Dsn.path = Text.pack "/f/",+                Dsn.projectId = Text.singleton 'g'+              }+      Dsn.fromUri [Uri.uri|a://b:c@d:5/f/g|] `Hspec.shouldBe` Just dsn++    Hspec.it "fails with a query" $ do+      Dsn.fromUri [Uri.uri|a://b@c/d?|] `Hspec.shouldBe` Nothing++    Hspec.it "fails with a fragment" $ do+      Dsn.fromUri [Uri.uri|a://b@c/d#|] `Hspec.shouldBe` Nothing++  Hspec.describe "intoUri" $ do+    Hspec.it "converts a minimal DSN into URI" $ do+      let dsn =+            Dsn.Dsn+              { Dsn.protocol = Text.singleton 'a',+                Dsn.publicKey = Text.singleton 'b',+                Dsn.secretKey = Text.empty,+                Dsn.host = Text.singleton 'c',+                Dsn.port = Nothing,+                Dsn.path = Text.singleton '/',+                Dsn.projectId = Text.singleton 'd'+              }+      Dsn.intoUri dsn `Hspec.shouldBe` [Uri.uri|a://b@c/d|]++    Hspec.it "converts a maximal DSN into URI" $ do+      let dsn =+            Dsn.Dsn+              { Dsn.protocol = Text.singleton 'a',+                Dsn.publicKey = Text.singleton 'b',+                Dsn.secretKey = Text.singleton 'c',+                Dsn.host = Text.singleton 'd',+                Dsn.port = Just 5,+                Dsn.path = Text.pack "/f/",+                Dsn.projectId = Text.singleton 'g'+              }+      Dsn.intoUri dsn `Hspec.shouldBe` [Uri.uri|a://b:c@d:5/f/g|]++  Hspec.describe "intoAuthorization" $ do+    Hspec.it "works without a secret key" $ do+      dsn <- maybe (fail "invalid DSN") pure $ Dsn.fromUri [Uri.uri|a://b@c/d|]+      let byteString = Text.encodeUtf8 . Text.pack $ "Sentry sentry_version=7,sentry_client=patrol/" <> Version.string <> ",sentry_key=b"+      Dsn.intoAuthorization dsn `Hspec.shouldBe` byteString++    Hspec.it "works with a secret key" $ do+      dsn <- maybe (fail "invalid DSN") pure $ Dsn.fromUri [Uri.uri|a://b:c@d/e|]+      let byteString = Text.encodeUtf8 . Text.pack $ "Sentry sentry_version=7,sentry_client=patrol/" <> Version.string <> ",sentry_key=b,sentry_secret=c"+      Dsn.intoAuthorization dsn `Hspec.shouldBe` byteString
+ source/test-suite/Patrol/Type/ErrorTypeSpec.hs view
@@ -0,0 +1,131 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.ErrorTypeSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Patrol.Type.ErrorType as ErrorType+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.ErrorType" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works for ClockDrift" $ do+      Aeson.toJSON ErrorType.ClockDrift `Hspec.shouldBe` [Aeson.aesonQQ| "clock_drift" |]++    Hspec.it "works for FetchGenericError" $ do+      Aeson.toJSON ErrorType.FetchGenericError `Hspec.shouldBe` [Aeson.aesonQQ| "fetch_generic_error" |]++    Hspec.it "works for FetchInvalidEncoding" $ do+      Aeson.toJSON ErrorType.FetchInvalidEncoding `Hspec.shouldBe` [Aeson.aesonQQ| "fetch_invalid_source_encoding" |]++    Hspec.it "works for FetchInvalidHttpCode" $ do+      Aeson.toJSON ErrorType.FetchInvalidHttpCode `Hspec.shouldBe` [Aeson.aesonQQ| "fetch_invalid_http_code" |]++    Hspec.it "works for FetchTimeout" $ do+      Aeson.toJSON ErrorType.FetchTimeout `Hspec.shouldBe` [Aeson.aesonQQ| "fetch_timeout" |]++    Hspec.it "works for FetchTooLarge" $ do+      Aeson.toJSON ErrorType.FetchTooLarge `Hspec.shouldBe` [Aeson.aesonQQ| "fetch_too_large" |]++    Hspec.it "works for FutureTimestamp" $ do+      Aeson.toJSON ErrorType.FutureTimestamp `Hspec.shouldBe` [Aeson.aesonQQ| "future_timestamp" |]++    Hspec.it "works for InvalidAttribute" $ do+      Aeson.toJSON ErrorType.InvalidAttribute `Hspec.shouldBe` [Aeson.aesonQQ| "invalid_attribute" |]++    Hspec.it "works for InvalidData" $ do+      Aeson.toJSON ErrorType.InvalidData `Hspec.shouldBe` [Aeson.aesonQQ| "invalid_data" |]++    Hspec.it "works for InvalidEnvironment" $ do+      Aeson.toJSON ErrorType.InvalidEnvironment `Hspec.shouldBe` [Aeson.aesonQQ| "invalid_environment" |]++    Hspec.it "works for JsFetchTimeout" $ do+      Aeson.toJSON ErrorType.JsFetchTimeout `Hspec.shouldBe` [Aeson.aesonQQ| "js_fetch_timeout" |]++    Hspec.it "works for JsGenericFetchError" $ do+      Aeson.toJSON ErrorType.JsGenericFetchError `Hspec.shouldBe` [Aeson.aesonQQ| "js_generic_fetch_error" |]++    Hspec.it "works for JsInvalidContent" $ do+      Aeson.toJSON ErrorType.JsInvalidContent `Hspec.shouldBe` [Aeson.aesonQQ| "js_invalid_content" |]++    Hspec.it "works for JsInvalidHttpCode" $ do+      Aeson.toJSON ErrorType.JsInvalidHttpCode `Hspec.shouldBe` [Aeson.aesonQQ| "js_invalid_http_code" |]++    Hspec.it "works for JsInvalidSourceEncoding" $ do+      Aeson.toJSON ErrorType.JsInvalidSourceEncoding `Hspec.shouldBe` [Aeson.aesonQQ| "js_invalid_source_encoding" |]++    Hspec.it "works for JsInvalidSourcemap" $ do+      Aeson.toJSON ErrorType.JsInvalidSourcemap `Hspec.shouldBe` [Aeson.aesonQQ| "js_invalid_source" |]++    Hspec.it "works for JsInvalidSourcemapLocation" $ do+      Aeson.toJSON ErrorType.JsInvalidSourcemapLocation `Hspec.shouldBe` [Aeson.aesonQQ| "js_invalid_sourcemap_location" |]++    Hspec.it "works for JsMissingSource" $ do+      Aeson.toJSON ErrorType.JsMissingSource `Hspec.shouldBe` [Aeson.aesonQQ| "js_no_source" |]++    Hspec.it "works for JsNoColumn" $ do+      Aeson.toJSON ErrorType.JsNoColumn `Hspec.shouldBe` [Aeson.aesonQQ| "js_no_column" |]++    Hspec.it "works for JsTooLarge" $ do+      Aeson.toJSON ErrorType.JsTooLarge `Hspec.shouldBe` [Aeson.aesonQQ| "js_too_large" |]++    Hspec.it "works for JsTooManyRemoteSources" $ do+      Aeson.toJSON ErrorType.JsTooManyRemoteSources `Hspec.shouldBe` [Aeson.aesonQQ| "js_too_many_sources" |]++    Hspec.it "works for MissingAttribute" $ do+      Aeson.toJSON ErrorType.MissingAttribute `Hspec.shouldBe` [Aeson.aesonQQ| "missing_attribute" |]++    Hspec.it "works for NativeBadDsym" $ do+      Aeson.toJSON ErrorType.NativeBadDsym `Hspec.shouldBe` [Aeson.aesonQQ| "native_bad_dsym" |]++    Hspec.it "works for NativeInternalFailure" $ do+      Aeson.toJSON ErrorType.NativeInternalFailure `Hspec.shouldBe` [Aeson.aesonQQ| "native_internal_failure" |]++    Hspec.it "works for NativeMissingDsym" $ do+      Aeson.toJSON ErrorType.NativeMissingDsym `Hspec.shouldBe` [Aeson.aesonQQ| "native_missing_dsym" |]++    Hspec.it "works for NativeMissingOptionallyBundledDsym" $ do+      Aeson.toJSON ErrorType.NativeMissingOptionallyBundledDsym `Hspec.shouldBe` [Aeson.aesonQQ| "native_optionally_bundled_dsym" |]++    Hspec.it "works for NativeMissingSymbol" $ do+      Aeson.toJSON ErrorType.NativeMissingSymbol `Hspec.shouldBe` [Aeson.aesonQQ| "native_missing_symbol" |]++    Hspec.it "works for NativeMissingSystemDsym" $ do+      Aeson.toJSON ErrorType.NativeMissingSystemDsym `Hspec.shouldBe` [Aeson.aesonQQ| "native_missing_system_dsym" |]++    Hspec.it "works for NativeNoCrashedThread" $ do+      Aeson.toJSON ErrorType.NativeNoCrashedThread `Hspec.shouldBe` [Aeson.aesonQQ| "native_no_crashed_thread" |]++    Hspec.it "works for NativeSimulatorFrame" $ do+      Aeson.toJSON ErrorType.NativeSimulatorFrame `Hspec.shouldBe` [Aeson.aesonQQ| "native_simulator_frame" |]++    Hspec.it "works for NativeSymbolicatorFailed" $ do+      Aeson.toJSON ErrorType.NativeSymbolicatorFailed `Hspec.shouldBe` [Aeson.aesonQQ| "native_symbolicator_failed" |]++    Hspec.it "works for NativeUnknownImage" $ do+      Aeson.toJSON ErrorType.NativeUnknownImage `Hspec.shouldBe` [Aeson.aesonQQ| "native_unknown_image" |]++    Hspec.it "works for PastTimestamp" $ do+      Aeson.toJSON ErrorType.PastTimestamp `Hspec.shouldBe` [Aeson.aesonQQ| "past_timestamp" |]++    Hspec.it "works for ProguardMissingLineno" $ do+      Aeson.toJSON ErrorType.ProguardMissingLineno `Hspec.shouldBe` [Aeson.aesonQQ| "proguard_missing_lineno" |]++    Hspec.it "works for ProguardMissingMapping" $ do+      Aeson.toJSON ErrorType.ProguardMissingMapping `Hspec.shouldBe` [Aeson.aesonQQ| "proguard_missing_mapping" |]++    Hspec.it "works for RestrictedIp" $ do+      Aeson.toJSON ErrorType.RestrictedIp `Hspec.shouldBe` [Aeson.aesonQQ| "restricted_ip" |]++    Hspec.it "works for SecurityViolation" $ do+      Aeson.toJSON ErrorType.SecurityViolation `Hspec.shouldBe` [Aeson.aesonQQ| "security_violation" |]++    Hspec.it "works for TooLargeForCache" $ do+      Aeson.toJSON ErrorType.TooLargeForCache `Hspec.shouldBe` [Aeson.aesonQQ| "too_large_for_cache" |]++    Hspec.it "works for UnknownError" $ do+      Aeson.toJSON ErrorType.UnknownError `Hspec.shouldBe` [Aeson.aesonQQ| "unknown_error" |]++    Hspec.it "works for ValueTooLong" $ do+      Aeson.toJSON ErrorType.ValueTooLong `Hspec.shouldBe` [Aeson.aesonQQ| "value_too_long" |]
+ source/test-suite/Patrol/Type/EventIdSpec.hs view
@@ -0,0 +1,74 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TypeApplications #-}++module Patrol.Type.EventIdSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Data.UUID as Uuid+import qualified Data.UUID.V4 as Uuid+import qualified Patrol.Type.EventId as EventId+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.EventId" $ do+  Hspec.describe "fromUuid" $ do+    Hspec.it "converts from UUID" $ do+      uuid <- Uuid.nextRandom+      EventId.fromUuid uuid `Hspec.shouldBe` EventId.EventId uuid++  Hspec.describe "intoUuid" $ do+    Hspec.it "converts into UUID" $ do+      uuid <- Uuid.nextRandom+      EventId.intoUuid (EventId.fromUuid uuid) `Hspec.shouldBe` uuid++  Hspec.describe "random" $ do+    Hspec.it "generates a non-nil UUID" $ do+      eventId <- EventId.random+      eventId `Hspec.shouldNotBe` EventId.empty++  Hspec.describe "intoText" $ do+    Hspec.it "converts into text" $ do+      let eventId = EventId.fromUuid $ Uuid.fromWords64 0x0011223344556677 0x8899aabbccddeeff+      EventId.intoText eventId `Hspec.shouldBe` Text.pack "00112233445566778899aabbccddeeff"++  Hspec.describe "fromText" $ do+    Hspec.it "converts from text" $ do+      let eventId = EventId.fromUuid $ Uuid.fromWords64 0x0011223344556677 0x8899aabbccddeeff+      EventId.fromText (Text.pack "00112233445566778899aabbccddeeff") `Hspec.shouldBe` Just eventId++    Hspec.it "succeeds with uppercase digits" $ do+      let eventId = EventId.fromUuid $ Uuid.fromWords64 0x0011223344556677 0x8899aabbccddeeff+      EventId.fromText (Text.pack "00112233445566778899AABBCCDDEEFF") `Hspec.shouldBe` Just eventId++    Hspec.it "fails with not enough digits" $ do+      EventId.fromText (Text.pack "0123456789abcdef0123456789abcde") `Hspec.shouldBe` Nothing++    Hspec.it "fails with too many digits" $ do+      EventId.fromText (Text.pack "0123456789abcdef0123456789abcdef0") `Hspec.shouldBe` Nothing++    Hspec.it "fails with invalid hexadecimal" $ do+      EventId.fromText (Text.pack "Z123456789abcdef0123456789abcdef") `Hspec.shouldBe` Nothing+      EventId.fromText (Text.pack "0123456789Zbcdef0123456789abcdef") `Hspec.shouldBe` Nothing+      EventId.fromText (Text.pack "0123456789abcdef0123456789Zbcdef") `Hspec.shouldBe` Nothing++  Hspec.describe "FromJSON" $ do+    Hspec.it "works" $ do+      let json = [Aeson.aesonQQ| "00112233445566778899aabbccddeeff" |]+          eventId = EventId.fromUuid $ Uuid.fromWords64 0x0011223344556677 0x8899aabbccddeeff+      Aeson.fromJSON json `Hspec.shouldBe` Aeson.Success eventId++    Hspec.it "fails with not enough digits" $ do+      let json = [Aeson.aesonQQ| "" |]+      Aeson.fromJSON @EventId.EventId json `Hspec.shouldBe` Aeson.Error "invalid EventId"++    Hspec.it "fails with the wrong type" $ do+      let json = [Aeson.aesonQQ| null |]+      Aeson.fromJSON @EventId.EventId json `Hspec.shouldBe` Aeson.Error "parsing EventId failed, expected String, but encountered Null"++  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let eventId = EventId.fromUuid $ Uuid.fromWords64 0x0011223344556677 0x8899aabbccddeeff+          json = [Aeson.aesonQQ| "00112233445566778899aabbccddeeff" |]+      Aeson.toJSON eventId `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/EventProcessingErrorSpec.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.EventProcessingErrorSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.EventProcessingError as EventProcessingError+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.EventProcessingError" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let eventProcessingError = EventProcessingError.empty+          json = [Aeson.aesonQQ| { "type": "unknown_error" } |]+      Aeson.toJSON eventProcessingError `Hspec.shouldBe` json++    Hspec.it "works with a name" $ do+      let eventProcessingError = EventProcessingError.empty {EventProcessingError.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "type": "unknown_error", "name": "example-name" } |]+      Aeson.toJSON eventProcessingError `Hspec.shouldBe` json++    Hspec.it "works with a value" $ do+      let eventProcessingError = EventProcessingError.empty {EventProcessingError.value = Aeson.Bool True}+          json = [Aeson.aesonQQ| { "type": "unknown_error", "value": true } |]+      Aeson.toJSON eventProcessingError `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/EventSpec.hs view
@@ -0,0 +1,274 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.EventSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.ByteString.Lazy as LazyByteString+import qualified Data.Map as Map+import qualified Data.Maybe as Maybe+import qualified Data.Text as Text+import qualified Data.Text.Encoding as Text+import qualified Data.Time as Time+import qualified Network.HTTP.Client as Client+import qualified Network.HTTP.Types as Http+import qualified Network.URI.Static as Uri+import qualified Patrol.Constant as Constant+import qualified Patrol.Type.Breadcrumb as Breadcrumb+import qualified Patrol.Type.Breadcrumbs as Breadcrumbs+import qualified Patrol.Type.ClientSdkInfo as ClientSdkInfo+import qualified Patrol.Type.Context as Context+import qualified Patrol.Type.DebugImage as DebugImage+import qualified Patrol.Type.DebugMeta as DebugMeta+import qualified Patrol.Type.Dsn as Dsn+import qualified Patrol.Type.Event as Event+import qualified Patrol.Type.EventId as EventId+import qualified Patrol.Type.EventProcessingError as EventProcessingError+import qualified Patrol.Type.EventType as EventType+import qualified Patrol.Type.Exception as Exception+import qualified Patrol.Type.Exceptions as Exceptions+import qualified Patrol.Type.Level as Level+import qualified Patrol.Type.LogEntry as LogEntry+import qualified Patrol.Type.Platform as Platform+import qualified Patrol.Type.Request as Request+import qualified Patrol.Type.TransactionInfo as TransactionInfo+import qualified Patrol.Type.User as User+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Event" $ do+  Hspec.describe "new" $ do+    Hspec.it "generates an ID" $ do+      event <- Event.new+      Event.eventId event `Hspec.shouldNotBe` EventId.empty++    Hspec.it "sets the timestamp" $ do+      event <- Event.new+      Event.timestamp event `Hspec.shouldSatisfy` Maybe.isJust++    Hspec.it "sets the platform" $ do+      event <- Event.new+      Event.platform event `Hspec.shouldBe` Just Platform.Haskell++    Hspec.it "sets the level" $ do+      event <- Event.new+      Event.level event `Hspec.shouldBe` Just Level.Error++    Hspec.it "sets the environment" $ do+      event <- Event.new+      Event.environment event `Hspec.shouldBe` Text.pack "production"++    Hspec.it "sets the version" $ do+      event <- Event.new+      Event.version event `Hspec.shouldBe` Constant.sentryVersion++    Hspec.it "sets the type" $ do+      event <- Event.new+      Event.type_ event `Hspec.shouldBe` Just EventType.Default++  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let event = Event.empty+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000" } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a breadcrumb" $ do+      let breadcrumb = Breadcrumb.empty {Breadcrumb.category = Text.pack "example-category"}+          breadcrumbs = Breadcrumbs.empty {Breadcrumbs.values = [breadcrumb]}+          event = Event.empty {Event.breadcrumbs = Just breadcrumbs}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "breadcrumbs": { "values": [ { "category": "example-category" } ] } } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a context" $ do+      let context = Context.Other . Map.singleton (Text.pack "example-key") $ Aeson.Bool True+          contexts = Map.singleton (Text.pack "example-context") context+          event = Event.empty {Event.contexts = contexts}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "contexts": { "example-context": { "example-key": true } } } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with some debug meta" $ do+      let image = DebugImage.Other . Map.singleton (Text.pack "example-image") $ Aeson.Bool True+          debugMeta = DebugMeta.empty {DebugMeta.images = [image]}+          event = Event.empty {Event.debugMeta = Just debugMeta}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "debug_meta": { "images": [ { "example-image": true } ] } } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a dist" $ do+      let event = Event.empty {Event.dist = Text.pack "example-dist"}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "dist": "example-dist" } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with an environment" $ do+      let event = Event.empty {Event.environment = Text.pack "example-environment"}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "environment": "example-environment" } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with an event processing error" $ do+      let eventProcessingError = EventProcessingError.empty+          event = Event.empty {Event.errors = [eventProcessingError]}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "errors": [ { "type": "unknown_error" } ] } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with an exception" $ do+      let exception = Exception.empty {Exception.type_ = Text.pack "example-type"}+          exceptions = Exceptions.empty {Exceptions.values = [exception]}+          event = Event.empty {Event.exception = Just exceptions}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "exception": { "values": [ { "type": "example-type" } ] } } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with some extra" $ do+      let event = Event.empty {Event.extra = Map.singleton (Text.pack "example-extra") (Aeson.Bool True)}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "extra": { "example-extra": true } } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a fingerprint" $ do+      let event = Event.empty {Event.fingerprint = [Text.pack "example-fingerprint"]}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "fingerprint": ["example-fingerprint"] } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a level" $ do+      let event = Event.empty {Event.level = Just Level.Error}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "level": "error" } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a log entry" $ do+      let logEntry = LogEntry.empty {LogEntry.message = Text.pack "example-message"}+          event = Event.empty {Event.logentry = Just logEntry}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "logentry": { "message": "example-message" } } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a logger" $ do+      let event = Event.empty {Event.logger = Text.pack "example-logger"}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "logger": "example-logger" } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with some modules" $ do+      let event = Event.empty {Event.modules = Map.fromList [(Text.pack "module-name", Text.pack "module-version")]}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "modules": { "module-name": "module-version" } } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a platform" $ do+      let event = Event.empty {Event.platform = Just Platform.Haskell}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "platform": "haskell" } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a release" $ do+      let event = Event.empty {Event.release = Text.pack "example-release"}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "release": "example-release" } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a request" $ do+      let request = Request.empty {Request.fragment = Text.pack "example-fragment"}+          event = Event.empty {Event.request = Just request}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "request": { "fragment": "example-fragment" } } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with an SDK" $ do+      let clientSdkInfo = ClientSdkInfo.empty {ClientSdkInfo.name = Text.pack "example-name"}+          event = Event.empty {Event.sdk = Just clientSdkInfo}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "sdk": { "name": "example-name" } } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a server name" $ do+      let event = Event.empty {Event.serverName = Text.pack "example-server-name"}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "server_name": "example-server-name" } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with some tags" $ do+      let event = Event.empty {Event.tags = Map.fromList [(Text.pack "tag-key", Text.pack "tag-value")]}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "tags": { "tag-key": "tag-value" } } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a time spent" $ do+      let event = Event.empty {Event.timeSpent = Just 1.2}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "time_spent": 1.2 } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a timestamp" $ do+      let event = Event.empty {Event.timestamp = Just $ Time.UTCTime (Time.fromGregorian 1970 1 1) 0}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "timestamp": "1970-01-01T00:00:00Z" } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a transaction" $ do+      let event = Event.empty {Event.transaction = Text.pack "example-transaction"}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "transaction": "example-transaction" } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with some transaction info" $ do+      let transactionInfo = TransactionInfo.empty {TransactionInfo.original = Text.pack "example-original"}+          event = Event.empty {Event.transactionInfo = Just transactionInfo}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "transaction_info": { "original": "example-original" } } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a type" $ do+      let event = Event.empty {Event.type_ = Just EventType.Default}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "type": "default" } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a user" $ do+      let user = User.empty {User.email = Text.pack "example-email"}+          event = Event.empty {Event.user = Just user}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "user": { "email": "example-email" } } |]+      Aeson.toJSON event `Hspec.shouldBe` json++    Hspec.it "works with a version" $ do+      let event = Event.empty {Event.version = Text.pack "example-version"}+          json = [Aeson.aesonQQ| { "event_id": "00000000000000000000000000000000", "version": "example-version" } |]+      Aeson.toJSON event `Hspec.shouldBe` json++  Hspec.describe "intoRequest" $ do+    Hspec.it "sets the method" $ do+      dsn <- maybe (fail "invalid DSN") pure $ Dsn.fromUri [Uri.uri|http://public-key@sentry.test/project-id|]+      event <- Event.new+      request <- Event.intoRequest dsn event+      Client.method request `Hspec.shouldBe` Http.methodPost++    Hspec.it "sets the host" $ do+      dsn <- maybe (fail "invalid DSN") pure $ Dsn.fromUri [Uri.uri|http://public-key@sentry.test:8080/project-id|]+      event <- Event.new+      request <- Event.intoRequest dsn event+      Client.host request `Hspec.shouldBe` Text.encodeUtf8 (Text.pack "sentry.test")++    Hspec.it "sets the port" $ do+      dsn <- maybe (fail "invalid DSN") pure $ Dsn.fromUri [Uri.uri|http://public-key@sentry.test:8080/project-id|]+      event <- Event.new+      request <- Event.intoRequest dsn event+      Client.port request `Hspec.shouldBe` 8080++    Hspec.it "sets the path" $ do+      dsn <- maybe (fail "invalid DSN") pure $ Dsn.fromUri [Uri.uri|http://public-key@sentry.test/project-id|]+      event <- Event.new+      request <- Event.intoRequest dsn event+      Client.path request `Hspec.shouldBe` Text.encodeUtf8 (Text.pack "/api/project-id/store/")++    Hspec.it "handles a custom path" $ do+      dsn <- maybe (fail "invalid DSN") pure $ Dsn.fromUri [Uri.uri|http://public-key@sentry.test/custom/project-id|]+      event <- Event.new+      request <- Event.intoRequest dsn event+      Client.path request `Hspec.shouldBe` Text.encodeUtf8 (Text.pack "/custom/api/project-id/store/")++    Hspec.it "sets the body" $ do+      dsn <- maybe (fail "invalid DSN") pure $ Dsn.fromUri [Uri.uri|http://public-key@sentry.test/project-id|]+      event <- Event.new+      request <- Event.intoRequest dsn event+      case Client.requestBody request of+        Client.RequestBodyBS byteString -> byteString `Hspec.shouldBe` LazyByteString.toStrict (Aeson.encode event)+        _ -> fail "unexpected request body"++    Hspec.it "sets the content type" $ do+      dsn <- maybe (fail "invalid DSN") pure $ Dsn.fromUri [Uri.uri|http://public-key@sentry.test/project-id|]+      event <- Event.new+      request <- Event.intoRequest dsn event+      lookup Http.hContentType (Client.requestHeaders request) `Hspec.shouldSatisfy` Maybe.isJust++    Hspec.it "sets the user agent" $ do+      dsn <- maybe (fail "invalid DSN") pure $ Dsn.fromUri [Uri.uri|http://public-key@sentry.test/project-id|]+      event <- Event.new+      request <- Event.intoRequest dsn event+      lookup Http.hUserAgent (Client.requestHeaders request) `Hspec.shouldSatisfy` Maybe.isJust++    Hspec.it "sets the authorization" $ do+      dsn <- maybe (fail "invalid DSN") pure $ Dsn.fromUri [Uri.uri|http://public-key@sentry.test/project-id|]+      event <- Event.new+      request <- Event.intoRequest dsn event+      lookup Constant.xSentryAuth (Client.requestHeaders request) `Hspec.shouldSatisfy` Maybe.isJust
+ source/test-suite/Patrol/Type/EventTypeSpec.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.EventTypeSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Patrol.Type.EventType as EventType+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.EventType" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works for Csp" $ do+      Aeson.toJSON EventType.Csp `Hspec.shouldBe` [Aeson.aesonQQ| "csp" |]++    Hspec.it "works for Default" $ do+      Aeson.toJSON EventType.Default `Hspec.shouldBe` [Aeson.aesonQQ| "default" |]++    Hspec.it "works for Error" $ do+      Aeson.toJSON EventType.Error `Hspec.shouldBe` [Aeson.aesonQQ| "error" |]++    Hspec.it "works for Expectct" $ do+      Aeson.toJSON EventType.Expectct `Hspec.shouldBe` [Aeson.aesonQQ| "expectct" |]++    Hspec.it "works for Expectstaple" $ do+      Aeson.toJSON EventType.Expectstaple `Hspec.shouldBe` [Aeson.aesonQQ| "expectstaple" |]++    Hspec.it "works for Hpkp" $ do+      Aeson.toJSON EventType.Hpkp `Hspec.shouldBe` [Aeson.aesonQQ| "hpkp" |]++    Hspec.it "works for Transaction" $ do+      Aeson.toJSON EventType.Transaction `Hspec.shouldBe` [Aeson.aesonQQ| "transaction" |]
+ source/test-suite/Patrol/Type/ExceptionSpec.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.ExceptionSpec where++import qualified Control.Monad.Catch as Catch+import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Patrol.Type.Exception as Exception+import qualified Patrol.Type.Mechanism as Mechanism+import qualified Patrol.Type.Stacktrace as Stacktrace+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Exception" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let exception = Exception.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON exception `Hspec.shouldBe` json++    Hspec.it "works with a mechanism" $ do+      let mechanism = Mechanism.empty {Mechanism.type_ = Text.pack "example-mechanism"}+          exception = Exception.empty {Exception.mechanism = Just mechanism}+          json = [Aeson.aesonQQ| { "mechanism": { "type": "example-mechanism" } } |]+      Aeson.toJSON exception `Hspec.shouldBe` json++    Hspec.it "works with a module" $ do+      let exception = Exception.empty {Exception.module_ = Text.pack "example-module"}+          json = [Aeson.aesonQQ| { "module": "example-module" } |]+      Aeson.toJSON exception `Hspec.shouldBe` json++    Hspec.it "works with a stack trace" $ do+      let stacktrace = Stacktrace.empty {Stacktrace.registers = Map.singleton (Text.pack "example-key") $ Text.pack "example-value"}+          exception = Exception.empty {Exception.stacktrace = Just stacktrace}+          json = [Aeson.aesonQQ| { "stacktrace": { "registers": { "example-key": "example-value" } } } |]+      Aeson.toJSON exception `Hspec.shouldBe` json++    Hspec.it "works with a thread ID" $ do+      let exception = Exception.empty {Exception.threadId = Text.pack "example-thread-id"}+          json = [Aeson.aesonQQ| { "thread_id": "example-thread-id" } |]+      Aeson.toJSON exception `Hspec.shouldBe` json++    Hspec.it "works with a type" $ do+      let exception = Exception.empty {Exception.type_ = Text.pack "example-type"}+          json = [Aeson.aesonQQ| { "type": "example-type" } |]+      Aeson.toJSON exception `Hspec.shouldBe` json++    Hspec.it "works with a value" $ do+      let exception = Exception.empty {Exception.value = Text.pack "example-value"}+          json = [Aeson.aesonQQ| { "value": "example-value" } |]+      Aeson.toJSON exception `Hspec.shouldBe` json++  Hspec.describe "fromSomeException" $ do+    let exception = Exception.fromSomeException . Catch.toException $ userError "example-exception-value"++    Hspec.it "sets the type" $ do+      Exception.type_ exception `Hspec.shouldBe` Text.pack "IOException"++    Hspec.it "sets the value" $ do+      Exception.value exception `Hspec.shouldBe` Text.pack "user error (example-exception-value)"
+ source/test-suite/Patrol/Type/ExceptionsSpec.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.ExceptionsSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.Exception as Exception+import qualified Patrol.Type.Exceptions as Exceptions+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Exceptions" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let exceptions = Exceptions.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON exceptions `Hspec.shouldBe` json++    Hspec.it "works with a value" $ do+      let exception = Exception.empty {Exception.type_ = Text.pack "example-type"}+          exceptions = Exceptions.empty {Exceptions.values = [exception]}+          json = [Aeson.aesonQQ| { "values": [ { "type": "example-type" } ] } |]+      Aeson.toJSON exceptions `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/FrameSpec.hs view
@@ -0,0 +1,137 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.FrameSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified GHC.Stack as Stack+import qualified Patrol.Type.Frame as Frame+import qualified Patrol.Type.Platform as Platform+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Frame" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let frame = Frame.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with an abs path" $ do+      let frame = Frame.empty {Frame.absPath = Text.pack "example-abs-path"}+          json = [Aeson.aesonQQ| { "abs_path": "example-abs-path" } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with an addr mode" $ do+      let frame = Frame.empty {Frame.addrMode = Text.pack "example-addr-mode"}+          json = [Aeson.aesonQQ| { "addr_mode": "example-addr-mode" } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with a colno" $ do+      let frame = Frame.empty {Frame.colno = Just 0}+          json = [Aeson.aesonQQ| { "colno": 0 } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with a context line" $ do+      let frame = Frame.empty {Frame.contextLine = Text.pack "example-context-line"}+          json = [Aeson.aesonQQ| { "context_line": "example-context-line" } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with a filename" $ do+      let frame = Frame.empty {Frame.filename = Text.pack "example-filename"}+          json = [Aeson.aesonQQ| { "filename": "example-filename" } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with a function" $ do+      let frame = Frame.empty {Frame.function = Text.pack "example-function"}+          json = [Aeson.aesonQQ| { "function": "example-function" } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with an image addr" $ do+      let frame = Frame.empty {Frame.imageAddr = Text.pack "example-image-addr"}+          json = [Aeson.aesonQQ| { "image_addr": "example-image-addr" } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with an in app flag" $ do+      let frame = Frame.empty {Frame.inApp = Just True}+          json = [Aeson.aesonQQ| { "in_app": true } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with an instruction addr" $ do+      let frame = Frame.empty {Frame.instructionAddr = Text.pack "example-instruction-addr"}+          json = [Aeson.aesonQQ| { "instruction_addr": "example-instruction-addr" } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with a lineno" $ do+      let frame = Frame.empty {Frame.lineno = Just 0}+          json = [Aeson.aesonQQ| { "lineno": 0 } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with a module" $ do+      let frame = Frame.empty {Frame.module_ = Text.pack "example-module"}+          json = [Aeson.aesonQQ| { "module": "example-module" } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with a package" $ do+      let frame = Frame.empty {Frame.package = Text.pack "example-package"}+          json = [Aeson.aesonQQ| { "package": "example-package" } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with a platform" $ do+      let frame = Frame.empty {Frame.platform = Just Platform.Other}+          json = [Aeson.aesonQQ| { "platform": "other" } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with some post context" $ do+      let frame = Frame.empty {Frame.postContext = [Text.pack "example-post-context"]}+          json = [Aeson.aesonQQ| { "post_context": [ "example-post-context" ] } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with some pre context" $ do+      let frame = Frame.empty {Frame.preContext = [Text.pack "example-pre-context"]}+          json = [Aeson.aesonQQ| { "pre_context": [ "example-pre-context" ] } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with a raw function" $ do+      let frame = Frame.empty {Frame.rawFunction = Text.pack "example-raw-function"}+          json = [Aeson.aesonQQ| { "raw_function": "example-raw-function" } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with a stack start flag" $ do+      let frame = Frame.empty {Frame.stackStart = Just True}+          json = [Aeson.aesonQQ| { "stack_start": true } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with a symbol addr" $ do+      let frame = Frame.empty {Frame.symbolAddr = Text.pack "example-symbol-addr"}+          json = [Aeson.aesonQQ| { "symbol_addr": "example-symbol-addr" } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++    Hspec.it "works with some vars" $ do+      let frame = Frame.empty {Frame.vars = Map.singleton (Text.pack "example-var") Aeson.Null}+          json = [Aeson.aesonQQ| { "vars": { "example-var": null } } |]+      Aeson.toJSON frame `Hspec.shouldBe` json++  Hspec.describe "fromSrcLoc" $ do+    Hspec.it "works" $ do+      let srcLoc =+            Stack.SrcLoc+              { Stack.srcLocEndCol = 3,+                Stack.srcLocEndLine = 4,+                Stack.srcLocFile = "example-file",+                Stack.srcLocModule = "example-module",+                Stack.srcLocPackage = "example-package",+                Stack.srcLocStartCol = 1,+                Stack.srcLocStartLine = 2+              }+          frame =+            Frame.empty+              { Frame.colno = Just 1,+                Frame.filename = Text.pack "example-file",+                Frame.lineno = Just 2,+                Frame.module_ = Text.pack "example-module",+                Frame.package = Text.pack "example-package"+              }+      Frame.fromSrcLoc srcLoc `Hspec.shouldBe` frame
+ source/test-suite/Patrol/Type/GeoSpec.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.GeoSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.Geo as Geo+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Geo" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let geo = Geo.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON geo `Hspec.shouldBe` json++    Hspec.it "works with a city" $ do+      let geo = Geo.empty {Geo.city = Text.pack "example-city"}+          json = [Aeson.aesonQQ| { "city": "example-city" } |]+      Aeson.toJSON geo `Hspec.shouldBe` json++    Hspec.it "works with a country code" $ do+      let geo = Geo.empty {Geo.countryCode = Text.pack "example-country-code"}+          json = [Aeson.aesonQQ| { "country_code": "example-country-code" } |]+      Aeson.toJSON geo `Hspec.shouldBe` json++    Hspec.it "works with a region" $ do+      let geo = Geo.empty {Geo.region = Text.pack "example-region"}+          json = [Aeson.aesonQQ| { "region": "example-region" } |]+      Aeson.toJSON geo `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/GpuContextSpec.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.GpuContextSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.GpuContext as GpuContext+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.GpuContext" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let gpuContext = GpuContext.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON gpuContext `Hspec.shouldBe` json++    Hspec.it "works with apiType" $ do+      let gpuContext = GpuContext.empty {GpuContext.apiType = Text.pack "example-apiType"}+          json = [Aeson.aesonQQ| { "api_type": "example-apiType" } |]+      Aeson.toJSON gpuContext `Hspec.shouldBe` json++    Hspec.it "works with graphicsShaderLevel" $ do+      let gpuContext = GpuContext.empty {GpuContext.graphicsShaderLevel = Text.pack "example-graphicsShaderLevel"}+          json = [Aeson.aesonQQ| { "graphics_shader_level": "example-graphicsShaderLevel" } |]+      Aeson.toJSON gpuContext `Hspec.shouldBe` json++    Hspec.it "works with id" $ do+      let gpuContext = GpuContext.empty {GpuContext.id = Aeson.Bool True}+          json = [Aeson.aesonQQ| { "id": true } |]+      Aeson.toJSON gpuContext `Hspec.shouldBe` json++    Hspec.it "works with maxTextureSize" $ do+      let gpuContext = GpuContext.empty {GpuContext.maxTextureSize = Just 0}+          json = [Aeson.aesonQQ| { "max_texture_size": 0 } |]+      Aeson.toJSON gpuContext `Hspec.shouldBe` json++    Hspec.it "works with memorySize" $ do+      let gpuContext = GpuContext.empty {GpuContext.memorySize = Just 0.1}+          json = [Aeson.aesonQQ| { "memory_size": 0.1 } |]+      Aeson.toJSON gpuContext `Hspec.shouldBe` json++    Hspec.it "works with multiThreadedRendering" $ do+      let gpuContext = GpuContext.empty {GpuContext.multiThreadedRendering = Just True}+          json = [Aeson.aesonQQ| { "multi_threaded_rendering": true } |]+      Aeson.toJSON gpuContext `Hspec.shouldBe` json++    Hspec.it "works with name" $ do+      let gpuContext = GpuContext.empty {GpuContext.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON gpuContext `Hspec.shouldBe` json++    Hspec.it "works with npotSupport" $ do+      let gpuContext = GpuContext.empty {GpuContext.npotSupport = Text.pack "example-npotSupport"}+          json = [Aeson.aesonQQ| { "npot_support": "example-npotSupport" } |]+      Aeson.toJSON gpuContext `Hspec.shouldBe` json++    Hspec.it "works with supportsComputeShaders" $ do+      let gpuContext = GpuContext.empty {GpuContext.supportsComputeShaders = Just True}+          json = [Aeson.aesonQQ| { "supports_compute_shaders": true } |]+      Aeson.toJSON gpuContext `Hspec.shouldBe` json++    Hspec.it "works with supportsGeometryShaders" $ do+      let gpuContext = GpuContext.empty {GpuContext.supportsGeometryShaders = Just True}+          json = [Aeson.aesonQQ| { "supports_geometry_shaders": true } |]+      Aeson.toJSON gpuContext `Hspec.shouldBe` json++    Hspec.it "works with supportsRayTracing" $ do+      let gpuContext = GpuContext.empty {GpuContext.supportsRayTracing = Just True}+          json = [Aeson.aesonQQ| { "supports_ray_tracing": true } |]+      Aeson.toJSON gpuContext `Hspec.shouldBe` json++    Hspec.it "works with vendorId" $ do+      let gpuContext = GpuContext.empty {GpuContext.vendorId = Text.pack "example-vendorId"}+          json = [Aeson.aesonQQ| { "vendor_id": "example-vendorId" } |]+      Aeson.toJSON gpuContext `Hspec.shouldBe` json++    Hspec.it "works with vendorName" $ do+      let gpuContext = GpuContext.empty {GpuContext.vendorName = Text.pack "example-vendorName"}+          json = [Aeson.aesonQQ| { "vendor_name": "example-vendorName" } |]+      Aeson.toJSON gpuContext `Hspec.shouldBe` json++    Hspec.it "works with version" $ do+      let gpuContext = GpuContext.empty {GpuContext.version = Text.pack "example-version"}+          json = [Aeson.aesonQQ| { "version": "example-version" } |]+      Aeson.toJSON gpuContext `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/LevelSpec.hs view
@@ -0,0 +1,26 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.LevelSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Patrol.Type.Level as Level+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Level" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works for Debug" $ do+      Aeson.toJSON Level.Debug `Hspec.shouldBe` [Aeson.aesonQQ| "debug" |]++    Hspec.it "works for Info" $ do+      Aeson.toJSON Level.Info `Hspec.shouldBe` [Aeson.aesonQQ| "info" |]++    Hspec.it "works for Warning" $ do+      Aeson.toJSON Level.Warning `Hspec.shouldBe` [Aeson.aesonQQ| "warning" |]++    Hspec.it "works for Error" $ do+      Aeson.toJSON Level.Error `Hspec.shouldBe` [Aeson.aesonQQ| "error" |]++    Hspec.it "works for Fatal" $ do+      Aeson.toJSON Level.Fatal `Hspec.shouldBe` [Aeson.aesonQQ| "fatal" |]
+ source/test-suite/Patrol/Type/LogEntrySpec.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.LogEntrySpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.LogEntry as LogEntry+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.LogEntry" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let logEntry = LogEntry.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON logEntry `Hspec.shouldBe` json++    Hspec.it "works with a formatted string" $ do+      let logEntry = LogEntry.empty {LogEntry.formatted = Text.pack "example-formatted"}+          json = [Aeson.aesonQQ| { "formatted": "example-formatted" } |]+      Aeson.toJSON logEntry `Hspec.shouldBe` json++    Hspec.it "works with a message" $ do+      let logEntry = LogEntry.empty {LogEntry.message = Text.pack "example-message"}+          json = [Aeson.aesonQQ| { "message": "example-message" } |]+      Aeson.toJSON logEntry `Hspec.shouldBe` json++    Hspec.it "works with some params" $ do+      let logEntry = LogEntry.empty {LogEntry.params = Aeson.Bool True}+          json = [Aeson.aesonQQ| { "params": true } |]+      Aeson.toJSON logEntry `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/MachExceptionSpec.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.MachExceptionSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.MachException as MachException+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.MachException" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let machException = MachException.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON machException `Hspec.shouldBe` json++    Hspec.it "works with a code" $ do+      let machException = MachException.empty {MachException.code = Just 0}+          json = [Aeson.aesonQQ| { "code": 0 } |]+      Aeson.toJSON machException `Hspec.shouldBe` json++    Hspec.it "works with an exception" $ do+      let machException = MachException.empty {MachException.exception = Just 0}+          json = [Aeson.aesonQQ| { "exception": 0 } |]+      Aeson.toJSON machException `Hspec.shouldBe` json++    Hspec.it "works with a name" $ do+      let machException = MachException.empty {MachException.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON machException `Hspec.shouldBe` json++    Hspec.it "works with a subcode" $ do+      let machException = MachException.empty {MachException.subcode = Just 0}+          json = [Aeson.aesonQQ| { "subcode": 0 } |]+      Aeson.toJSON machException `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/MechanismMetaSpec.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.MechanismMetaSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Patrol.Type.CError as CError+import qualified Patrol.Type.MachException as MachException+import qualified Patrol.Type.MechanismMeta as MechanismMeta+import qualified Patrol.Type.NsError as NsError+import qualified Patrol.Type.PosixSignal as PosixSignal+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.MechanismMeta" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let mechanismMeta = MechanismMeta.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON mechanismMeta `Hspec.shouldBe` json++    Hspec.it "works with a C error" $ do+      let cError = CError.empty {CError.number = Just 0}+          mechanismMeta = MechanismMeta.empty {MechanismMeta.errno = Just cError}+          json = [Aeson.aesonQQ| { "errno": { "number": 0 } } |]+      Aeson.toJSON mechanismMeta `Hspec.shouldBe` json++    Hspec.it "works with a mach exception" $ do+      let machException =+            MachException.empty+              { MachException.code = Just 0+              }+          mechanismMeta = MechanismMeta.empty {MechanismMeta.machException = Just machException}+          json = [Aeson.aesonQQ| { "mach_exception": { "code": 0 } } |]+      Aeson.toJSON mechanismMeta `Hspec.shouldBe` json++    Hspec.it "works with an NS error" $ do+      let nsError = NsError.empty {NsError.code = Just 0}+          mechanismMeta = MechanismMeta.empty {MechanismMeta.nsError = Just nsError}+          json = [Aeson.aesonQQ| { "ns_error": { "code": 0 } } |]+      Aeson.toJSON mechanismMeta `Hspec.shouldBe` json++    Hspec.it "works with a signal" $ do+      let posixSignal = PosixSignal.empty {PosixSignal.code = Just 0}+          mechanismMeta = MechanismMeta.empty {MechanismMeta.signal = Just posixSignal}+          json = [Aeson.aesonQQ| { "signal": { "code": 0 } } |]+      Aeson.toJSON mechanismMeta `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/MechanismSpec.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.MechanismSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Patrol.Type.CError as CError+import qualified Patrol.Type.Mechanism as Mechanism+import qualified Patrol.Type.MechanismMeta as MechanismMeta+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Mechanism" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let mechanism = Mechanism.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON mechanism `Hspec.shouldBe` json++    Hspec.it "works with some data" $ do+      let mechanism = Mechanism.empty {Mechanism.data_ = Map.singleton (Text.pack "example-data") Aeson.Null}+          json = [Aeson.aesonQQ| { "data": { "example-data": null } } |]+      Aeson.toJSON mechanism `Hspec.shouldBe` json++    Hspec.it "works with a description" $ do+      let mechanism = Mechanism.empty {Mechanism.description = Text.pack "example-description"}+          json = [Aeson.aesonQQ| { "description": "example-description" } |]+      Aeson.toJSON mechanism `Hspec.shouldBe` json++    Hspec.it "works with a handled flag" $ do+      let mechanism = Mechanism.empty {Mechanism.handled = Just True}+          json = [Aeson.aesonQQ| { "handled": true } |]+      Aeson.toJSON mechanism `Hspec.shouldBe` json++    Hspec.it "works with a help link" $ do+      let mechanism = Mechanism.empty {Mechanism.helpLink = Text.pack "example-help-link"}+          json = [Aeson.aesonQQ| { "help_link": "example-help-link" } |]+      Aeson.toJSON mechanism `Hspec.shouldBe` json++    Hspec.it "works with some meta" $ do+      let cError = CError.empty {CError.number = Just 0}+          mechanismMeta = MechanismMeta.empty {MechanismMeta.errno = Just cError}+          mechanism = Mechanism.empty {Mechanism.meta = Just mechanismMeta}+          json = [Aeson.aesonQQ| { "meta": { "errno": { "number": 0 } } } |]+      Aeson.toJSON mechanism `Hspec.shouldBe` json++    Hspec.it "works with a synthetic flag" $ do+      let mechanism = Mechanism.empty {Mechanism.synthetic = Just True}+          json = [Aeson.aesonQQ| { "synthetic": true } |]+      Aeson.toJSON mechanism `Hspec.shouldBe` json++    Hspec.it "works with a type" $ do+      let mechanism = Mechanism.empty {Mechanism.type_ = Text.pack "example-type"}+          json = [Aeson.aesonQQ| { "type": "example-type" } |]+      Aeson.toJSON mechanism `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/NativeDebugImageSpec.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.NativeDebugImageSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.NativeDebugImage as NativeDebugImage+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.NativeDebugImage" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let nativeDebugImage = NativeDebugImage.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON nativeDebugImage `Hspec.shouldBe` json++    Hspec.it "works with an arch" $ do+      let nativeDebugImage = NativeDebugImage.empty {NativeDebugImage.arch = Text.pack "example-arch"}+          json = [Aeson.aesonQQ| { "arch": "example-arch" } |]+      Aeson.toJSON nativeDebugImage `Hspec.shouldBe` json++    Hspec.it "works with a code file" $ do+      let nativeDebugImage = NativeDebugImage.empty {NativeDebugImage.codeFile = Text.pack "example-code-file"}+          json = [Aeson.aesonQQ| { "code_file": "example-code-file" } |]+      Aeson.toJSON nativeDebugImage `Hspec.shouldBe` json++    Hspec.it "works with a code id" $ do+      let nativeDebugImage = NativeDebugImage.empty {NativeDebugImage.codeId = Text.pack "example-code-id"}+          json = [Aeson.aesonQQ| { "code_id": "example-code-id" } |]+      Aeson.toJSON nativeDebugImage `Hspec.shouldBe` json++    Hspec.it "works with a debug file" $ do+      let nativeDebugImage = NativeDebugImage.empty {NativeDebugImage.debugFile = Text.pack "example-debug-file"}+          json = [Aeson.aesonQQ| { "debug_file": "example-debug-file" } |]+      Aeson.toJSON nativeDebugImage `Hspec.shouldBe` json++    Hspec.it "works with a debug id" $ do+      let nativeDebugImage = NativeDebugImage.empty {NativeDebugImage.debugId = Text.pack "example-debug-id"}+          json = [Aeson.aesonQQ| { "debug_id": "example-debug-id" } |]+      Aeson.toJSON nativeDebugImage `Hspec.shouldBe` json++    Hspec.it "works with a image addr" $ do+      let nativeDebugImage = NativeDebugImage.empty {NativeDebugImage.imageAddr = Text.pack "example-image-addr"}+          json = [Aeson.aesonQQ| { "image_addr": "example-image-addr" } |]+      Aeson.toJSON nativeDebugImage `Hspec.shouldBe` json++    Hspec.it "works with a image size" $ do+      let nativeDebugImage = NativeDebugImage.empty {NativeDebugImage.imageSize = Just 0}+          json = [Aeson.aesonQQ| { "image_size": 0 } |]+      Aeson.toJSON nativeDebugImage `Hspec.shouldBe` json++    Hspec.it "works with a image vmaddr" $ do+      let nativeDebugImage = NativeDebugImage.empty {NativeDebugImage.imageVmaddr = Text.pack "example-image-vmaddr"}+          json = [Aeson.aesonQQ| { "image_vmaddr": "example-image-vmaddr" } |]+      Aeson.toJSON nativeDebugImage `Hspec.shouldBe` json++    Hspec.it "works with a type" $ do+      let nativeDebugImage = NativeDebugImage.empty {NativeDebugImage.type_ = Text.pack "example-type"}+          json = [Aeson.aesonQQ| { "type": "example-type" } |]+      Aeson.toJSON nativeDebugImage `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/NsErrorSpec.hs view
@@ -0,0 +1,27 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.NsErrorSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.NsError as NsError+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.NsError" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let nsError = NsError.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON nsError `Hspec.shouldBe` json++    Hspec.it "works with a code" $ do+      let nsError = NsError.empty {NsError.code = Just 0}+          json = [Aeson.aesonQQ| { "code": 0 } |]+      Aeson.toJSON nsError `Hspec.shouldBe` json++    Hspec.it "works with a domain" $ do+      let nsError = NsError.empty {NsError.domain = Text.pack "example-domain"}+          json = [Aeson.aesonQQ| { "domain": "example-domain" } |]+      Aeson.toJSON nsError `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/OsContextSpec.hs view
@@ -0,0 +1,47 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.OsContextSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.OsContext as OsContext+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.OsContext" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let osContext = OsContext.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON osContext `Hspec.shouldBe` json++    Hspec.it "works with build" $ do+      let osContext = OsContext.empty {OsContext.build = Text.pack "example-build"}+          json = [Aeson.aesonQQ| { "build": "example-build" } |]+      Aeson.toJSON osContext `Hspec.shouldBe` json++    Hspec.it "works with kernelVersion" $ do+      let osContext = OsContext.empty {OsContext.kernelVersion = Text.pack "example-kernelVersion"}+          json = [Aeson.aesonQQ| { "kernel_version": "example-kernelVersion" } |]+      Aeson.toJSON osContext `Hspec.shouldBe` json++    Hspec.it "works with name" $ do+      let osContext = OsContext.empty {OsContext.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON osContext `Hspec.shouldBe` json++    Hspec.it "works with rawDescription" $ do+      let osContext = OsContext.empty {OsContext.rawDescription = Text.pack "example-rawDescription"}+          json = [Aeson.aesonQQ| { "raw_description": "example-rawDescription" } |]+      Aeson.toJSON osContext `Hspec.shouldBe` json++    Hspec.it "works with rooted" $ do+      let osContext = OsContext.empty {OsContext.rooted = Just True}+          json = [Aeson.aesonQQ| { "rooted": true } |]+      Aeson.toJSON osContext `Hspec.shouldBe` json++    Hspec.it "works with version" $ do+      let osContext = OsContext.empty {OsContext.version = Text.pack "example-version"}+          json = [Aeson.aesonQQ| { "version": "example-version" } |]+      Aeson.toJSON osContext `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/PlatformSpec.hs view
@@ -0,0 +1,68 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.PlatformSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Patrol.Type.Platform as Platform+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Platform" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works for As3" $ do+      Aeson.toJSON Platform.As3 `Hspec.shouldBe` [Aeson.aesonQQ| "as3" |]++    Hspec.it "works for C" $ do+      Aeson.toJSON Platform.C `Hspec.shouldBe` [Aeson.aesonQQ| "c" |]++    Hspec.it "works for Cfml" $ do+      Aeson.toJSON Platform.Cfml `Hspec.shouldBe` [Aeson.aesonQQ| "cfml" |]++    Hspec.it "works for Cocoa" $ do+      Aeson.toJSON Platform.Cocoa `Hspec.shouldBe` [Aeson.aesonQQ| "cocoa" |]++    Hspec.it "works for Csharp" $ do+      Aeson.toJSON Platform.Csharp `Hspec.shouldBe` [Aeson.aesonQQ| "csharp" |]++    Hspec.it "works for Elixir" $ do+      Aeson.toJSON Platform.Elixir `Hspec.shouldBe` [Aeson.aesonQQ| "elixir" |]++    Hspec.it "works for Haskell" $ do+      Aeson.toJSON Platform.Haskell `Hspec.shouldBe` [Aeson.aesonQQ| "haskell" |]++    Hspec.it "works for Go" $ do+      Aeson.toJSON Platform.Go `Hspec.shouldBe` [Aeson.aesonQQ| "go" |]++    Hspec.it "works for Groovy" $ do+      Aeson.toJSON Platform.Groovy `Hspec.shouldBe` [Aeson.aesonQQ| "groovy" |]++    Hspec.it "works for Java" $ do+      Aeson.toJSON Platform.Java `Hspec.shouldBe` [Aeson.aesonQQ| "java" |]++    Hspec.it "works for Javascript" $ do+      Aeson.toJSON Platform.Javascript `Hspec.shouldBe` [Aeson.aesonQQ| "javascript" |]++    Hspec.it "works for Native" $ do+      Aeson.toJSON Platform.Native `Hspec.shouldBe` [Aeson.aesonQQ| "native" |]++    Hspec.it "works for Node" $ do+      Aeson.toJSON Platform.Node `Hspec.shouldBe` [Aeson.aesonQQ| "node" |]++    Hspec.it "works for Objc" $ do+      Aeson.toJSON Platform.Objc `Hspec.shouldBe` [Aeson.aesonQQ| "objc" |]++    Hspec.it "works for Other" $ do+      Aeson.toJSON Platform.Other `Hspec.shouldBe` [Aeson.aesonQQ| "other" |]++    Hspec.it "works for Perl" $ do+      Aeson.toJSON Platform.Perl `Hspec.shouldBe` [Aeson.aesonQQ| "perl" |]++    Hspec.it "works for Php" $ do+      Aeson.toJSON Platform.Php `Hspec.shouldBe` [Aeson.aesonQQ| "php" |]++    Hspec.it "works for Python" $ do+      Aeson.toJSON Platform.Python `Hspec.shouldBe` [Aeson.aesonQQ| "python" |]++    Hspec.it "works for Ruby" $ do+      Aeson.toJSON Platform.Ruby `Hspec.shouldBe` [Aeson.aesonQQ| "ruby" |]
+ source/test-suite/Patrol/Type/PosixSignalSpec.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.PosixSignalSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.PosixSignal as PosixSignal+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.PosixSignal" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let posixSignal = PosixSignal.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON posixSignal `Hspec.shouldBe` json++    Hspec.it "works with a code" $ do+      let posixSignal = PosixSignal.empty {PosixSignal.code = Just 0}+          json = [Aeson.aesonQQ| { "code": 0 } |]+      Aeson.toJSON posixSignal `Hspec.shouldBe` json++    Hspec.it "works with a code name" $ do+      let posixSignal = PosixSignal.empty {PosixSignal.codeName = Text.pack "example-code-name"}+          json = [Aeson.aesonQQ| { "code_name": "example-code-name" } |]+      Aeson.toJSON posixSignal `Hspec.shouldBe` json++    Hspec.it "works with a name" $ do+      let posixSignal = PosixSignal.empty {PosixSignal.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON posixSignal `Hspec.shouldBe` json++    Hspec.it "works with a number" $ do+      let posixSignal = PosixSignal.empty {PosixSignal.number = Just 0}+          json = [Aeson.aesonQQ| { "number": 0 } |]+      Aeson.toJSON posixSignal `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/ProguardDebugImageSpec.hs view
@@ -0,0 +1,22 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.ProguardDebugImageSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.ProguardDebugImage as ProguardDebugImage+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.ProguardDebugImage" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let proguardDebugImage = ProguardDebugImage.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON proguardDebugImage `Hspec.shouldBe` json++    Hspec.it "works with a UUID" $ do+      let proguardDebugImage = ProguardDebugImage.empty {ProguardDebugImage.uuid = Text.pack "example-uuid"}+          json = [Aeson.aesonQQ| { "uuid": "example-uuid" } |]+      Aeson.toJSON proguardDebugImage `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/RequestSpec.hs view
@@ -0,0 +1,63 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.RequestSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Patrol.Type.Request as Request+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Request" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let request = Request.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON request `Hspec.shouldBe` json++    Hspec.it "works with a cookie" $ do+      let request = Request.empty {Request.cookies = Map.singleton (Text.pack "example-key") $ Text.pack "example-value"}+          json = [Aeson.aesonQQ| { "cookies": { "example-key": "example-value" } } |]+      Aeson.toJSON request `Hspec.shouldBe` json++    Hspec.it "works with some data" $ do+      let request = Request.empty {Request.data_ = Aeson.Bool True}+          json = [Aeson.aesonQQ| { "data": true } |]+      Aeson.toJSON request `Hspec.shouldBe` json++    Hspec.it "works with an env" $ do+      let request = Request.empty {Request.env = Map.singleton (Text.pack "example-key") $ Aeson.Bool True}+          json = [Aeson.aesonQQ| { "env": { "example-key": true } } |]+      Aeson.toJSON request `Hspec.shouldBe` json++    Hspec.it "works with a fragment" $ do+      let request = Request.empty {Request.fragment = Text.pack "example-fragment"}+          json = [Aeson.aesonQQ| { "fragment": "example-fragment" } |]+      Aeson.toJSON request `Hspec.shouldBe` json++    Hspec.it "works with a header" $ do+      let request = Request.empty {Request.headers = Map.singleton (Text.pack "example-key") $ Text.pack "example-value"}+          json = [Aeson.aesonQQ| { "headers": { "example-key": "example-value" } } |]+      Aeson.toJSON request `Hspec.shouldBe` json++    Hspec.it "works with an inferred content type" $ do+      let request = Request.empty {Request.inferredContentType = Text.pack "example-inferred-content-type"}+          json = [Aeson.aesonQQ| { "inferred_content_type": "example-inferred-content-type" } |]+      Aeson.toJSON request `Hspec.shouldBe` json++    Hspec.it "works with a method" $ do+      let request = Request.empty {Request.method = Text.pack "example-method"}+          json = [Aeson.aesonQQ| { "method": "example-method" } |]+      Aeson.toJSON request `Hspec.shouldBe` json++    Hspec.it "works with a query string" $ do+      let request = Request.empty {Request.queryString = Map.singleton (Text.pack "example-key") $ Text.pack "example-value"}+          json = [Aeson.aesonQQ| { "query_string": { "example-key": "example-value" } } |]+      Aeson.toJSON request `Hspec.shouldBe` json++    Hspec.it "works with a URL" $ do+      let request = Request.empty {Request.url = Text.pack "example-url"}+          json = [Aeson.aesonQQ| { "url": "example-url" } |]+      Aeson.toJSON request `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/ResponseSpec.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE TypeApplications #-}++module Patrol.Type.ResponseSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.UUID as Uuid+import qualified Patrol.Type.EventId as EventId+import qualified Patrol.Type.Response as Response+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Response" $ do+  Hspec.describe "FromJSON" $ do+    Hspec.it "works" $ do+      let json = [Aeson.aesonQQ| { "id": "00112233445566778899aabbccddeeff" } |]+          eventId = EventId.fromUuid $ Uuid.fromWords64 0x0011223344556677 0x8899aabbccddeeff+          response = Response.Response {Response.id = eventId}+      Aeson.fromJSON json `Hspec.shouldBe` Aeson.Success response++    Hspec.it "fails with the wrong type" $ do+      let json = [Aeson.aesonQQ| null |]+      Aeson.fromJSON @Response.Response json `Hspec.shouldBe` Aeson.Error "parsing Response failed, expected Object, but encountered Null"
+ source/test-suite/Patrol/Type/RuntimeContextSpec.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.RuntimeContextSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.RuntimeContext as RuntimeContext+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.RuntimeContext" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let runtimeContext = RuntimeContext.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON runtimeContext `Hspec.shouldBe` json++    Hspec.it "works with build" $ do+      let runtimeContext = RuntimeContext.empty {RuntimeContext.build = Text.pack "example-build"}+          json = [Aeson.aesonQQ| { "build": "example-build" } |]+      Aeson.toJSON runtimeContext `Hspec.shouldBe` json++    Hspec.it "works with name" $ do+      let runtimeContext = RuntimeContext.empty {RuntimeContext.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON runtimeContext `Hspec.shouldBe` json++    Hspec.it "works with rawDescription" $ do+      let runtimeContext = RuntimeContext.empty {RuntimeContext.rawDescription = Text.pack "example-rawDescription"}+          json = [Aeson.aesonQQ| { "raw_description": "example-rawDescription" } |]+      Aeson.toJSON runtimeContext `Hspec.shouldBe` json++    Hspec.it "works with version" $ do+      let runtimeContext = RuntimeContext.empty {RuntimeContext.version = Text.pack "example-version"}+          json = [Aeson.aesonQQ| { "version": "example-version" } |]+      Aeson.toJSON runtimeContext `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/SpanStatusSpec.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.SpanStatusSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Patrol.Type.SpanStatus as SpanStatus+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.SpanStatus" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works for Aborted" $ do+      Aeson.toJSON SpanStatus.Aborted `Hspec.shouldBe` [Aeson.aesonQQ| "aborted" |]++    Hspec.it "works for AlreadyExists" $ do+      Aeson.toJSON SpanStatus.AlreadyExists `Hspec.shouldBe` [Aeson.aesonQQ| "already_exists" |]++    Hspec.it "works for Cancelled" $ do+      Aeson.toJSON SpanStatus.Cancelled `Hspec.shouldBe` [Aeson.aesonQQ| "cancelled" |]++    Hspec.it "works for DataLoss" $ do+      Aeson.toJSON SpanStatus.DataLoss `Hspec.shouldBe` [Aeson.aesonQQ| "data_loss" |]++    Hspec.it "works for DeadlineExceeded" $ do+      Aeson.toJSON SpanStatus.DeadlineExceeded `Hspec.shouldBe` [Aeson.aesonQQ| "deadline_exceeded" |]++    Hspec.it "works for FailedPrecondition" $ do+      Aeson.toJSON SpanStatus.FailedPrecondition `Hspec.shouldBe` [Aeson.aesonQQ| "failed_precondition" |]++    Hspec.it "works for InternalError" $ do+      Aeson.toJSON SpanStatus.InternalError `Hspec.shouldBe` [Aeson.aesonQQ| "internal_error" |]++    Hspec.it "works for InvalidArgument" $ do+      Aeson.toJSON SpanStatus.InvalidArgument `Hspec.shouldBe` [Aeson.aesonQQ| "invalid_argument" |]++    Hspec.it "works for NotFound" $ do+      Aeson.toJSON SpanStatus.NotFound `Hspec.shouldBe` [Aeson.aesonQQ| "not_found" |]++    Hspec.it "works for Ok" $ do+      Aeson.toJSON SpanStatus.Ok `Hspec.shouldBe` [Aeson.aesonQQ| "ok" |]++    Hspec.it "works for OutOfRange" $ do+      Aeson.toJSON SpanStatus.OutOfRange `Hspec.shouldBe` [Aeson.aesonQQ| "out_of_range" |]++    Hspec.it "works for PermissionDenied" $ do+      Aeson.toJSON SpanStatus.PermissionDenied `Hspec.shouldBe` [Aeson.aesonQQ| "permission_denied" |]++    Hspec.it "works for ResourceExhausted" $ do+      Aeson.toJSON SpanStatus.ResourceExhausted `Hspec.shouldBe` [Aeson.aesonQQ| "resource_exhausted" |]++    Hspec.it "works for Unauthenticated" $ do+      Aeson.toJSON SpanStatus.Unauthenticated `Hspec.shouldBe` [Aeson.aesonQQ| "unauthenticated" |]++    Hspec.it "works for Unavailable" $ do+      Aeson.toJSON SpanStatus.Unavailable `Hspec.shouldBe` [Aeson.aesonQQ| "unavailable" |]++    Hspec.it "works for Unimplemented" $ do+      Aeson.toJSON SpanStatus.Unimplemented `Hspec.shouldBe` [Aeson.aesonQQ| "unimplemented" |]++    Hspec.it "works for Unknown" $ do+      Aeson.toJSON SpanStatus.Unknown `Hspec.shouldBe` [Aeson.aesonQQ| "unknown" |]
+ source/test-suite/Patrol/Type/StacktraceSpec.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.StacktraceSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified GHC.Stack as Stack+import qualified Patrol.Type.Frame as Frame+import qualified Patrol.Type.Stacktrace as Stacktrace+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Stacktrace" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let stacktrace = Stacktrace.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON stacktrace `Hspec.shouldBe` json++    Hspec.it "works with a frame" $ do+      let frame = Frame.empty+          stacktrace = Stacktrace.empty {Stacktrace.frames = [frame]}+          json = [Aeson.aesonQQ| { "frames": [ {} ] } |]+      Aeson.toJSON stacktrace `Hspec.shouldBe` json++    Hspec.it "works with some registers" $ do+      let stacktrace = Stacktrace.empty {Stacktrace.registers = Map.singleton (Text.pack "example-key") $ Text.pack "example-value"}+          json = [Aeson.aesonQQ| { "registers": { "example-key": "example-value" } } |]+      Aeson.toJSON stacktrace `Hspec.shouldBe` json++  Hspec.describe "fromCallStack" $ do+    Hspec.it "works with an empty call stack" $ do+      let callStack = Stack.emptyCallStack+          stacktrace = Stacktrace.empty+      Stacktrace.fromCallStack callStack `Hspec.shouldBe` stacktrace++    Hspec.it "works with a single call site" $ do+      let emptySrcLoc =+            Stack.SrcLoc+              { Stack.srcLocEndCol = 0,+                Stack.srcLocEndLine = 0,+                Stack.srcLocFile = "",+                Stack.srcLocModule = "",+                Stack.srcLocPackage = "",+                Stack.srcLocStartCol = 0,+                Stack.srcLocStartLine = 0+              }+          callStack =+            Stack.fromCallSiteList+              [ ("example-function", emptySrcLoc)+              ]+          frame =+            (Frame.fromSrcLoc emptySrcLoc)+              { Frame.function = Text.pack "example-function"+              }+          stacktrace =+            Stacktrace.empty+              { Stacktrace.frames = [frame]+              }+      Stacktrace.fromCallStack callStack `Hspec.shouldBe` stacktrace++    Hspec.it "works with two call sites" $ do+      let srcLoc1 =+            Stack.SrcLoc+              { Stack.srcLocEndCol = 1,+                Stack.srcLocEndLine = 1,+                Stack.srcLocFile = "example-file-1",+                Stack.srcLocModule = "example-module-1",+                Stack.srcLocPackage = "example-package-1",+                Stack.srcLocStartCol = 1,+                Stack.srcLocStartLine = 1+              }+          srcLoc2 =+            Stack.SrcLoc+              { Stack.srcLocEndCol = 2,+                Stack.srcLocEndLine = 2,+                Stack.srcLocFile = "example-file-2",+                Stack.srcLocModule = "example-module-2",+                Stack.srcLocPackage = "example-package-2",+                Stack.srcLocStartCol = 2,+                Stack.srcLocStartLine = 2+              }+          callStack =+            Stack.fromCallSiteList+              [ ("example-function-1", srcLoc1),+                ("example-function-2", srcLoc2)+              ]+          frame1 =+            (Frame.fromSrcLoc srcLoc1)+              { Frame.function = Text.pack "example-function-1"+              }+          frame2 =+            (Frame.fromSrcLoc srcLoc2)+              { Frame.function = Text.pack "example-function-2"+              }+          stacktrace =+            Stacktrace.empty+              { Stacktrace.frames = [frame2, frame1]+              }+      Stacktrace.fromCallStack callStack `Hspec.shouldBe` stacktrace
+ source/test-suite/Patrol/Type/SystemSdkInfoSpec.hs view
@@ -0,0 +1,37 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.SystemSdkInfoSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.SystemSdkInfo as SystemSdkInfo+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.SystemSdkInfo" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let systemSdkInfo = SystemSdkInfo.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON systemSdkInfo `Hspec.shouldBe` json++    Hspec.it "works with an SDK name" $ do+      let systemSdkInfo = SystemSdkInfo.empty {SystemSdkInfo.sdkName = Text.pack "example-sdk-name"}+          json = [Aeson.aesonQQ| { "sdk_name": "example-sdk-name" } |]+      Aeson.toJSON systemSdkInfo `Hspec.shouldBe` json++    Hspec.it "works with a major version number" $ do+      let systemSdkInfo = SystemSdkInfo.empty {SystemSdkInfo.versionMajor = Just 0}+          json = [Aeson.aesonQQ| { "version_major": 0 } |]+      Aeson.toJSON systemSdkInfo `Hspec.shouldBe` json++    Hspec.it "works with a minor version number" $ do+      let systemSdkInfo = SystemSdkInfo.empty {SystemSdkInfo.versionMinor = Just 0}+          json = [Aeson.aesonQQ| { "version_minor": 0 } |]+      Aeson.toJSON systemSdkInfo `Hspec.shouldBe` json++    Hspec.it "works with a patch version number" $ do+      let systemSdkInfo = SystemSdkInfo.empty {SystemSdkInfo.versionPatchlevel = Just 0}+          json = [Aeson.aesonQQ| { "version_patchlevel": 0 } |]+      Aeson.toJSON systemSdkInfo `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/ThreadSpec.hs view
@@ -0,0 +1,45 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.ThreadSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Patrol.Type.Stacktrace as Stacktrace+import qualified Patrol.Type.Thread as Thread+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Thread" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let thread = Thread.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON thread `Hspec.shouldBe` json++    Hspec.it "works with a crashed flag" $ do+      let thread = Thread.empty {Thread.crashed = Just True}+          json = [Aeson.aesonQQ| { "crashed": true } |]+      Aeson.toJSON thread `Hspec.shouldBe` json++    Hspec.it "works with a current flag" $ do+      let thread = Thread.empty {Thread.current = Just True}+          json = [Aeson.aesonQQ| { "current": true } |]+      Aeson.toJSON thread `Hspec.shouldBe` json++    Hspec.it "works with an ID" $ do+      let thread = Thread.empty {Thread.id = Text.pack "example-id"}+          json = [Aeson.aesonQQ| { "id": "example-id" } |]+      Aeson.toJSON thread `Hspec.shouldBe` json++    Hspec.it "works with a name" $ do+      let thread = Thread.empty {Thread.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON thread `Hspec.shouldBe` json++    Hspec.it "works with a stack trace" $ do+      let stacktrace = Stacktrace.empty {Stacktrace.registers = Map.singleton (Text.pack "example-key") $ Text.pack "example-value"}+          thread = Thread.empty {Thread.stacktrace = Just stacktrace}+          json = [Aeson.aesonQQ| { "stacktrace": { "registers": { "example-key": "example-value" } } } |]+      Aeson.toJSON thread `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/ThreadsSpec.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.ThreadsSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Patrol.Type.Thread as Thread+import qualified Patrol.Type.Threads as Threads+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.Threads" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let threads = Threads.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON threads `Hspec.shouldBe` json++    Hspec.it "works with a value" $ do+      let thread = Thread.empty {Thread.crashed = Just True}+          threads = Threads.empty {Threads.values = [thread]}+          json = [Aeson.aesonQQ| { "values": [ { "crashed": true } ] } |]+      Aeson.toJSON threads `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/TraceContextSpec.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.TraceContextSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.SpanStatus as SpanStatus+import qualified Patrol.Type.TraceContext as TraceContext+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.TraceContext" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let traceContext = TraceContext.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON traceContext `Hspec.shouldBe` json++    Hspec.it "works with exclusiveTime" $ do+      let traceContext = TraceContext.empty {TraceContext.exclusiveTime = Just 0}+          json = [Aeson.aesonQQ| { "exclusive_time": 0 } |]+      Aeson.toJSON traceContext `Hspec.shouldBe` json++    Hspec.it "works with op" $ do+      let traceContext = TraceContext.empty {TraceContext.op = Text.pack "example-op"}+          json = [Aeson.aesonQQ| { "op": "example-op" } |]+      Aeson.toJSON traceContext `Hspec.shouldBe` json++    Hspec.it "works with parentSpanId" $ do+      let traceContext = TraceContext.empty {TraceContext.parentSpanId = Text.pack "example-parentSpanId"}+          json = [Aeson.aesonQQ| { "parent_span_id": "example-parentSpanId" } |]+      Aeson.toJSON traceContext `Hspec.shouldBe` json++    Hspec.it "works with spanId" $ do+      let traceContext = TraceContext.empty {TraceContext.spanId = Text.pack "example-spanId"}+          json = [Aeson.aesonQQ| { "span_id": "example-spanId" } |]+      Aeson.toJSON traceContext `Hspec.shouldBe` json++    Hspec.it "works with status" $ do+      let traceContext = TraceContext.empty {TraceContext.status = Just SpanStatus.Unknown}+          json = [Aeson.aesonQQ| { "status": "unknown" } |]+      Aeson.toJSON traceContext `Hspec.shouldBe` json++    Hspec.it "works with traceId" $ do+      let traceContext = TraceContext.empty {TraceContext.traceId = Text.pack "example-traceId"}+          json = [Aeson.aesonQQ| { "trace_id": "example-traceId" } |]+      Aeson.toJSON traceContext `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/TransactionInfoSpec.hs view
@@ -0,0 +1,28 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.TransactionInfoSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Text as Text+import qualified Patrol.Type.TransactionInfo as TransactionInfo+import qualified Patrol.Type.TransactionSource as TransactionSource+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.TransactionInfo" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let transactionInfo = TransactionInfo.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON transactionInfo `Hspec.shouldBe` json++    Hspec.it "works with an original" $ do+      let transactionInfo = TransactionInfo.empty {TransactionInfo.original = Text.pack "example-original"}+          json = [Aeson.aesonQQ| { "original": "example-original" } |]+      Aeson.toJSON transactionInfo `Hspec.shouldBe` json++    Hspec.it "works with a source" $ do+      let transactionInfo = TransactionInfo.empty {TransactionInfo.source = Just TransactionSource.Unknown}+          json = [Aeson.aesonQQ| { "source": "unknown" } |]+      Aeson.toJSON transactionInfo `Hspec.shouldBe` json
+ source/test-suite/Patrol/Type/TransactionSourceSpec.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.TransactionSourceSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Patrol.Type.TransactionSource as TransactionSource+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.TransactionSource" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works for Component" $ do+      Aeson.toJSON TransactionSource.Component `Hspec.shouldBe` [Aeson.aesonQQ| "component" |]++    Hspec.it "works for Custom" $ do+      Aeson.toJSON TransactionSource.Custom `Hspec.shouldBe` [Aeson.aesonQQ| "custom" |]++    Hspec.it "works for Route" $ do+      Aeson.toJSON TransactionSource.Route `Hspec.shouldBe` [Aeson.aesonQQ| "route" |]++    Hspec.it "works for Task" $ do+      Aeson.toJSON TransactionSource.Task `Hspec.shouldBe` [Aeson.aesonQQ| "task" |]++    Hspec.it "works for Url" $ do+      Aeson.toJSON TransactionSource.Url `Hspec.shouldBe` [Aeson.aesonQQ| "url" |]++    Hspec.it "works for Unknown" $ do+      Aeson.toJSON TransactionSource.Unknown `Hspec.shouldBe` [Aeson.aesonQQ| "unknown" |]++    Hspec.it "works for View" $ do+      Aeson.toJSON TransactionSource.View `Hspec.shouldBe` [Aeson.aesonQQ| "view" |]
+ source/test-suite/Patrol/Type/UserSpec.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE QuasiQuotes #-}++module Patrol.Type.UserSpec where++import qualified Data.Aeson as Aeson+import qualified Data.Aeson.QQ.Simple as Aeson+import qualified Data.Map as Map+import qualified Data.Text as Text+import qualified Patrol.Type.Geo as Geo+import qualified Patrol.Type.User as User+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = Hspec.describe "Patrol.Type.User" $ do+  Hspec.describe "ToJSON" $ do+    Hspec.it "works" $ do+      let user = User.empty+          json = [Aeson.aesonQQ| {} |]+      Aeson.toJSON user `Hspec.shouldBe` json++    Hspec.it "works with some data" $ do+      let user = User.empty {User.data_ = Map.singleton (Text.pack "example-data") (Aeson.Bool True)}+          json = [Aeson.aesonQQ| { "data": { "example-data": true } } |]+      Aeson.toJSON user `Hspec.shouldBe` json++    Hspec.it "works with an email" $ do+      let user = User.empty {User.email = Text.pack "example-email"}+          json = [Aeson.aesonQQ| { "email": "example-email" } |]+      Aeson.toJSON user `Hspec.shouldBe` json++    Hspec.it "works with a geo" $ do+      let geo = Geo.empty {Geo.city = Text.pack "example-city"}+          user = User.empty {User.geo = Just geo}+          json = [Aeson.aesonQQ| { "geo": { "city": "example-city" } } |]+      Aeson.toJSON user `Hspec.shouldBe` json++    Hspec.it "works with an id" $ do+      let user = User.empty {User.id = Text.pack "example-id"}+          json = [Aeson.aesonQQ| { "id": "example-id" } |]+      Aeson.toJSON user `Hspec.shouldBe` json++    Hspec.it "works with an IP address" $ do+      let user = User.empty {User.ipAddress = Text.pack "example-ip-address"}+          json = [Aeson.aesonQQ| { "ip_address": "example-ip-address" } |]+      Aeson.toJSON user `Hspec.shouldBe` json++    Hspec.it "works with a name" $ do+      let user = User.empty {User.name = Text.pack "example-name"}+          json = [Aeson.aesonQQ| { "name": "example-name" } |]+      Aeson.toJSON user `Hspec.shouldBe` json++    Hspec.it "works with a segment" $ do+      let user = User.empty {User.segment = Text.pack "example-segment"}+          json = [Aeson.aesonQQ| { "segment": "example-segment" } |]+      Aeson.toJSON user `Hspec.shouldBe` json++    Hspec.it "works with a username" $ do+      let user = User.empty {User.username = Text.pack "example-username"}+          json = [Aeson.aesonQQ| { "username": "example-username" } |]+      Aeson.toJSON user `Hspec.shouldBe` json
+ source/test-suite/PatrolSpec.hs view
@@ -0,0 +1,108 @@+module PatrolSpec where++import qualified Patrol.ClientSpec+import qualified Patrol.ConstantSpec+import qualified Patrol.Extra.AesonSpec+import qualified Patrol.Extra.ListSpec+import qualified Patrol.Type.AppContextSpec+import qualified Patrol.Type.AppleDebugImageSpec+import qualified Patrol.Type.BreadcrumbSpec+import qualified Patrol.Type.BreadcrumbTypeSpec+import qualified Patrol.Type.BreadcrumbsSpec+import qualified Patrol.Type.BrowserContextSpec+import qualified Patrol.Type.CErrorSpec+import qualified Patrol.Type.ClientSdkInfoSpec+import qualified Patrol.Type.ClientSdkPackageSpec+import qualified Patrol.Type.ContextSpec+import qualified Patrol.Type.DebugImageSpec+import qualified Patrol.Type.DebugMetaSpec+import qualified Patrol.Type.DeviceContextSpec+import qualified Patrol.Type.DsnSpec+import qualified Patrol.Type.ErrorTypeSpec+import qualified Patrol.Type.EventIdSpec+import qualified Patrol.Type.EventProcessingErrorSpec+import qualified Patrol.Type.EventSpec+import qualified Patrol.Type.EventTypeSpec+import qualified Patrol.Type.ExceptionSpec+import qualified Patrol.Type.ExceptionsSpec+import qualified Patrol.Type.FrameSpec+import qualified Patrol.Type.GeoSpec+import qualified Patrol.Type.GpuContextSpec+import qualified Patrol.Type.LevelSpec+import qualified Patrol.Type.LogEntrySpec+import qualified Patrol.Type.MachExceptionSpec+import qualified Patrol.Type.MechanismMetaSpec+import qualified Patrol.Type.MechanismSpec+import qualified Patrol.Type.NativeDebugImageSpec+import qualified Patrol.Type.NsErrorSpec+import qualified Patrol.Type.OsContextSpec+import qualified Patrol.Type.PlatformSpec+import qualified Patrol.Type.PosixSignalSpec+import qualified Patrol.Type.ProguardDebugImageSpec+import qualified Patrol.Type.RequestSpec+import qualified Patrol.Type.ResponseSpec+import qualified Patrol.Type.RuntimeContextSpec+import qualified Patrol.Type.SpanStatusSpec+import qualified Patrol.Type.StacktraceSpec+import qualified Patrol.Type.SystemSdkInfoSpec+import qualified Patrol.Type.ThreadSpec+import qualified Patrol.Type.ThreadsSpec+import qualified Patrol.Type.TraceContextSpec+import qualified Patrol.Type.TransactionInfoSpec+import qualified Patrol.Type.TransactionSourceSpec+import qualified Patrol.Type.UserSpec+import qualified Test.Hspec as Hspec++spec :: Hspec.Spec+spec = do+  Patrol.ClientSpec.spec+  Patrol.ConstantSpec.spec+  Patrol.Extra.AesonSpec.spec+  Patrol.Extra.ListSpec.spec+  Patrol.Type.AppContextSpec.spec+  Patrol.Type.AppleDebugImageSpec.spec+  Patrol.Type.BreadcrumbSpec.spec+  Patrol.Type.BreadcrumbsSpec.spec+  Patrol.Type.BreadcrumbTypeSpec.spec+  Patrol.Type.BrowserContextSpec.spec+  Patrol.Type.CErrorSpec.spec+  Patrol.Type.ClientSdkInfoSpec.spec+  Patrol.Type.ClientSdkPackageSpec.spec+  Patrol.Type.ContextSpec.spec+  Patrol.Type.DebugImageSpec.spec+  Patrol.Type.DebugMetaSpec.spec+  Patrol.Type.DeviceContextSpec.spec+  Patrol.Type.DsnSpec.spec+  Patrol.Type.ErrorTypeSpec.spec+  Patrol.Type.EventIdSpec.spec+  Patrol.Type.EventProcessingErrorSpec.spec+  Patrol.Type.EventSpec.spec+  Patrol.Type.EventTypeSpec.spec+  Patrol.Type.ExceptionSpec.spec+  Patrol.Type.ExceptionsSpec.spec+  Patrol.Type.FrameSpec.spec+  Patrol.Type.GeoSpec.spec+  Patrol.Type.GpuContextSpec.spec+  Patrol.Type.LevelSpec.spec+  Patrol.Type.LogEntrySpec.spec+  Patrol.Type.MachExceptionSpec.spec+  Patrol.Type.MechanismMetaSpec.spec+  Patrol.Type.MechanismSpec.spec+  Patrol.Type.NativeDebugImageSpec.spec+  Patrol.Type.NsErrorSpec.spec+  Patrol.Type.OsContextSpec.spec+  Patrol.Type.PlatformSpec.spec+  Patrol.Type.PosixSignalSpec.spec+  Patrol.Type.ProguardDebugImageSpec.spec+  Patrol.Type.RequestSpec.spec+  Patrol.Type.ResponseSpec.spec+  Patrol.Type.RuntimeContextSpec.spec+  Patrol.Type.SpanStatusSpec.spec+  Patrol.Type.StacktraceSpec.spec+  Patrol.Type.SystemSdkInfoSpec.spec+  Patrol.Type.ThreadSpec.spec+  Patrol.Type.ThreadsSpec.spec+  Patrol.Type.TraceContextSpec.spec+  Patrol.Type.TransactionInfoSpec.spec+  Patrol.Type.TransactionSourceSpec.spec+  Patrol.Type.UserSpec.spec
− src/lib/Patrol.hs
@@ -1,28 +0,0 @@--- | <https://develop.sentry.dev/sdk/>-module Patrol-  ( Dsn.Dsn-  , Event.Event-  , EventId.EventId-  , Exception.Exception-  , Frame.Frame-  , Level.Level-  , Platform.Platform-  , Request.Request-  , Response.Response-  , StackTrace.StackTrace-  , Timestamp.Timestamp-  , User.User-  ) where--import qualified Patrol.Type.Dsn as Dsn-import qualified Patrol.Type.Event as Event-import qualified Patrol.Type.EventId as EventId-import qualified Patrol.Type.Exception as Exception-import qualified Patrol.Type.Frame as Frame-import qualified Patrol.Type.Level as Level-import qualified Patrol.Type.Platform as Platform-import qualified Patrol.Type.Request as Request-import qualified Patrol.Type.Response as Response-import qualified Patrol.Type.StackTrace as StackTrace-import qualified Patrol.Type.Timestamp as Timestamp-import qualified Patrol.Type.User as User
− src/lib/Patrol/Client.hs
@@ -1,63 +0,0 @@-module Patrol.Client-  ( store-  ) where--import qualified Data.Aeson as Aeson-import qualified Data.ByteString as ByteString-import qualified Data.CaseInsensitive as CI-import qualified Data.Maybe as Maybe-import qualified Data.Text as Text-import qualified Data.Text.Encoding as Text-import qualified Data.Time as Time-import qualified Data.Version as Version-import qualified Network.HTTP.Client as Client-import qualified Network.HTTP.Types as Http-import qualified Paths_patrol as Package-import qualified Patrol.Type.Dsn as Dsn-import qualified Patrol.Type.Event as Event-import qualified Patrol.Type.EventId as EventId-import qualified Patrol.Type.Response as Response---- | <https://develop.sentry.dev/sdk/store/>-store :: Client.Manager -> Dsn.Dsn -> Event.Event -> IO EventId.EventId-store manager dsn event = do-  now <- Time.getCurrentTime-  request <- Client.parseUrlThrow $ makeUrl dsn-  -- TODO: Compress request body.-  response <- Client.httpLbs request-    { Client.requestBody = Client.RequestBodyLBS $ Aeson.encode event-    , Client.requestHeaders =-      [ (Http.hContentType, utf8 "application/json")-      , (Http.hUserAgent, utf8 userAgent)-      , (ci $ utf8 "X-Sentry-Auth", Text.encodeUtf8 . Text.intercalate (Text.singleton ',') $ Maybe.catMaybes-        [ Just $ Text.pack "Sentry sentry_version=7"-        , Just . Text.pack $ "sentry_client=" <> userAgent-        , Just . Text.pack $ "sentry_timestamp=" <> Time.formatTime Time.defaultTimeLocale "%s" now-        , Just $ Text.pack "sentry_key=" <> Dsn.publicKey dsn-        , (\ x -> Text.pack "sentry_secret=" <> x) <$> Dsn.secretKey dsn-        ])-      ]-    , Client.method = Http.methodPost-    } manager-  -- TODO: Handle 429 response codes.-  either fail (pure . Response.id_) . Aeson.eitherDecode $ Client.responseBody response--makeUrl :: Dsn.Dsn -> String-makeUrl dsn =-  Text.unpack (Dsn.protocol dsn)-  <> "://"-  <> Text.unpack (Dsn.host dsn)-  <> maybe "" (\ x -> ":" <> Text.unpack x) (Dsn.port dsn)-  <> Text.unpack (Dsn.path dsn)-  <> "api/"-  <> Text.unpack (Dsn.projectId dsn)-  <> "/store/"--utf8 :: String -> ByteString.ByteString-utf8 = Text.encodeUtf8 . Text.pack--ci :: CI.FoldCase a => a -> CI.CI a-ci = CI.mk--userAgent :: String-userAgent = "patrol/" <> Version.showVersion Package.version
− src/lib/Patrol/Type/Dsn.hs
@@ -1,75 +0,0 @@-module Patrol.Type.Dsn-  ( Dsn(..)-  , fromUri-  , fromString-  , toUri-  , toString-  ) where--import qualified Data.Text as Text-import qualified Network.URI as Uri-import qualified Patrol.Utility.Maybe as Maybe---- | <https://develop.sentry.dev/sdk/overview/#parsing-the-dsn>-data Dsn = Dsn-  { protocol :: Text.Text-  , publicKey :: Text.Text-  , secretKey :: Maybe Text.Text-  , host :: Text.Text-  , port :: Maybe Text.Text-  , path :: Text.Text-  , projectId :: Text.Text-  } deriving (Eq, Show)--fromUri :: Uri.URI -> Either String Dsn-fromUri uri = do-  protocol <- Maybe.note "invalid protocol"-    . Text.stripSuffix (Text.singleton ':')-    . Text.pack-    $ Uri.uriScheme uri-  authority <- Maybe.note "missing authority" $ Uri.uriAuthority uri-  userInfo <- Maybe.note "invalid user info"-    . Text.stripSuffix (Text.singleton '@')-    . Text.pack-    $ Uri.uriUserInfo authority-  let-    (publicKey, secretKey) = Text.drop 1-      <$> Text.breakOn (Text.singleton ':') userInfo-    (host, port) = fmap (Text.drop 1) . Text.breakOn (Text.singleton ':')-      . Text.pack-      $ Uri.uriRegName authority <> Uri.uriPort authority-    (path, projectId) = Text.breakOnEnd (Text.singleton '/')-      . Text.pack-      $ Uri.uriPath uri-  Right Dsn-    { protocol-    , publicKey-    , secretKey = if Text.null secretKey then Nothing else Just secretKey-    , host-    , port = if Text.null port then Nothing else Just port-    , path-    , projectId-    }--fromString :: String -> Either String Dsn-fromString string = do-  uri <- Maybe.note "invalid URI" $ Uri.parseURI string-  fromUri uri--toUri :: Dsn -> Uri.URI-toUri dsn = Uri.URI-  { Uri.uriScheme = Text.unpack (protocol dsn) <> ":"-  , Uri.uriAuthority = Just Uri.URIAuth-    { Uri.uriUserInfo = Text.unpack (publicKey dsn)-      <> maybe "" (\ x -> ":" <> Text.unpack x) (secretKey dsn) <> "@"-    , Uri.uriRegName = Text.unpack (host dsn)-      <> maybe "" (\ x -> ":" <> Text.unpack x) (port dsn)-    , Uri.uriPort = ""-    }-  , Uri.uriPath = Text.unpack (path dsn) <> Text.unpack (projectId dsn)-  , Uri.uriQuery = ""-  , Uri.uriFragment = ""-  }--toString :: Dsn -> String-toString dsn = Uri.uriToString id (toUri dsn) ""
− src/lib/Patrol/Type/Event.hs
@@ -1,86 +0,0 @@-module Patrol.Type.Event-  ( Event(..)-  , new-  ) where--import qualified Control.Monad.IO.Class as IO-import qualified Data.Aeson as Aeson-import qualified Data.Map as Map-import qualified Data.Maybe as Maybe-import qualified Data.Text as Text-import qualified Data.Time as Time-import qualified Data.UUID.V4 as Uuid-import qualified Patrol.Type.EventId as EventId-import qualified Patrol.Type.Exception as Exception-import qualified Patrol.Type.Level as Level-import qualified Patrol.Type.Platform as Platform-import qualified Patrol.Type.Request as Request-import qualified Patrol.Type.Timestamp as Timestamp-import qualified Patrol.Type.User as User-import qualified Patrol.Utility.Json as Json---- | <https://develop.sentry.dev/sdk/event-payloads/>-data Event = Event-  { dist :: Maybe Text.Text-  , environment :: Maybe Text.Text-  , eventId :: EventId.EventId-  , exception :: Maybe [Exception.Exception]-  , extra :: Maybe Aeson.Object-  , fingerprint :: Maybe [Text.Text]-  , level :: Maybe Level.Level-  , logger :: Maybe Text.Text-  , modules :: Maybe (Map.Map Text.Text Text.Text)-  , platform :: Platform.Platform-  , release :: Maybe Text.Text-  , request :: Maybe Request.Request-  , serverName :: Maybe Text.Text-  , tags :: Maybe (Map.Map Text.Text Text.Text)-  , timestamp :: Timestamp.Timestamp-  , transaction :: Maybe Text.Text-  , user :: Maybe User.User-  } deriving (Eq, Show)--instance Aeson.ToJSON Event where-  toJSON event = Aeson.object $ Maybe.catMaybes-    [ Json.pair "dist" <$> dist event-    , Json.pair "environment" <$> environment event-    , Just . Json.pair "event_id" $ eventId event-    , Json.pair "extra" <$> extra event-    , Json.pair "exception" . Aeson.object . pure . Json.pair "values" <$> exception event-    , Json.pair "fingerprint" <$> fingerprint event-    , Json.pair "level" <$> level event-    , Json.pair "logger" <$> logger event-    , Json.pair "modules" <$> modules event-    , Just . Json.pair "platform" $ platform event-    , Json.pair "release" <$> release event-    , Json.pair "request" <$> request event-    , Json.pair "server_name" <$> serverName event-    , Json.pair "tags" <$> tags event-    , Just . Json.pair "timestamp" $ timestamp event-    , Json.pair "transaction" <$> transaction event-    , Json.pair "user" <$> user event-    ]--new :: IO.MonadIO io => io Event-new = IO.liftIO $ do-  eventId <- EventId.fromUuid <$> Uuid.nextRandom-  timestamp <- Timestamp.fromUtcTime <$> Time.getCurrentTime-  pure Event-    { dist = Nothing-    , environment = Nothing-    , eventId-    , exception = Nothing-    , extra = Nothing-    , fingerprint = Nothing-    , level = Nothing-    , logger = Nothing-    , modules = Nothing-    , platform = Platform.Haskell-    , release = Nothing-    , request = Nothing-    , serverName = Nothing-    , tags = Nothing-    , timestamp-    , transaction = Nothing-    , user = Nothing-    }
− src/lib/Patrol/Type/EventId.hs
@@ -1,30 +0,0 @@-module Patrol.Type.EventId-  ( EventId-  , fromUuid-  , toUuid-  ) where--import qualified Data.Aeson as Aeson-import qualified Data.Text as Text-import qualified Data.UUID as Uuid---- | <https://develop.sentry.dev/sdk/event-payloads/#required-attributes>-newtype EventId-  = EventId Uuid.UUID-  deriving (Eq, Show)--instance Aeson.FromJSON EventId where-  parseJSON = Aeson.withText "EventId" $ \ text -> case Text.chunksOf 4 text of-    [a, b, c, d, e, f, g, h] -> maybe (fail "invalid EventId") (pure . fromUuid)-      . Uuid.fromText-      $ Text.intercalate (Text.singleton '-') [a <> b, c, d, e, f <> g <> h]-    _ -> fail "invalid EventId"--instance Aeson.ToJSON EventId where-  toJSON = Aeson.toJSON . Text.filter (/= '-') . Uuid.toText . toUuid--fromUuid :: Uuid.UUID -> EventId-fromUuid = EventId--toUuid :: EventId -> Uuid.UUID-toUuid (EventId x) = x
− src/lib/Patrol/Type/Exception.hs
@@ -1,38 +0,0 @@-module Patrol.Type.Exception-  ( Exception(..)-  , fromSomeException-  ) where--import qualified Control.Exception as Exception-import qualified Data.Aeson as Aeson-import qualified Data.Maybe as Maybe-import qualified Data.Text as Text-import qualified Data.Typeable as Typeable-import qualified Patrol.Type.StackTrace as StackTrace-import qualified Patrol.Utility.Json as Json---- | <https://develop.sentry.dev/sdk/event-payloads/exception/>-data Exception = Exception-  { module_ :: Maybe Text.Text-  , stackTrace :: Maybe StackTrace.StackTrace-  , type_ :: Text.Text-  , value :: Text.Text-  } deriving (Eq, Show)--instance Aeson.ToJSON Exception where-  toJSON exception = Aeson.object $ Maybe.catMaybes-    [ Json.pair "module" <$> module_ exception-    , Json.pair "stacktrace" <$> stackTrace exception-    , Just . Json.pair "type" $ type_ exception-    , Just . Json.pair "value" $ value exception-    ]--fromSomeException :: Exception.SomeException -> Exception-fromSomeException (Exception.SomeException x) =-  let tyCon = Typeable.typeRepTyCon $ Typeable.typeOf x-  in Exception-  { module_ = Just . Text.pack $ Typeable.tyConPackage tyCon <> ":" <> Typeable.tyConModule tyCon-  , stackTrace = Nothing-  , type_ = Text.pack $ Typeable.tyConName tyCon-  , value = Text.pack $ Exception.displayException x-  }
− src/lib/Patrol/Type/Frame.hs
@@ -1,40 +0,0 @@-module Patrol.Type.Frame-  ( Frame(..)-  , fromSrcLoc-  ) where--import qualified Data.Aeson as Aeson-import qualified Data.Maybe as Maybe-import qualified Data.Text as Text-import qualified GHC.Stack as Stack-import qualified Patrol.Utility.Json as Json---- | <https://develop.sentry.dev/sdk/event-payloads/stacktrace/#frame-attributes>-data Frame = Frame-  { colno :: Maybe Int-  , filename :: Maybe Text.Text-  , function :: Text.Text-  , lineno :: Maybe Int-  , module_ :: Maybe Text.Text-  , package :: Maybe Text.Text-  } deriving (Eq, Show)--instance Aeson.ToJSON Frame where-  toJSON frame = Aeson.object $ Maybe.catMaybes-    [ Json.pair "colno" <$> colno frame-    , Json.pair "filename" <$> filename frame-    , Just . Json.pair "function" $ function frame-    , Json.pair "lineno" <$> lineno frame-    , Json.pair "module" <$> module_ frame-    , Json.pair "package" <$> package frame-    ]--fromSrcLoc :: String -> Stack.SrcLoc -> Frame-fromSrcLoc function srcLoc = Frame-  { colno = Just $ Stack.srcLocStartCol srcLoc-  , filename = Just . Text.pack $ Stack.srcLocFile srcLoc-  , function = Text.pack function-  , lineno = Just $ Stack.srcLocStartLine srcLoc-  , module_ = Just . Text.pack $ Stack.srcLocModule srcLoc-  , package = Just . Text.pack $ Stack.srcLocPackage srcLoc-  }
− src/lib/Patrol/Type/Level.hs
@@ -1,22 +0,0 @@-module Patrol.Type.Level-  ( Level(..)-  ) where--import qualified Data.Aeson as Aeson---- | <https://develop.sentry.dev/sdk/event-payloads/#optional-attributes>-data Level-  = Fatal-  | Error-  | Warning-  | Info-  | Debug-  deriving (Eq, Show)--instance Aeson.ToJSON Level where-  toJSON level = Aeson.toJSON $ case level of-    Fatal -> "fatal"-    Error -> "error"-    Warning -> "warning"-    Info -> "info"-    Debug -> "debug"
− src/lib/Patrol/Type/Platform.hs
@@ -1,50 +0,0 @@-module Patrol.Type.Platform-  ( Platform(..)-  ) where--import qualified Data.Aeson as Aeson---- | <https://develop.sentry.dev/sdk/event-payloads/#required-attributes>-data Platform-  = As3-  | C-  | Cfml-  | Cocoa-  | Csharp-  | Elixir-  | Go-  | Groovy-  | Haskell-  | Java-  | Javascript-  | Native-  | Node-  | Objc-  | Other-  | Perl-  | Php-  | Python-  | Ruby-  deriving (Eq, Show)--instance Aeson.ToJSON Platform where-  toJSON platform = Aeson.toJSON $ case platform of-    As3 -> "as3"-    C -> "c"-    Cfml -> "cfml"-    Cocoa -> "cocoa"-    Csharp -> "csharp"-    Elixir -> "elixir"-    Go -> "go"-    Groovy -> "groovy"-    Haskell -> "haskell"-    Java -> "java"-    Javascript -> "javascript"-    Native -> "native"-    Node -> "node"-    Objc -> "objc"-    Other -> "other"-    Perl -> "perl"-    Php -> "php"-    Python -> "python"-    Ruby -> "ruby"
− src/lib/Patrol/Type/Request.hs
@@ -1,30 +0,0 @@-module Patrol.Type.Request-  ( Request(..)-  ) where--import qualified Data.Aeson as Aeson-import qualified Data.Map as Map-import qualified Data.Maybe as Maybe-import qualified Data.Text as Text-import qualified Patrol.Utility.Json as Json--data Request = Request-  { cookies :: Maybe (Map.Map Text.Text Text.Text)-  , data_ :: Maybe Aeson.Value-  , env :: Maybe (Map.Map Text.Text Text.Text)-  , headers :: Maybe (Map.Map Text.Text Text.Text)-  , method :: Maybe Text.Text-  , queryString :: Maybe (Map.Map Text.Text Text.Text)-  , url :: Maybe Text.Text-  } deriving (Eq, Show)--instance Aeson.ToJSON Request where-  toJSON request = Aeson.object $ Maybe.catMaybes-    [ Json.pair "cookies" <$> cookies request-    , Json.pair "env" <$> env request-    , Json.pair "headers" <$> headers request-    , Json.pair "data" <$> data_ request-    , Json.pair "method" <$> method request-    , Json.pair "query_string" <$> queryString request-    , Json.pair "url" <$> url request-    ]
− src/lib/Patrol/Type/Response.hs
@@ -1,17 +0,0 @@-module Patrol.Type.Response-  ( Response(..)-  ) where--import qualified Data.Aeson as Aeson-import qualified Patrol.Type.EventId as EventId-import qualified Patrol.Utility.Json as Json---- | <https://develop.sentry.dev/sdk/overview/#reading-the-response>-newtype Response = Response-  { id_ :: EventId.EventId-  } deriving (Eq, Show)--instance Aeson.FromJSON Response where-  parseJSON = Aeson.withObject "Response" $ \ object -> do-    id_ <- Json.required object "id"-    pure Response { id_ }
− src/lib/Patrol/Type/StackTrace.hs
@@ -1,25 +0,0 @@-module Patrol.Type.StackTrace-  ( StackTrace(..)-  , fromCallStack-  ) where--import qualified Data.Aeson as Aeson-import qualified Data.List.NonEmpty as NonEmpty-import qualified GHC.Stack as Stack-import qualified Patrol.Type.Frame as Frame-import qualified Patrol.Utility.Json as Json---- | <https://develop.sentry.dev/sdk/event-payloads/stacktrace/>-newtype StackTrace = StackTrace-  { frames :: NonEmpty.NonEmpty Frame.Frame-  } deriving (Eq, Show)--instance Aeson.ToJSON StackTrace where-  toJSON stackTrace = Aeson.object-    [ Json.pair "frames" $ frames stackTrace-    ]--fromCallStack :: Stack.CallStack -> Maybe StackTrace-fromCallStack callStack = do-  frames <- NonEmpty.nonEmpty . fmap (uncurry Frame.fromSrcLoc) $ Stack.getCallStack callStack-  pure StackTrace { frames }
− src/lib/Patrol/Type/Timestamp.hs
@@ -1,22 +0,0 @@-module Patrol.Type.Timestamp-  ( Timestamp-  , fromUtcTime-  , toUtcTime-  ) where--import qualified Data.Aeson as Aeson-import qualified Data.Time as Time---- | <https://develop.sentry.dev/sdk/event-payloads/#required-attributes>-newtype Timestamp-  = Timestamp Time.UTCTime-  deriving (Eq, Show)--instance Aeson.ToJSON Timestamp where-  toJSON = Aeson.toJSON . Time.formatTime Time.defaultTimeLocale "%Y-%m-%dT%H:%M:%SZ" . toUtcTime--fromUtcTime :: Time.UTCTime -> Timestamp-fromUtcTime = Timestamp--toUtcTime :: Timestamp -> Time.UTCTime-toUtcTime (Timestamp x) = x
− src/lib/Patrol/Type/User.hs
@@ -1,23 +0,0 @@-module Patrol.Type.User-  ( User(..)-  ) where--import qualified Data.Aeson as Aeson-import qualified Data.Maybe as Maybe-import qualified Data.Text as Text-import qualified Patrol.Utility.Json as Json--data User = User-  { email :: Maybe Text.Text-  , id_ :: Maybe Text.Text-  , ipAddress :: Maybe Text.Text-  , username :: Maybe Text.Text-  } deriving (Eq, Show)--instance Aeson.ToJSON User where-  toJSON request = Aeson.object $ Maybe.catMaybes-    [ Json.pair "email" <$> email request-    , Json.pair "id" <$> id_ request-    , Json.pair "ipAddress" <$> ipAddress request-    , Json.pair "username" <$> username request-    ]
− src/lib/Patrol/Utility/Json.hs
@@ -1,14 +0,0 @@-module Patrol.Utility.Json-  ( pair-  , required-  ) where--import qualified Data.Aeson as Aeson-import qualified Data.Aeson.Types as Aeson-import qualified Data.String as String--pair :: (Aeson.ToJSON value, Aeson.KeyValue pair) => String -> value -> pair-pair key value = String.fromString key Aeson..= value--required :: Aeson.FromJSON value => Aeson.Object -> String -> Aeson.Parser value-required object key = object Aeson..: String.fromString key
− src/lib/Patrol/Utility/Maybe.hs
@@ -1,6 +0,0 @@-module Patrol.Utility.Maybe-  ( note-  ) where--note :: e -> Maybe a -> Either e a-note e = maybe (Left e) Right