浏览代码

Add high level Output and API error types

jherve 2 年之前
父节点
当前提交
ebfa46be64

+ 30 - 13
src/LinkedIn.purs

@@ -1,14 +1,17 @@
-module LinkedIn (encodeToJson, getContext, extractFromDocument, extractFromDocumentInContext) where
+module LinkedIn (APIError(..), encodeToJson, getContext, extractFromDocument, extractFromDocumentInContext) where
 
 
 import Prelude
 import Prelude
 
 
-import Control.Monad.Except (ExceptT, lift, runExceptT, throwError)
+import Control.Monad.Except (ExceptT, lift, runExceptT, throwError, withExceptT)
 import Data.Argonaut.Core (Json)
 import Data.Argonaut.Core (Json)
-import Data.Argonaut.Encode (encodeJson)
+import Data.Argonaut.Encode (class EncodeJson, encodeJson)
+import Data.Argonaut.Encode.Generic (genericEncodeJson)
 import Data.Either (Either(..))
 import Data.Either (Either(..))
+import Data.Generic.Rep (class Generic)
 import Data.Maybe (Maybe(..))
 import Data.Maybe (Maybe(..))
+import Data.Show.Generic (genericShow)
 import Effect (Effect)
 import Effect (Effect)
-import LinkedIn.Output (toOutput)
+import LinkedIn.Output (OutputError, toOutput)
 import LinkedIn.Output.Types (Output)
 import LinkedIn.Output.Types (Output)
 import LinkedIn.PageUrl (PageUrl, pageUrlP)
 import LinkedIn.PageUrl (PageUrl, pageUrlP)
 import Parsing (runParser)
 import Parsing (runParser)
@@ -16,28 +19,42 @@ import Web.DOM (Document)
 import Web.DOM.Document (url)
 import Web.DOM.Document (url)
 import Web.URL as U
 import Web.URL as U
 
 
-getContext ∷ Document → Effect (Either String PageUrl)
+data APIError =
+  ErrorExtraction OutputError
+  | ErrorInvalidUrl
+  | ErrorUnexpectedUrl
+
+derive instance Generic APIError _
+instance Show APIError where
+  show = genericShow
+instance EncodeJson APIError where
+  encodeJson a = genericEncodeJson a
+
+getContext ∷ Document → Effect (Either APIError PageUrl)
 getContext = runExceptT <<< getContext'
 getContext = runExceptT <<< getContext'
 
 
-getContext' ∷ Document → ExceptT String Effect PageUrl
+getContext' ∷ Document → ExceptT APIError Effect PageUrl
 getContext' dom = do
 getContext' dom = do
   u <- lift $ url dom
   u <- lift $ url dom
   case U.fromAbsolute u of
   case U.fromAbsolute u of
-    Nothing -> throwError "No URL found"
+    Nothing -> throwError ErrorInvalidUrl
     Just u' ->  case runParser (U.pathname u') pageUrlP of
     Just u' ->  case runParser (U.pathname u') pageUrlP of
-      Left _ -> throwError "Unexpected URL"
+      Left _ -> throwError ErrorUnexpectedUrl
       Right page -> pure page
       Right page -> pure page
 
 
-extractFromDocument :: Document -> Effect (Either String Output)
+extractFromDocument :: Document -> Effect (Either APIError Output)
 extractFromDocument = runExceptT <<< extractFromDocument'
 extractFromDocument = runExceptT <<< extractFromDocument'
 
 
-extractFromDocument' ∷ Document → ExceptT String Effect Output
+extractFromDocument' ∷ Document → ExceptT APIError Effect Output
 extractFromDocument' dom = do
 extractFromDocument' dom = do
   ctx <- getContext' dom
   ctx <- getContext' dom
-  toOutput ctx dom
+  toOutput' ctx dom
+
+extractFromDocumentInContext :: PageUrl -> Document -> Effect (Either APIError Output)
+extractFromDocumentInContext url dom = runExceptT $ toOutput' url dom
 
 
-extractFromDocumentInContext :: PageUrl -> Document -> Effect (Either String Output)
-extractFromDocumentInContext url dom = runExceptT $ toOutput url dom
+toOutput' ∷ PageUrl → Document → ExceptT APIError Effect Output
+toOutput' ctx dom = withExceptT (\err -> ErrorExtraction err) $ toOutput ctx dom
 
 
 encodeToJson :: Either String Output -> Json
 encodeToJson :: Either String Output -> Json
 encodeToJson = encodeJson
 encodeToJson = encodeJson

+ 10 - 10
src/LinkedIn/Output.purs

@@ -1,15 +1,14 @@
-module LinkedIn.Output (run, runToDetached, toOutput) where
+module LinkedIn.Output (module LinkedIn.Output.Types, run, runToDetached, toOutput) where
 
 
 import Prelude
 import Prelude
 
 
