content: {-# LANGUAGE DerivingStrategies #-}
  {-# LANGUAGE RecordWildCards #-}
  
  module Srclib.Types (
    SourceUnit (..),
    SourceUnitBuild (..),
    SourceUnitDependency (..),
    SourceUnitNoticeFile (..),
    AdditionalDepData (..),
    SourceUserDefDep (..),
    SourceRemoteDep (..),
    Locator (..),
    LicenseSourceUnit (..),
    LicenseScanType (..),
    LicenseUnit (..),
    LicenseUnitData (..),
    LicenseUnitInfo (..),
    LicenseUnitMatchData (..),
    FullSourceUnit (..),
    ProvidedPackageLabels (..),
    buildProvidedPackageLabels,
    ProvidedPackageLabel (..),
    ProvidedPackageLabelScope (..),
    renderProvidedPackageLabelScope,
    parseProvidedPackageLabelScope,
    OriginPath,
    renderLocator,
    parseLocator,
    pathToOriginPath,
    projectId,
    someBaseToOriginPath,
    somePathToOriginPath,
    emptyLicenseUnit,
    emptyLicenseUnitData,
    sourceUnitToFullSourceUnit,
    licenseUnitToFullSourceUnit,
    textToOriginPath,
  ) where
  
  import Data.Aeson
  import Data.List.NonEmpty (NonEmpty ((:|)))
  import Data.List.NonEmpty qualified as NE
  import Data.Map (Map)
  import Data.Map qualified as Map
  import Data.Maybe (fromMaybe)
  import Data.String (IsString)
  import Data.String.Conversion (ToString (toString), ToText, toText)
  import Data.Text (Text)
  import Data.Text qualified as Text
  import Path (File, Path, SomeBase (..), toFilePath)
  import Path.Extra (SomePath (..))
  import Types (GraphBreadth (..))
  
  data LicenseScanType = CliLicenseScanned
    deriving (Eq, Ord, Show)
  
  instance ToText LicenseScanType where
    toText CliLicenseScanned = "cli-license-scanned"
  
  instance ToJSON LicenseScanType where
    toJSON = toJSON . toText
  
  -- | This type is meant to represent the paths in a project where a particular set of dependencies were discovered.
  --  For example, in a project directory that has a @go.mod@ the OriginPath might be 'foo/bar/go.mod'.
  --  In a project with VSI dependencies the OriginPath would be the directory the dep was found in, such as 'vendored/zlib/'
  --
  --  OriginPaths were previously `SomeBase File`, however with support for VSI OriginPaths can now be a directory in addition to a file path.
  --  The reason we cannot use `SomePath` for this is that outputting an `OriginPath` to JSON in a form like @/foo/bar/path_end@ doesn't say whether the path is a file or directory which is required for parsing to a `SomePath` in `FromJSON`.
  --  This type and its exported smart constructors describe that OriginPath is a path, but has no information about whether the path is a file or directory.
  newtype OriginPath = OriginPath FilePath
    deriving newtype (Eq, Ord, Show, ToJSON, FromJSON, ToText, IsString)
  
  pathToOriginPath :: Path b t -> OriginPath
  pathToOriginPath = OriginPath . toFilePath
  
  someBaseToOriginPath :: SomeBase b -> OriginPath
  someBaseToOriginPath (Abs p) = pathToOriginPath p
  someBaseToOriginPath (Rel p) = pathToOriginPath p
  
  somePathToOriginPath :: SomePath -> OriginPath
  somePathToOriginPath (SomeFile f) = someBaseToOriginPath f
  somePathToOriginPath (SomeDir d) = someBaseToOriginPath d
  
  textToOriginPath :: Text -> OriginPath
  textToOriginPath = OriginPath . toString
  
  -- export interface SourceUnit {
  --   Name?: string;
  --   Type?: string;
  --   Repo?: string;
  --   Dir?: string;
  --   Ops?: {
  --     scan?: any;
  --     depresolve?: any;
  --     graph?: any;
  --   };
  --   ThemisVersion?: string;
  --   SourceUnits?: SourceUnit[];
  --   LicenseUnits?: SourceUnit[];
  --   Info?: any;
  --   Data?: SourceUnitNomosData[] | any;
  --   Licenses?: { [licenseId: string]: SourceUnitLicenseMatch[] }
  --   DeclaredLicenses?: SourceUnitLicense[];
  --   HiddenLicenses?: HiddenLicense[];
  --   NoticeFiles?: { [noticeFilePath: string]: string };
  --   Dependencies?: SourceUnitDependency[];
  --   DependencyLocks?: DependencyLock[];
  --   Build?: SourceUnitBuild;
  --   OriginPaths?: string[];
  --   AdditionalDependencyData?: {
  --     UserDefinedDependencies?: CLIUserDefinedDependency[];
  --     RemoteDependencies?: RemoteDependency[];
  --   };
  
  --   /**
  --    * Represents Manifest attribute
  --    *
  --    * For older CLI version submitted source unit,
  --    * It provides equivalent value to @OriginPaths
  --    */
  --   Manifest?: string;
  -- }
  
  data SourceUnitNoticeFile = SourceUnitNoticeFile
    { sourceUnitNoticeFilePath :: Text
    , sourceUnitNoticeFileContents :: Text
    , sourceUnitNoticeFileCopyrights :: [Text]
    }
    deriving (Eq, Ord, Show)
  
  instance ToJSON SourceUnitNoticeFile where
    toJSON SourceUnitNoticeFile{..} =
      object
        [ "path" .= sourceUnitNoticeFilePath
        , "contents" .= sourceUnitNoticeFileContents
        , "copyrights" .= sourceUnitNoticeFileCopyrights
        ]
  
  instance FromJSON SourceUnitNoticeFile where
    parseJSON = withObject "SourceUnitNoticeFile" $ \obj ->
      SourceUnitNoticeFile
        <$> obj .: "path"
        <*> obj .: "contents"
        <*> obj .:? "copyrights" .!= []
  
  data FullSourceUnit = FullSourceUnit
    { fullSourceUnitName :: Text
    , fullSourceUnitType :: Text
    , fullSourceUnitTitle :: Maybe Text
    , fullSourceUnitManifest :: Maybe Text
    , fullSourceUnitBuild :: Maybe SourceUnitBuild
    , fullSourceUnitGraphBreadth :: GraphBreadth
    , fullSourceUnitNoticeFiles :: [SourceUnitNoticeFile]
    , fullSourceUnitOriginPaths :: [OriginPath]
    , fullSourceUnitAdditionalData :: Maybe AdditionalDepData
    , fullSourceUnitFiles :: Maybe (NonEmpty Text)
    , fullSourceUnitData :: Maybe (NonEmpty LicenseUnitData)
    , fullSourceUnitInfo :: Maybe LicenseUnitInfo
    }
    deriving (Eq, Ord, Show)
  
  licenseUnitToFullSourceUnit :: LicenseUnit -> FullSourceUnit
  licenseUnitToFullSourceUnit LicenseUnit{..} =
    FullSourceUnit
      { fullSourceUnitName = licenseUnitName
      , fullSourceUnitType = licenseUnitType
      , fullSourceUnitTitle = licenseUnitTitle
      , fullSourceUnitManifest = Nothing
      , fullSourceUnitBuild = Nothing
      , fullSourceUnitGraphBreadth = Complete
      , fullSourceUnitNoticeFiles = licenseUnitNoticeFiles
      , fullSourceUnitOriginPaths = []
      , fullSourceUnitAdditionalData = Nothing
      , fullSourceUnitFiles = Just licenseUnitFiles
      , fullSourceUnitData = Just licenseUnitData
      , fullSourceUnitInfo = Just licenseUnitInfo
      }
  
  sourceUnitToFullSourceUnit :: SourceUnit -> FullSourceUnit
  sourceUnitToFullSourceUnit SourceUnit{..} =
    FullSourceUnit
      { fullSourceUnitName = sourceUnitName
      , fullSourceUnitType = sourceUnitType
      , fullSourceUnitTitle = Nothing
      , fullSourceUnitManifest = Just sourceUnitManifest
      , fullSourceUnitBuild = sourceUnitBuild
      , fullSourceUnitGraphBreadth = sourceUnitGraphBreadth
      , fullSourceUnitOriginPaths = sourceUnitOriginPaths
      , fullSourceUnitNoticeFiles = sourceUnitNoticeFiles
      , fullSourceUnitAdditionalData = additionalData
      , fullSourceUnitFiles = Nothing
      , fullSourceUnitData = Nothing
      , fullSourceUnitInfo = Nothing
      }
  
  instance ToJSON FullSourceUnit where
    toJSON FullSourceUnit{..} =
      object
        [ "Name" .= fullSourceUnitName
        , "Type" .= fullSourceUnitType
        , "Title" .= fullSourceUnitTitle
        , "Manifest" .= fullSourceUnitManifest
        , "Build" .= fullSourceUnitBuild
        , "GraphBreadth" .= fullSourceUnitGraphBreadth
        , "OriginPaths" .= fullSourceUnitOriginPaths
        , "NoticeFiles" .= fullSourceUnitNoticeFiles
        , "AdditionalDependencyData" .= fullSourceUnitAdditionalData
        , "Files" .= fullSourceUnitFiles
        , "Data" .= fullSourceUnitData
        , "Info" .= fullSourceUnitInfo
        ]
  
  -- | LicenseSourceUnit is the base of the results sent to Core for a CLI-side license scan
  -- licenseSourceUnitLicenseUnits will be empty if you scan an empty directory.
  data LicenseSourceUnit = LicenseSourceUnit
    { licenseSourceUnitName :: Text
    , licenseSourceUnitType :: LicenseScanType
    , licenseSourceUnitLicenseUnits :: NonEmpty LicenseUnit
    }
    deriving (Eq, Ord, Show)
  
  instance Semigroup LicenseSourceUnit where
    LicenseSourceUnit name unitType licenseUnits1 <> LicenseSourceUnit _ _ licenseUnits2 = LicenseSourceUnit name unitType $ licenseUnits1 <> licenseUnits2
  
  instance ToJSON LicenseSourceUnit where
    toJSON LicenseSourceUnit{..} =
      object
        [ "Name" .= licenseSourceUnitName
        , "Type" .= licenseSourceUnitType
        , "LicenseUnits" .= licenseSourceUnitLicenseUnits
        ]
  
  -- There will be one of these for each license type found by Themis
  -- The data returned from themis-cli is an array of these.
  data LicenseUnit = LicenseUnit
    { licenseUnitName :: Text
    , licenseUnitType :: Text
    , licenseUnitTitle :: Maybe Text
    , licenseUnitDir :: Text
    , licenseUnitFiles :: (NonEmpty Text)
    , licenseUnitData :: (NonEmpty LicenseUnitData)
    , licenseUnitNoticeFiles :: [SourceUnitNoticeFile]
    , licenseUnitInfo :: LicenseUnitInfo
    }
    deriving (Eq, Ord, Show)
  
  emptyLicenseUnit :: LicenseUnit
  emptyLicenseUnit =
    LicenseUnit
      { licenseUnitName = "empty"
      , licenseUnitType = "LicenseUnit"
      , licenseUnitTitle = Nothing
      , licenseUnitDir = ""
      , licenseUnitFiles = "" :| []
      , licenseUnitData = emptyLicenseUnitData :| []
      , licenseUnitNoticeFiles = []
      , licenseUnitInfo = LicenseUnitInfo{licenseUnitInfoDescription = Nothing}
      }
  
  instance Semigroup LicenseUnit where
    licenseUnit1 <> licenseUnit2 =
      licenseUnit1
        { licenseUnitData = licenseUnitData licenseUnit1 <> licenseUnitData licenseUnit2
        , licenseUnitFiles = NE.nub $ licenseUnitFiles licenseUnit1 <> licenseUnitFiles licenseUnit2
        }
  
  instance ToJSON LicenseUnit where
    toJSON LicenseUnit{..} =
      object
        [ "Name" .= licenseUnitName
        , "Type" .= licenseUnitType
        , "Dir" .= licenseUnitDir
        , "Files" .= licenseUnitFiles
        , "Data" .= licenseUnitData
        , "NoticeFiles" .= licenseUnitNoticeFiles
        , "Info" .= licenseUnitInfo
        ]
  
  instance FromJSON LicenseUnit where
    parseJSON = withObject "LicenseUnit" $ \obj ->
      LicenseUnit
        <$> obj .: "Name"
        <*> obj .: "Type"
        <*> obj .:? "Title"
        <*> obj .: "Dir"
        <*> obj .: "Files"
        <*> obj .: "Data"
        <*> obj .:? "NoticeFiles" .!= []
        <*> obj .: "Info"
  
  newtype LicenseUnitInfo = LicenseUnitInfo
    {licenseUnitInfoDescription :: Maybe Text}
    deriving (Eq, Ord, Show)
  
  instance ToJSON LicenseUnitInfo where
    toJSON LicenseUnitInfo{..} =
      object ["Description" .= licenseUnitInfoDescription]
  
  instance FromJSON LicenseUnitInfo where
    parseJSON = withObject "LicenseUnitInfo" $ \obj ->
      LicenseUnitInfo <$> obj .: "Description"
  
  -- | LicenseUnitData contains data about a license unit. At least one of licenseUnitDataMatchData or licenseUnitDataCopyrights will be non-empty
  data LicenseUnitData = LicenseUnitData
    { licenseUnitDataPath :: Text
    , licenseUnitDataCopyright :: Maybe Text
    , licenseUnitDataThemisVersion :: Text
    , licenseUnitDataMatchData :: Maybe (NonEmpty LicenseUnitMatchData)
    , licenseUnitDataCopyrights :: Maybe (NonEmpty Text)
    , licenseUnitDataContents :: Maybe Text
    }
    deriving (Eq, Ord, Show)
  
  emptyLicenseUnitData :: LicenseUnitData
  emptyLicenseUnitData =
    LicenseUnitData
      { licenseUnitDataPath = ""
      , licenseUnitDataCopyright = Nothing
      , licenseUnitDataThemisVersion = ""
      , licenseUnitDataMatchData = Nothing
      , licenseUnitDataCopyrights = Nothing
      , licenseUnitDataContents = Nothing
      }
  
  instance Semigroup LicenseUnitData where
    LicenseUnitData path copyright themisVersion matchData1 copyrights1 contents
      <> LicenseUnitData _ _ _ matchData2 copyrights2 _ =
        LicenseUnitData path copyright themisVersion (matchData1 <> matchData2) (copyrights1 <> copyrights2) contents
  
  -- instance Semigroup LicenseSourceUnit where
  --   LicenseSourceUnit name unitType licenseUnits1 <> LicenseSourceUnit _ _ licenseUnits2 = LicenseSourceUnit name unitType $ licenseUnits1 <> licenseUnits2
  instance ToJSON LicenseUnitData where
    toJSON LicenseUnitData{..} =
      object
        [ "path" .= licenseUnitDataPath
        , "Copyright" .= licenseUnitDataCopyright
        , "ThemisVersion" .= licenseUnitDataThemisVersion
        , "match_data" .= licenseUnitDataMatchData
        , "Copyrights" .= licenseUnitDataCopyrights
        , "Contents" .= licenseUnitDataContents
        ]
  
  instance FromJSON LicenseUnitData where
    parseJSON = withObject "LicenseUnitData" $ \obj ->
      LicenseUnitData
        <$> obj .: "path"
        <*> obj .:? "Copyright"
        <*> obj .: "ThemisVersion"
        <*> obj .:? "match_data"
        <*> obj .:? "Copyrights"
        <*> obj .:? "Contents"
  
  data LicenseUnitMatchData = LicenseUnitMatchData
    { licenseUnitMatchDataMatchString :: Maybe Text
    , licenseUnitMatchDataLocation :: Integer
    , licenseUnitMatchDataLength :: Integer
    , licenseUnitMatchDataIndex :: Integer
    , licenseUnitDataStartLine :: Integer
    , licenseUnitDataEndLine :: Integer
    }
    deriving (Eq, Ord, Show)
  
  instance ToJSON LicenseUnitMatchData where
    toJSON :: LicenseUnitMatchData -> Value
    toJSON LicenseUnitMatchData{..} =
      object
        [ "match_string" .= licenseUnitMatchDataMatchString
        , "location" .= licenseUnitMatchDataLocation
        , "length" .= licenseUnitMatchDataLength
        , "index" .= licenseUnitMatchDataIndex
        , "start_line" .= licenseUnitDataStartLine
        , "end_line" .= licenseUnitDataEndLine
        ]
  
  instance FromJSON LicenseUnitMatchData where
    parseJSON = withObject "LicenseUnitMatchData" $ \obj ->
      LicenseUnitMatchData
        <$> obj .:? "match_string"
        <*> obj .: "location"
        <*> obj .: "length"
        <*> obj .: "index"
        <*> obj .: "start_line"
        <*> obj .: "end_line"
  
  data SourceUnit = SourceUnit
    { sourceUnitName :: Text
    , sourceUnitType :: Text
    , sourceUnitManifest :: Text
    -- ^ path to manifest file
    , sourceUnitBuild :: Maybe SourceUnitBuild
    , sourceUnitGraphBreadth :: GraphBreadth
    , sourceUnitNoticeFiles :: [SourceUnitNoticeFile]
    , sourceUnitOriginPaths :: [OriginPath]
    , sourceUnitLabels :: Maybe ProvidedPackageLabels
    , additionalData :: Maybe AdditionalDepData
    }
    deriving (Eq, Ord, Show)
  
  data SourceUnitBuild = SourceUnitBuild
    { buildArtifact :: Text
    -- ^ always "default"
    , buildSucceeded :: Bool
    -- ^ always true
    , buildImports :: [Locator]
    , buildDependencies :: [SourceUnitDependency]
    }
    deriving (Eq, Ord, Show)
  
  data SourceUnitDependency = SourceUnitDependency
    { sourceDepLocator :: Locator
    , sourceDepImports :: [Locator] -- omitempty
    -- , sourceDepData :: Aeson.Value
    }
    deriving (Eq, Ord, Show)
  
  data AdditionalDepData = AdditionalDepData
    { userDefinedDeps :: Maybe [SourceUserDefDep]
    , remoteDeps :: Maybe [SourceRemoteDep]
    }
    deriving (Eq, Ord, Show)
  
  data SourceUserDefDep = SourceUserDefDep
    { srcUserDepName :: Text
    , srcUserDepVersion :: Text
    , srcUserDepLicense :: Text
    , srcUserDepDescription :: Maybe Text
    , srcUserDepHomepage :: Maybe Text
    , srcUserDepOrigin :: Maybe (SomeBase File)
    }
    deriving (Eq, Ord, Show)
  
  data SourceRemoteDep = SourceRemoteDep
    { srcRemoteDepName :: Text
    , srcRemoteDepVersion :: Text
    , srcRemoteDepUrl :: Text
    , srcRemoteDepDescription :: Maybe Text
    , srcRemoteDepHomepage :: Maybe Text
    }
    deriving (Eq, Ord, Show)
  
  -- | Labels are side channel information about dependencies,
  -- used to communicate information about the dependency in some way.
  newtype ProvidedPackageLabels = ProvidedPackageLabels
    { unProvidedPackageLabels :: Map Text [ProvidedPackageLabel]
    }
    deriving (Eq, Ord, Show)
  
  buildProvidedPackageLabels :: Map Text [ProvidedPackageLabel] -> Maybe ProvidedPackageLabels
  buildProvidedPackageLabels labels = if Map.null labels then Nothing else Just $ ProvidedPackageLabels labels
  
  -- | A label provided by the user for a given dependency.
  -- Note that FOSSA Core supports both "user" and "derived" labels;
  -- FOSSA CLI doesn't ever create "derived" labels
  -- (these will be created and inserted in the future by Sparkle).
  data ProvidedPackageLabel = ProvidedPackageLabel
    { providedPackageLabelContent :: Text
    , providedPackageLabelScope :: ProvidedPackageLabelScope
    }
    deriving (Eq, Ord, Show)
  
  -- | The scope of the provided package label.
  -- Refer to @PackageLabelScope@ in FOSSA Core for details on what these mean.
  data ProvidedPackageLabelScope
    = ProvidedPackageLabelScopeOrg
    | ProvidedPackageLabelScopeProject
    | ProvidedPackageLabelScopeRevision
    deriving (Eq, Ord, Show)
  
  renderProvidedPackageLabelScope :: ProvidedPackageLabelScope -> Text
  renderProvidedPackageLabelScope ProvidedPackageLabelScopeOrg = "org"
  renderProvidedPackageLabelScope ProvidedPackageLabelScopeProject = "project"
  renderProvidedPackageLabelScope ProvidedPackageLabelScopeRevision = "revision"
  
  parseProvidedPackageLabelScope :: Text -> ProvidedPackageLabelScope
  parseProvidedPackageLabelScope "org" = ProvidedPackageLabelScopeOrg
  parseProvidedPackageLabelScope "project" = ProvidedPackageLabelScopeProject
  parseProvidedPackageLabelScope _ = ProvidedPackageLabelScopeRevision
  
  data Locator = Locator
    { locatorFetcher :: Text
    , locatorProject :: Text
    , locatorRevision :: Maybe Text
    }
    deriving (Eq, Ord, Show)
  
  instance ToText Locator where
    toText = renderLocator
  
  renderLocator :: Locator -> Text
  renderLocator Locator{..} =
    locatorFetcher <> "+" <> locatorProject <> "$" <> fromMaybe "" locatorRevision
  
  -- The projectId is the full locator of the project. E.g. custom+123/someProject (<fetcher>+<orgId>/<project-name>)
  projectId :: Locator -> Text
  projectId Locator{..} =
    locatorFetcher <> "+" <> locatorProject
  
  parseLocator :: Text -> Locator
  parseLocator raw = Locator fetcher project (if Text.null revision then Nothing else Just revision)
    where
      (fetcher, xs) = Text.breakOn "+" raw
      (project, xs') = Text.breakOn "$" (Text.drop 1 xs)
      revision = Text.drop 1 xs'
  
  instance ToJSON SourceUnit where
    toJSON SourceUnit{..} =
      object
        [ "Name" .= sourceUnitName
        , "Type" .= sourceUnitType
        , "Manifest" .= sourceUnitManifest
        , "Build" .= sourceUnitBuild
        , "GraphBreadth" .= sourceUnitGraphBreadth
        , "NoticeFiles" .= sourceUnitNoticeFiles
        , "OriginPaths" .= sourceUnitOriginPaths
        , "AdditionalDependencyData" .= additionalData
        , "Labels" .= sourceUnitLabels
        ]
  
  instance FromJSON SourceUnit where
    parseJSON = withObject "SourceUnit" $ \obj ->
      SourceUnit
        <$> obj .: "Name"
        <*> obj .: "Type"
        <*> obj .: "Manifest"
        <*> obj .:? "Build"
        <*> obj .: "GraphBreadth"
        <*> obj .:? "NoticeFiles" .!= []
        <*> obj .: "OriginPaths"
        <*> obj .:? "AdditionalDependencyData"
        <*> obj .:? "Labels"
  
  instance ToJSON SourceUnitBuild where
    toJSON SourceUnitBuild{..} =
      object
        [ "Artifact" .= buildArtifact
        , "Succeeded" .= buildSucceeded
        , "Imports" .= buildImports
        , "Dependencies" .= buildDependencies
        ]
  
  instance FromJSON SourceUnitBuild where
    parseJSON = withObject "SourceUnitBuild" $ \obj ->
      SourceUnitBuild
        <$> obj .: "Artifact"
        <*> obj .: "Succeeded"
        <*> obj .: "Imports"
        <*> obj .: "Dependencies"
  
  instance ToJSON SourceUnitDependency where
    toJSON SourceUnitDependency{..} =
      object
        [ "locator" .= sourceDepLocator
        , "imports" .= sourceDepImports
        ]
  
  instance FromJSON SourceUnitDependency where
    parseJSON = withObject "SourceUnitDependency" $ \obj ->
      SourceUnitDependency
        <$> obj .: "locator"
        <*> obj .: "imports"
  
  instance ToJSON AdditionalDepData where
    toJSON AdditionalDepData{..} =
      object
        [ "UserDefinedDependencies" .= userDefinedDeps
        , "RemoteDependencies" .= remoteDeps
        ]
  
  instance FromJSON AdditionalDepData where
    parseJSON = withObject "AdditionalDepData" $ \obj ->
      AdditionalDepData
        <$> obj .:? "UserDefinedDependencies"
        <*> obj .:? "RemoteDependencies"
  
  instance ToJSON SourceUserDefDep where
    toJSON SourceUserDefDep{..} =
      object
        [ "Name" .= srcUserDepName
        , "Version" .= srcUserDepVersion
        , "License" .= srcUserDepLicense
        , "Description" .= srcUserDepDescription
        , "Homepage" .= srcUserDepHomepage
        , "Origin" .= fmap toText srcUserDepOrigin
        ]
  
  instance FromJSON SourceUserDefDep where
    parseJSON = withObject "SourceUserDefDep" $ \obj ->
      SourceUserDefDep
        <$> obj .: "Name"
        <*> obj .: "Version"
        <*> obj .: "License"
        <*> obj .:? "Description"
        <*> obj .:? "Homepage"
        <*> obj .:? "Origin"
  
  instance ToJSON SourceRemoteDep where
    toJSON SourceRemoteDep{..} =
      object
        [ "Name" .= srcRemoteDepName
        , "Version" .= srcRemoteDepVersion
        , "Url" .= srcRemoteDepUrl
        , "Description" .= srcRemoteDepDescription
        , "Homepage" .= srcRemoteDepHomepage
        ]
  
  instance FromJSON SourceRemoteDep where
    parseJSON = withObject "SourceRemoteDep" $ \obj ->
      SourceRemoteDep
        <$> obj .: "Name"
        <*> obj .: "Version"
        <*> obj .: "Url"
        <*> obj .:? "Description"
        <*> obj .:? "Homepage"
  
  instance ToJSON ProvidedPackageLabels where
    toJSON ProvidedPackageLabels{..} =
      toJSON unProvidedPackageLabels
  
  instance FromJSON ProvidedPackageLabels where
    parseJSON = withObject "ProvidedPackageLabels" $ \obj ->
      ProvidedPackageLabels <$> parseJSON (Object obj)
  
  -- | Note: The ToJSON and FromJSON implementations for this type differ
  -- because ToJSON is used to upload to Core and FromJSON is used to parse
  -- from a fossa-deps file.
  instance ToJSON ProvidedPackageLabel where
    toJSON ProvidedPackageLabel{..} =
      object
        [ "kind" .= ("user" :: Text)
        , "content" .= providedPackageLabelContent
        , "scope" .= providedPackageLabelScope
        ]
  
  -- | Note: The ToJSON and FromJSON implementations for this type differ
  -- because ToJSON is used to upload to Core and FromJSON is used to parse
  -- from a fossa-deps file.
  instance FromJSON ProvidedPackageLabel where
    parseJSON = withObject "ProvidedPackageLabel" $ \obj ->
      ProvidedPackageLabel
        <$> obj .: "label"
        <*> obj .: "scope"
  
  instance ToJSON ProvidedPackageLabelScope where
    toJSON = toJSON . renderProvidedPackageLabelScope
  
  instance FromJSON ProvidedPackageLabelScope where
    parseJSON = withText "ProvidedPackageLabelScope" (pure . parseProvidedPackageLabelScope)
  
  instance ToJSON Locator where
    -- render as text
    toJSON :: Locator -> Value
    toJSON = toJSON . renderLocator
  
  instance FromJSON Locator where
    parseJSON = withText "Locator" (pure . parseLocator)
  