-import Control.Monad.Except (ExceptT, except, lift, withExceptT)
-import Data.Either (Either(..))
+import Control.Monad.Except (ExceptT, except, lift, throwError, withExceptT)
 import Data.Traversable (class Traversable, traverse)
 import Data.Traversable (class Traversable, traverse)
 import Effect (Effect)
 import Effect (Effect)
 import LinkedIn.DetachedNode (DetachedNode, toDetached)
 import LinkedIn.DetachedNode (DetachedNode, toDetached)
 import LinkedIn.Extractible (class Extractible)
 import LinkedIn.Extractible (class Extractible)
 import LinkedIn.Extractible as LE
 import LinkedIn.Extractible as LE
-import LinkedIn.Output.Types (Output)
+import LinkedIn.Output.Types (Output, OutputError(..))
 import LinkedIn.Page.JobOffer (JobOfferPage)
 import LinkedIn.Page.JobOffer (JobOfferPage)
 import LinkedIn.Page.Projects (ProjectsPage)
 import LinkedIn.Page.Projects (ProjectsPage)
 import LinkedIn.Page.Skills (SkillsPage)
 import LinkedIn.Page.Skills (SkillsPage)
@@ -17,6 +16,7 @@ import LinkedIn.Page.WorkExperiences (WorkExperiencesPage)
 import LinkedIn.PageUrl (PageUrl(..))
 import LinkedIn.PageUrl (PageUrl(..))
 import LinkedIn.QueryRunner (QueryError)
 import LinkedIn.QueryRunner (QueryError)
 import LinkedIn.UI.Elements.Parser (toUIElement)
 import LinkedIn.UI.Elements.Parser (toUIElement)
+import Parsing (parseErrorMessage)
 import Type.Proxy (Proxy(..))
 import Type.Proxy (Proxy(..))
 import Web.DOM (Document)
 import Web.DOM (Document)
 
 
@@ -25,11 +25,11 @@ run :: forall t.
   => Extractible t
   => Extractible t
   => Proxy t
   => Proxy t
   -> Document
   -> Document
-  -> ExceptT String Effect Output
+  -> ExceptT OutputError Effect Output
 run prox dom = do
 run prox dom = do
-  detached <- withExceptT (\_ -> "Error on detach") $ runToDetached prox dom
-  asUI <- withExceptT (\_ -> "error on conversion to UI element") $ except $ traverse toUIElement detached
-  except $ LE.extract asUI
+  detached <- withExceptT (\err -> ErrorOnDetach err) $ runToDetached prox dom
+  asUI <- withExceptT (\err -> ErrorOnUIConversion $ parseErrorMessage err) $ except $ traverse toUIElement detached
+  withExceptT (\err -> ErrorOnExtract err) $ except $ LE.extract asUI
 
 
 runToDetached :: forall t.
 runToDetached :: forall t.
   Traversable t
   Traversable t
@@ -41,10 +41,10 @@ runToDetached _ dom = do
   qRes <- LE.query @t dom
   qRes <- LE.query @t dom
   lift $ traverse toDetached qRes
   lift $ traverse toDetached qRes
 
 
-toOutput ∷ PageUrl → (Document → ExceptT String Effect Output)
+toOutput ∷ PageUrl → (Document → ExceptT OutputError Effect Output)
 toOutput = case _ of
 toOutput = case _ of
   UrlProjects _ -> run (Proxy :: Proxy ProjectsPage)
   UrlProjects _ -> run (Proxy :: Proxy ProjectsPage)
   UrlSkills _ -> run (Proxy :: Proxy SkillsPage)
   UrlSkills _ -> run (Proxy :: Proxy SkillsPage)
   UrlWorkExperience _ -> run (Proxy :: Proxy WorkExperiencesPage)
   UrlWorkExperience _ -> run (Proxy :: Proxy WorkExperiencesPage)
   UrlJobOffer _ -> run (Proxy :: Proxy JobOfferPage)
   UrlJobOffer _ -> run (Proxy :: Proxy JobOfferPage)
-  _ -> const $ except $ Left "Not handled yet"
+  u -> const $ throwError $ ErrorUrlNotSupported u

+ 15 - 0
src/LinkedIn/Output/Types.purs

@@ -8,9 +8,11 @@ import Data.Generic.Rep (class Generic)
 import Data.List.Types (NonEmptyList)
 import Data.List.Types (NonEmptyList)
 import Data.Show.Generic (genericShow)
 import Data.Show.Generic (genericShow)
 import LinkedIn.Jobs.JobOffer as JO
 import LinkedIn.Jobs.JobOffer as JO
+import LinkedIn.PageUrl (PageUrl)
 import LinkedIn.Profile.Project (Project)
 import LinkedIn.Profile.Project (Project)
 import LinkedIn.Profile.Skill (Skill)
 import LinkedIn.Profile.Skill (Skill)
 import LinkedIn.Profile.WorkExperience (WorkExperience)
 import LinkedIn.Profile.WorkExperience (WorkExperience)
+import LinkedIn.QueryRunner (QueryError)
 
 
 data Output =
 data Output =
   OutProjects (NonEmptyList Project)
   OutProjects (NonEmptyList Project)
@@ -23,3 +25,16 @@ instance Show Output where
   show = genericShow
   show = genericShow
 instance EncodeJson Output where
 instance EncodeJson Output where
   encodeJson a = genericEncodeJson a
   encodeJson a = genericEncodeJson a
+
+
+data OutputError =
+  ErrorOnDetach QueryError
+  | ErrorOnUIConversion String
+  | ErrorOnExtract String
+  | ErrorUrlNotSupported PageUrl
+
+derive instance Generic OutputError _
+instance Show OutputError where
+  show = genericShow
+instance EncodeJson OutputError where
+  encodeJson a = genericEncodeJson a

+ 4 - 0
src/LinkedIn/PageUrl.purs

@@ -3,6 +3,8 @@ module LinkedIn.PageUrl (PageUrl(..), pageUrlP) where
 import Prelude
 import Prelude
 
 
 import Control.Alt ((<|>))
 import Control.Alt ((<|>))
+import Data.Argonaut.Encode (class EncodeJson)
+import Data.Argonaut.Encode.Generic (genericEncodeJson)
 import Data.Generic.Rep (class Generic)
 import Data.Generic.Rep (class Generic)
 import Data.Int64 as I64
 import Data.Int64 as I64
 import Data.Maybe (Maybe(..))
 import Data.Maybe (Maybe(..))
@@ -27,6 +29,8 @@ derive instance Eq PageUrl
 derive instance Generic PageUrl _
 derive instance Generic PageUrl _
 instance Show PageUrl where
 instance Show PageUrl where
   show = genericShow
   show = genericShow
+instance EncodeJson PageUrl where
+  encodeJson a = genericEncodeJson a
 
 
 pathComponentP :: String -> Parser String Unit
 pathComponentP :: String -> Parser String Unit
 pathComponentP s = do
 pathComponentP s = do

+ 4 - 0
src/LinkedIn/QueryRunner.purs

@@ -4,6 +4,8 @@ import Prelude
 
 
 import Control.Alt ((<|>))
 import Control.Alt ((<|>))
 import Control.Monad.Except (ExceptT(..), mapExceptT, runExceptT, throwError)
 import Control.Monad.Except (ExceptT(..), mapExceptT, runExceptT, throwError)
+import Data.Argonaut.Encode (class EncodeJson)
+import Data.Argonaut.Encode.Generic (genericEncodeJson)
 import Data.Array as A
 import Data.Array as A
 import Data.Either (Either(..), note)
 import Data.Either (Either(..), note)
 import Data.Generic.Rep (class Generic)
 import Data.Generic.Rep (class Generic)
@@ -22,6 +24,8 @@ data QueryError =
   | QNodeUnexpectedType String String
   | QNodeUnexpectedType String String
   | QTextNotFoundError
   | QTextNotFoundError
   | QChooseError
   | QChooseError
+instance EncodeJson QueryError where
+  encodeJson a = genericEncodeJson a
 
 
 derive instance Generic QueryError _
 derive instance Generic QueryError _
 derive instance Eq QueryError
 derive instance Eq QueryError

+ 4 - 1
src/LinkedIn/UI/Basic/Types.purs

@@ -3,11 +3,12 @@ module LinkedIn.UI.Basic.Types where
 import Prelude
 import Prelude
 
 
 import Data.Argonaut.Encode (class EncodeJson)
 import Data.Argonaut.Encode (class EncodeJson)
-import Data.Argonaut.Encode.Encoders (encodeString)
+import Data.Argonaut.Encode.Encoders (encodeNumber, encodeString)
 import Data.Argonaut.Encode.Generic (genericEncodeJson)
 import Data.Argonaut.Encode.Generic (genericEncodeJson)
 import Data.Date (Month, Year)
 import Data.Date (Month, Year)
 import Data.Generic.Rep (class Generic)
 import Data.Generic.Rep (class Generic)
 import Data.Int64 (Int64)
 import Data.Int64 (Int64)
+import Data.Int64 as I64
 import Data.Show.Generic (genericShow)
 import Data.Show.Generic (genericShow)
 
 
 newtype JobOfferId = JobOfferId Int64
 newtype JobOfferId = JobOfferId Int64
@@ -16,6 +17,8 @@ derive instance Eq JobOfferId
 derive instance Generic JobOfferId _
 derive instance Generic JobOfferId _
 instance Show JobOfferId where
 instance Show JobOfferId where
   show = genericShow
   show = genericShow
+instance EncodeJson JobOfferId where
+  encodeJson (JobOfferId a) = encodeNumber $ I64.toNumber a
 
 
 data MonthYear = MonthYear Month Year
 data MonthYear = MonthYear Month Year