From e00e1f26ec884c3a84183370a164ce5f5cd0fc5b Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Sat, 15 Jun 2019 14:48:20 +0100 Subject: [PATCH 01/11] wip --- .circleci/config.yml | 2 +- .hlint.yaml | 2986 ----------------------- package.yaml | 4 +- src/Aws/Lambda/API.hs | 21 + src/Aws/Lambda/Runtime.hs | 207 +- src/Aws/Lambda/Runtime/API/Endpoints.hs | 60 + src/Aws/Lambda/Runtime/API/Version.hs | 7 + src/Aws/Lambda/Runtime/ApiInfo.hs | 56 + src/Aws/Lambda/Runtime/Context.hs | 19 + src/Aws/Lambda/Runtime/Environment.hs | 61 + src/Aws/Lambda/Runtime/Error.hs | 59 + stack.yaml | 2 +- 12 files changed, 334 insertions(+), 3150 deletions(-) delete mode 100644 .hlint.yaml create mode 100644 src/Aws/Lambda/API.hs create mode 100644 src/Aws/Lambda/Runtime/API/Endpoints.hs create mode 100644 src/Aws/Lambda/Runtime/API/Version.hs create mode 100644 src/Aws/Lambda/Runtime/ApiInfo.hs create mode 100644 src/Aws/Lambda/Runtime/Context.hs create mode 100644 src/Aws/Lambda/Runtime/Environment.hs create mode 100644 src/Aws/Lambda/Runtime/Error.hs diff --git a/.circleci/config.yml b/.circleci/config.yml index e82daad..1b38118 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,7 +1,7 @@ defaults: &defaults working_directory: ~/aws-lambda-haskell-runtime docker: - - image: fpco/stack-build:lts-13.0 + - image: fpco/stack-build:lts-13.25 version: 2 jobs: diff --git a/.hlint.yaml b/.hlint.yaml deleted file mode 100644 index 11bb23b..0000000 --- a/.hlint.yaml +++ /dev/null @@ -1,2986 +0,0 @@ -- arguments: - - -XConstraintKinds - - -XDeriveGeneric - - -XGeneralizedNewtypeDeriving - - -XLambdaCase - - -XOverloadedStrings - - -XRecordWildCards - - -XScopedTypeVariables - - -XStandaloneDeriving - - -XTupleSections - - -XTypeApplications - - -XViewPatterns -- ignore: - name: Use head -- ignore: - name: Use Foldable.forM_ -- hint: - lhs: pure () - note: Use 'pass' - rhs: pass -- hint: - lhs: return () - note: Use 'pass' - rhs: pass -- hint: - lhs: ! '(: [])' - note: Use `one` - rhs: one -- hint: - lhs: (:| []) - note: Use `one` - rhs: one -- hint: - lhs: Data.Sequence.singleton - note: Use `one` - rhs: one -- hint: - lhs: Data.Text.singleton - note: Use `one` - rhs: one -- hint: - lhs: Data.Text.Lazy.singleton - note: Use `one` - rhs: one -- hint: - lhs: Data.ByteString.singleton - note: Use `one` - rhs: one -- hint: - lhs: Data.ByteString.Lazy.singleton - note: Use `one` - rhs: one -- hint: - lhs: Data.Map.singleton - note: Use `one` - rhs: one -- hint: - lhs: Data.Map.Strict.singleton - note: Use `one` - rhs: one -- hint: - lhs: Data.HashMap.Strict.singleton - note: Use `one` - rhs: one -- hint: - lhs: Data.HashMap.Lazy.singleton - note: Use `one` - rhs: one -- hint: - lhs: Data.IntMap.singleton - note: Use `one` - rhs: one -- hint: - lhs: Data.IntMap.Strict.singleton - note: Use `one` - rhs: one -- hint: - lhs: Data.Set.singleton - note: Use `one` - rhs: one -- hint: - lhs: Data.HashSet.singleton - note: Use `one` - rhs: one -- hint: - lhs: Data.IntSet.singleton - note: Use `one` - rhs: one -- warn: - lhs: Control.Exception.evaluate - rhs: evaluateWHNF -- warn: - lhs: Control.Exception.evaluate (force x) - rhs: evaluateNF x -- warn: - lhs: Control.Exception.evaluate (x `deepseq` ()) - rhs: evaluateNF_ x -- warn: - lhs: void (evaluateWHNF x) - rhs: evaluateWHNF_ x -- warn: - lhs: void (evaluateNF x) - rhs: evaluateNF_ x -- hint: - lhs: Control.Exception.throw - note: Use 'impureThrow' - rhs: impureThrow -- warn: - lhs: Data.Text.IO.readFile - rhs: readFileText -- warn: - lhs: Data.Text.IO.writeFile - rhs: writeFileText -- warn: - lhs: Data.Text.IO.appendFile - rhs: appendFileText -- warn: - lhs: Data.Text.Lazy.IO.readFile - rhs: readFileLText -- warn: - lhs: Data.Text.Lazy.IO.writeFile - rhs: writeFileLText -- warn: - lhs: Data.Text.Lazy.IO.appendFile - rhs: appendFileLText -- warn: - lhs: Data.ByteString.readFile - rhs: readFileBS -- warn: - lhs: Data.ByteString.writeFile - rhs: writeFileBS -- warn: - lhs: Data.ByteString.appendFile - rhs: appendFileBS -- warn: - lhs: Data.ByteString.Lazy.readFile - rhs: readFileLBS -- warn: - lhs: Data.ByteString.Lazy.writeFile - rhs: writeFileLBS -- warn: - lhs: Data.ByteString.Lazy.appendFile - rhs: appendFileLBS -- hint: - lhs: foldl' (flip f) - note: Use 'flipfoldl'' - rhs: flipfoldl' f -- warn: - lhs: foldl' (+) 0 - rhs: sum -- warn: - lhs: foldl' (*) 1 - rhs: product -- hint: - lhs: fmap and (sequence s) - note: Applying this hint would mean that some actions that were being executed - previously would no longer be executed. - rhs: andM s -- hint: - lhs: and <$> sequence s - note: Applying this hint would mean that some actions that were being executed - previously would no longer be executed. - rhs: andM s -- hint: - lhs: fmap or (sequence s) - note: Applying this hint would mean that some actions that were being executed - previously would no longer be executed. - rhs: orM s -- hint: - lhs: or <$> sequence s - note: Applying this hint would mean that some actions that were being executed - previously would no longer be executed. - rhs: orM s -- hint: - lhs: fmap and (mapM f s) - note: Applying this hint would mean that some actions that were being executed - previously would no longer be executed. - rhs: allM f s -- hint: - lhs: and <$> mapM f s - note: Applying this hint would mean that some actions that were being executed - previously would no longer be executed. - rhs: allM f s -- hint: - lhs: fmap or (mapM f s) - note: Applying this hint would mean that some actions that were being executed - previously would no longer be executed. - rhs: anyM f s -- hint: - lhs: or <$> mapM f s - note: Applying this hint would mean that some actions that were being executed - previously would no longer be executed. - rhs: anyM f s -- warn: - lhs: getAlt (foldMap (Alt . f) xs) - rhs: asumMap xs -- warn: - lhs: getAlt . foldMap (Alt . f) - rhs: asumMap -- hint: - lhs: foldr (\x acc -> f x <|> acc) empty - note: Use 'asumMap' - rhs: asumMap f -- hint: - lhs: asum (map f xs) - note: Use 'asumMap' - rhs: asumMap f xs -- warn: - lhs: map fst &&& map snd - rhs: unzip -- hint: - lhs: fmap (fmap f) x - note: Use '(<<$>>)' - rhs: f <<$>> x -- hint: - lhs: (\f -> f x) <$> ff - note: Use flap operator - rhs: ff ?? x -- hint: - lhs: fmap (\f -> f x) ff - note: Use flap operator - rhs: ff ?? x -- hint: - lhs: fmap ($ x) ff - note: Use flap operator - rhs: ff ?? x -- hint: - lhs: ($ x) <$> ff - note: Use flap operator - rhs: ff ?? x -- warn: - lhs: fmap f (nonEmpty x) - rhs: viaNonEmpty f x -- warn: - lhs: fmap f . nonEmpty - rhs: viaNonEmpty f -- warn: - lhs: f <$> nonEmpty x - rhs: viaNonEmpty f x -- warn: - lhs: f >>= guard - rhs: guardM f -- warn: - lhs: guard =<< f - rhs: guardM f -- warn: - lhs: whenM (not <$> x) - rhs: unlessM x -- warn: - lhs: unlessM (not <$> x) - rhs: whenM x -- warn: - lhs: either (const True) (const False) - rhs: isLeft -- warn: - lhs: either (const False) (const True) - rhs: isRight -- warn: - lhs: either id (const a) - rhs: fromLeft a -- warn: - lhs: either (const b) id - rhs: fromRight b -- warn: - lhs: either Just (const Nothing) - rhs: leftToMaybe -- warn: - lhs: either (const Nothing) Just - rhs: rightToMaybe -- warn: - lhs: maybe (Left l) Right - rhs: maybeToRight l -- warn: - lhs: maybe (Right r) Left - rhs: maybeToLeft r -- warn: - lhs: case m of Just x -> f x; Nothing -> pure () - rhs: whenJust m f -- warn: - lhs: case m of Just x -> f x; Nothing -> return () - rhs: whenJust m f -- warn: - lhs: case m of Just x -> f x; Nothing -> pass - rhs: whenJust m f -- warn: - lhs: case m of Nothing -> pure () ; Just x -> f x - rhs: whenJust m f -- warn: - lhs: case m of Nothing -> return (); Just x -> f x - rhs: whenJust m f -- warn: - lhs: case m of Nothing -> pass ; Just x -> f x - rhs: whenJust m f -- warn: - lhs: maybe (pure ()) f m - rhs: whenJust m f -- warn: - lhs: maybe (return ()) f m - rhs: whenJust m f -- warn: - lhs: maybe pass f m - rhs: whenJust m f -- warn: - lhs: m >>= \a -> whenJust a f - rhs: whenJustM m f -- warn: - lhs: m >>= \case Just x -> f x; Nothing -> pure () - rhs: whenJustM m f -- warn: - lhs: m >>= \case Just x -> f x; Nothing -> return () - rhs: whenJustM m f -- warn: - lhs: m >>= \case Just x -> f x; Nothing -> pass - rhs: whenJustM m f -- warn: - lhs: m >>= \case Nothing -> pure () ; Just x -> f x - rhs: whenJustM m f -- warn: - lhs: m >>= \case Nothing -> return (); Just x -> f x - rhs: whenJustM m f -- warn: - lhs: m >>= \case Nothing -> pass ; Just x -> f x - rhs: whenJustM m f -- warn: - lhs: maybe (pure ()) f =<< m - rhs: whenJustM m f -- warn: - lhs: maybe (return ()) f =<< m - rhs: whenJustM m f -- warn: - lhs: maybe pass f =<< m - rhs: whenJustM m f -- warn: - lhs: m >>= maybe (pure ()) f - rhs: whenJustM m f -- warn: - lhs: m >>= maybe (return ()) f - rhs: whenJustM m f -- warn: - lhs: m >>= maybe pass f - rhs: whenJustM m f -- warn: - lhs: case m of Just _ -> pure () ; Nothing -> x - rhs: whenNothing_ m x -- warn: - lhs: case m of Just _ -> return (); Nothing -> x - rhs: whenNothing_ m x -- warn: - lhs: case m of Just _ -> pass ; Nothing -> x - rhs: whenNothing_ m x -- warn: - lhs: case m of Nothing -> x; Just _ -> pure () - rhs: whenNothing_ m x -- warn: - lhs: case m of Nothing -> x; Just _ -> return () - rhs: whenNothing_ m x -- warn: - lhs: case m of Nothing -> x; Just _ -> pass - rhs: whenNothing_ m x -- warn: - lhs: maybe x (\_ -> pure () ) m - rhs: whenNothing_ m x -- warn: - lhs: maybe x (\_ -> return () ) m - rhs: whenNothing_ m x -- warn: - lhs: maybe x (\_ -> pass ) m - rhs: whenNothing_ m x -- warn: - lhs: maybe x (const (pure () )) m - rhs: whenNothing_ m x -- warn: - lhs: maybe x (const (return ())) m - rhs: whenNothing_ m x -- warn: - lhs: maybe x (const pass) m - rhs: whenNothing_ m x -- warn: - lhs: m >>= \a -> whenNothing_ a x - rhs: whenNothingM_ m x -- warn: - lhs: m >>= \case Just _ -> pure () ; Nothing -> x - rhs: whenNothingM_ m x -- warn: - lhs: m >>= \case Just _ -> return (); Nothing -> x - rhs: whenNothingM_ m x -- warn: - lhs: m >>= \case Just _ -> pass ; Nothing -> x - rhs: whenNothingM_ m x -- warn: - lhs: m >>= \case Nothing -> x; Just _ -> pure () - rhs: whenNothingM_ m x -- warn: - lhs: m >>= \case Nothing -> x; Just _ -> return () - rhs: whenNothingM_ m x -- warn: - lhs: m >>= \case Nothing -> x; Just _ -> pass - rhs: whenNothingM_ m x -- warn: - lhs: maybe x (\_ -> pure () ) =<< m - rhs: whenNothingM_ m x -- warn: - lhs: maybe x (\_ -> return () ) =<< m - rhs: whenNothingM_ m x -- warn: - lhs: maybe x (\_ -> pass ) =<< m - rhs: whenNothingM_ m x -- warn: - lhs: maybe x (const (pure () )) =<< m - rhs: whenNothingM_ m x -- warn: - lhs: maybe x (const (return ())) =<< m - rhs: whenNothingM_ m x -- warn: - lhs: maybe x (const pass) =<< m - rhs: whenNothingM_ m x -- warn: - lhs: m >>= maybe x (\_ -> pure ()) - rhs: whenNothingM_ m x -- warn: - lhs: m >>= maybe x (\_ -> return ()) - rhs: whenNothingM_ m x -- warn: - lhs: m >>= maybe x (\_ -> pass) - rhs: whenNothingM_ m x -- warn: - lhs: m >>= maybe x (const (pure ()) ) - rhs: whenNothingM_ m x -- warn: - lhs: m >>= maybe x (const (return ())) - rhs: whenNothingM_ m x -- warn: - lhs: m >>= maybe x (const pass) - rhs: whenNothingM_ m x -- warn: - lhs: whenLeft () - rhs: whenLeft_ -- warn: - lhs: case m of Left x -> f x; Right _ -> pure () - rhs: whenLeft_ m f -- warn: - lhs: case m of Left x -> f x; Right _ -> return () - rhs: whenLeft_ m f -- warn: - lhs: case m of Left x -> f x; Right _ -> pass - rhs: whenLeft_ m f -- warn: - lhs: case m of Right _ -> pure () ; Left x -> f x - rhs: whenLeft_ m f -- warn: - lhs: case m of Right _ -> return (); Left x -> f x - rhs: whenLeft_ m f -- warn: - lhs: case m of Right _ -> pass ; Left x -> f x - rhs: whenLeft_ m f -- warn: - lhs: either f (\_ -> pure () ) m - rhs: whenLeft_ m f -- warn: - lhs: either f (\_ -> return () ) m - rhs: whenLeft_ m f -- warn: - lhs: either f (\_ -> pass ) m - rhs: whenLeft_ m f -- warn: - lhs: either f (const (pure () )) m - rhs: whenLeft_ m f -- warn: - lhs: either f (const (return ())) m - rhs: whenLeft_ m f -- warn: - lhs: either f (const pass) m - rhs: whenLeft_ m f -- warn: - lhs: m >>= \a -> whenLeft_ a f - rhs: whenLeftM_ m f -- warn: - lhs: m >>= \case Left x -> f x; Right _ -> pure () - rhs: whenLeftM_ m f -- warn: - lhs: m >>= \case Left x -> f x; Right _ -> return () - rhs: whenLeftM_ m f -- warn: - lhs: m >>= \case Left x -> f x; Right _ -> pass - rhs: whenLeftM_ m f -- warn: - lhs: m >>= \case Right _ -> pure () ; Left x -> f x - rhs: whenLeftM_ m f -- warn: - lhs: m >>= \case Right _ -> return (); Left x -> f x - rhs: whenLeftM_ m f -- warn: - lhs: m >>= \case Right _ -> pass ; Left x -> f x - rhs: whenLeftM_ m f -- warn: - lhs: either f (\_ -> pure () ) =<< m - rhs: whenLeftM_ m f -- warn: - lhs: either f (\_ -> return () ) =<< m - rhs: whenLeftM_ m f -- warn: - lhs: either f (\_ -> pass ) =<< m - rhs: whenLeftM_ m f -- warn: - lhs: either f (const (pure () )) =<< m - rhs: whenLeftM_ m f -- warn: - lhs: either f (const (return ())) =<< m - rhs: whenLeftM_ m f -- warn: - lhs: either f (const pass) =<< m - rhs: whenLeftM_ m f -- warn: - lhs: m >>= either f (\_ -> pure ()) - rhs: whenLeftM_ m f -- warn: - lhs: m >>= either f (\_ -> return ()) - rhs: whenLeftM_ m f -- warn: - lhs: m >>= either f (\_ -> pass) - rhs: whenLeftM_ m f -- warn: - lhs: m >>= either f (const (pure ()) ) - rhs: whenLeftM_ m f -- warn: - lhs: m >>= either f (const (return ())) - rhs: whenLeftM_ m f -- warn: - lhs: m >>= either f (const pass) - rhs: whenLeftM_ m f -- warn: - lhs: whenRight () - rhs: whenRight_ -- warn: - lhs: case m of Right x -> f x; Left _ -> pure () - rhs: whenRight_ m f -- warn: - lhs: case m of Right x -> f x; Left _ -> return () - rhs: whenRight_ m f -- warn: - lhs: case m of Right x -> f x; Left _ -> pass - rhs: whenRight_ m f -- warn: - lhs: case m of Left _ -> pure () ; Right x -> f x - rhs: whenRight_ m f -- warn: - lhs: case m of Left _ -> return (); Right x -> f x - rhs: whenRight_ m f -- warn: - lhs: case m of Left _ -> pass ; Right x -> f x - rhs: whenRight_ m f -- warn: - lhs: either (\_ -> pure () ) f m - rhs: whenRight_ m f -- warn: - lhs: either (\_ -> return () ) f m - rhs: whenRight_ m f -- warn: - lhs: either (\_ -> pass ) f m - rhs: whenRight_ m f -- warn: - lhs: either (const (pure () )) f m - rhs: whenRight_ m f -- warn: - lhs: either (const (return ())) f m - rhs: whenRight_ m f -- warn: - lhs: either (const pass) f m - rhs: whenRight_ m f -- warn: - lhs: m >>= \a -> whenRight_ a f - rhs: whenRightM_ m f -- warn: - lhs: ! 'm >>= \case Right x -> f x; Left _ -> pure () ' - rhs: whenRightM_ m f -- warn: - lhs: m >>= \case Right x -> f x; Left _ -> return () - rhs: whenRightM_ m f -- warn: - lhs: m >>= \case Right x -> f x; Left _ -> pass - rhs: whenRightM_ m f -- warn: - lhs: m >>= \case Left _ -> pure () ; Right x -> f x - rhs: whenRightM_ m f -- warn: - lhs: m >>= \case Left _ -> return (); Right x -> f x - rhs: whenRightM_ m f -- warn: - lhs: m >>= \case Left _ -> pass ; Right x -> f x - rhs: whenRightM_ m f -- warn: - lhs: either (\_ -> pure () ) f =<< m - rhs: whenRightM_ m f -- warn: - lhs: either (\_ -> return () ) f =<< m - rhs: whenRightM_ m f -- warn: - lhs: either (\_ -> pass ) f =<< m - rhs: whenRightM_ m f -- warn: - lhs: either (const (pure () )) f =<< m - rhs: whenRightM_ m f -- warn: - lhs: either (const (return ())) f =<< m - rhs: whenRightM_ m f -- warn: - lhs: either (const pass) f =<< m - rhs: whenRightM_ m f -- warn: - lhs: m >>= either (\_ -> pure ()) f - rhs: whenRightM_ m f -- warn: - lhs: m >>= either (\_ -> return ()) f - rhs: whenRightM_ m f -- warn: - lhs: m >>= either (\_ -> pass) f - rhs: whenRightM_ m f -- warn: - lhs: m >>= either (const (pure ()) ) f - rhs: whenRightM_ m f -- warn: - lhs: m >>= either (const (return ())) f - rhs: whenRightM_ m f -- warn: - lhs: m >>= either (const pass) f - rhs: whenRightM_ m f -- warn: - lhs: ! 'case m of Left x -> f x; Right _ -> pure d ' - rhs: whenLeft d m f -- warn: - lhs: case m of Left x -> f x; Right _ -> return d - rhs: whenLeft d m f -- warn: - lhs: case m of Right _ -> pure d ; Left x -> f x - rhs: whenLeft d m f -- warn: - lhs: case m of Right _ -> return d; Left x -> f x - rhs: whenLeft d m f -- warn: - lhs: either f (\_ -> pure d ) m - rhs: whenLeft d m f -- warn: - lhs: either f (\_ -> return d ) m - rhs: whenLeft d m f -- warn: - lhs: either f (const (pure d )) m - rhs: whenLeft d m f -- warn: - lhs: either f (const (return d)) m - rhs: whenLeft d m f -- warn: - lhs: m >>= \a -> whenLeft d a f - rhs: whenLeftM d m f -- warn: - lhs: m >>= \case Left x -> f x; Right _ -> pure d - rhs: whenLeftM d m f -- warn: - lhs: m >>= \case Left x -> f x; Right _ -> return d - rhs: whenLeftM d m f -- warn: - lhs: m >>= \case Right _ -> pure d ; Left x -> f x - rhs: whenLeftM d m f -- warn: - lhs: m >>= \case Right _ -> return d; Left x -> f x - rhs: whenLeftM d m f -- warn: - lhs: either f (\_ -> pure d ) =<< m - rhs: whenLeftM d m f -- warn: - lhs: either f (\_ -> return d ) =<< m - rhs: whenLeftM d m f -- warn: - lhs: either f (const (pure d )) =<< m - rhs: whenLeftM d m f -- warn: - lhs: either f (const (return d)) =<< m - rhs: whenLeftM d m f -- warn: - lhs: m >>= either f (\_ -> pure d) - rhs: whenLeftM d m f -- warn: - lhs: m >>= either f (\_ -> return d) - rhs: whenLeftM d m f -- warn: - lhs: m >>= either f (const (pure d)) - rhs: whenLeftM d m f -- warn: - lhs: m >>= either f (const (return d)) - rhs: whenLeftM d m f -- warn: - lhs: case m of Right x -> f x; Left _ -> pure d - rhs: whenRight d m f -- warn: - lhs: case m of Right x -> f x; Left _ -> return d - rhs: whenRight d m f -- warn: - lhs: case m of Left _ -> pure d ; Right x -> f x - rhs: whenRight d m f -- warn: - lhs: case m of Left _ -> return d; Right x -> f x - rhs: whenRight d m f -- warn: - lhs: either (\_ -> pure d ) f m - rhs: whenRight d m f -- warn: - lhs: either (\_ -> return d ) f m - rhs: whenRight d m f -- warn: - lhs: either (const (pure d )) f m - rhs: whenRight d m f -- warn: - lhs: either (const (return d)) f m - rhs: whenRight d m f -- warn: - lhs: m >>= \a -> whenRight d a f - rhs: whenRightM d m f -- warn: - lhs: m >>= \case Right x -> f x; Left _ -> pure d - rhs: whenRightM d m f -- warn: - lhs: m >>= \case Right x -> f x; Left _ -> return d - rhs: whenRightM d m f -- warn: - lhs: m >>= \case Left _ -> pure d ; Right x -> f x - rhs: whenRightM d m f -- warn: - lhs: m >>= \case Left _ -> return d; Right x -> f x - rhs: whenRightM d m f -- warn: - lhs: either (\_ -> pure d ) f =<< m - rhs: whenRightM d m f -- warn: - lhs: either (\_ -> return d ) f =<< m - rhs: whenRightM d m f -- warn: - lhs: either (const (pure d )) f =<< m - rhs: whenRightM d m f -- warn: - lhs: either (const (return d)) f =<< m - rhs: whenRightM d m f -- warn: - lhs: m >>= either (\_ -> pure d) f - rhs: whenRightM d m f -- warn: - lhs: m >>= either (\_ -> return d) f - rhs: whenRightM d m f -- warn: - lhs: m >>= either (const (pure d) ) f - rhs: whenRightM d m f -- warn: - lhs: m >>= either (const (return d)) f - rhs: whenRightM d m f -- warn: - lhs: case m of [] -> return (); (x:xs) -> f (x :| xs) - rhs: whenNotNull m f -- warn: - lhs: case m of [] -> pure () ; (x:xs) -> f (x :| xs) - rhs: whenNotNull m f -- warn: - lhs: case m of [] -> pass ; (x:xs) -> f (x :| xs) - rhs: whenNotNull m f -- warn: - lhs: case m of (x:xs) -> f (x :| xs); [] -> return () - rhs: whenNotNull m f -- warn: - lhs: ! 'case m of (x:xs) -> f (x :| xs); [] -> pure () ' - rhs: whenNotNull m f -- warn: - lhs: ! 'case m of (x:xs) -> f (x :| xs); [] -> pass ' - rhs: whenNotNull m f -- warn: - lhs: m >>= \case [] -> pass ; (x:xs) -> f (x :| xs) - rhs: whenNotNullM m f -- warn: - lhs: m >>= \case [] -> pure () ; (x:xs) -> f (x :| xs) - rhs: whenNotNullM m f -- warn: - lhs: m >>= \case [] -> return (); (x:xs) -> f (x :| xs) - rhs: whenNotNullM m f -- warn: - lhs: ! 'm >>= \case (x:xs) -> f (x :| xs); [] -> pass ' - rhs: whenNotNullM m f -- warn: - lhs: ! 'm >>= \case (x:xs) -> f (x :| xs); [] -> pure () ' - rhs: whenNotNullM m f -- warn: - lhs: m >>= \case (x:xs) -> f (x :| xs); [] -> return () - rhs: whenNotNullM m f -- warn: - lhs: mapMaybe leftToMaybe - rhs: lefts -- warn: - lhs: mapMaybe rightToMaybe - rhs: rights -- warn: - lhs: flip runReaderT - rhs: usingReaderT -- warn: - lhs: flip runReader - rhs: usingReader -- warn: - lhs: flip runStateT - rhs: usingStateT -- warn: - lhs: flip runState - rhs: usingState -- warn: - lhs: fst <$> usingStateT s st - rhs: evaluatingStateT s st -- warn: - lhs: fst (usingState s st) - rhs: evaluatingState s st -- warn: - lhs: snd <$> usingStateT s st - rhs: executingStateT s st -- warn: - lhs: snd (usingState s st) - rhs: executingState s st -- warn: - lhs: MaybeT (pure m) - rhs: hoistMaybe m -- warn: - lhs: MaybeT (return m) - rhs: hoistMaybe m -- warn: - lhs: MaybeT . pure - rhs: hoistMaybe -- warn: - lhs: MaybeT . return - rhs: hoistMaybe -- warn: - lhs: ExceptT (pure m) - rhs: hoistEither m -- warn: - lhs: ExceptT (return m) - rhs: hoistEither m -- warn: - lhs: ExceptT . pure - rhs: hoistEither -- warn: - lhs: ExceptT . return - rhs: hoistEither -- warn: - lhs: fromMaybe mempty - rhs: maybeToMonoid -- warn: - lhs: ! 'm ?: mempty' - rhs: maybeToMonoid m -- warn: - lhs: Data.Map.toAscList (Data.Map.fromList x) - rhs: sortWith fst x -- warn: - lhs: Data.Map.toDescList (Data.Map.fromList x) - rhs: sortWith (Down . fst) x -- warn: - lhs: Data.Set.toList (Data.Set.fromList l) - rhs: sortNub l -- warn: - lhs: Data.Set.assocs (Data.Set.fromList l) - rhs: sortNub l -- warn: - lhs: Data.Set.toAscList (Data.Set.fromList l) - rhs: sortNub l -- warn: - lhs: Data.HashSet.toList (Data.HashSet.fromList l) - rhs: unstableNub l -- warn: - lhs: nub - note: ! '''nub'' is O(n^2), ''ordNub'' is O(n log n)' - rhs: ordNub -- warn: - lhs: sortBy (comparing f) - note: If the function you are using for 'comparing' is slow, use 'sortOn' instead - of 'sortWith', because 'sortOn' caches applications the function and 'sortWith' - doesn't. - rhs: sortWith f -- warn: - lhs: sortOn fst - note: ! '''sortWith'' will be faster here because it doesn''t do caching' - rhs: sortWith fst -- warn: - lhs: sortOn snd - note: ! '''sortWith'' will be faster here because it doesn''t do caching' - rhs: sortWith snd -- warn: - lhs: sortOn (Down . fst) - note: ! '''sortWith'' will be faster here because it doesn''t do caching' - rhs: sortWith (Down . fst) -- warn: - lhs: sortOn (Down . snd) - note: ! '''sortWith'' will be faster here because it doesn''t do caching' - rhs: sortWith (Down . snd) -- warn: - lhs: Data.Text.IO.putStr - rhs: putText -- warn: - lhs: Data.Text.IO.putStrLn - rhs: putTextLn -- warn: - lhs: Data.Text.Lazy.IO.putStr - rhs: putLText -- warn: - lhs: Data.Text.Lazy.IO.putStrLn - rhs: putLTextLn -- warn: - lhs: Data.ByteString.Char8.putStr - rhs: putBS -- warn: - lhs: Data.ByteString.Char8.putStrLn - rhs: putBSLn -- warn: - lhs: Data.ByteString.Lazy.Char8.putStr - rhs: putLBS -- warn: - lhs: Data.ByteString.Lazy.Char8.putStrLn - rhs: putLBSLn -- warn: - lhs: Data.Text.Lazy.Text - rhs: LText -- warn: - lhs: Data.ByteString.Lazy.ByteString - rhs: LByteString -- warn: - lhs: Data.ByteString.UTF8.fromString - rhs: encodeUtf8 -- warn: - lhs: Data.ByteString.UTF8.toString - rhs: decodeUtf8 -- warn: - lhs: Data.Text.Encoding.encodeUtf8 - rhs: encodeUtf8 -- warn: - lhs: Data.Text.Encoding.decodeUtf8 - rhs: decodeUtf8 -- warn: - lhs: Data.ByteString.Lazy.toStrict (encodeUtf8 x) - rhs: encodeUtf8 x -- warn: - lhs: toStrict (encodeUtf8 x) - rhs: encodeUtf8 x -- warn: - lhs: decodeUtf8 (Data.ByteString.Lazy.fromStrict x) - rhs: decodeUtf8 x -- warn: - lhs: decodeUtf8 (fromStrict x) - rhs: decodeUtf8 x -- warn: - lhs: Data.ByteString.Lazy.UTF8.fromString - rhs: encodeUtf8 -- warn: - lhs: Data.ByteString.Lazy.UTF8.toString - rhs: decodeUtf8 -- warn: - lhs: Data.ByteString.Lazy.fromStrict (Data.Text.Encoding.encodeUtf8 x) - rhs: encodeUtf8 x -- warn: - lhs: Data.ByteString.Lazy.fromStrict (encodeUtf8 x) - rhs: encodeUtf8 x -- warn: - lhs: Data.Text.Encoding.decodeUtf8 (Data.ByteString.Lazy.toStrict x) - rhs: decodeUtf8 x -- warn: - lhs: Data.Text.Encoding.decodeUtf8 (toStrict x) - rhs: decodeUtf8 x -- warn: - lhs: decodeUtf8 (Data.ByteString.Lazy.toStrict x) - rhs: decodeUtf8 x -- warn: - lhs: decodeUtf8 (toStrict x) - rhs: decodeUtf8 x -- warn: - lhs: Data.Text.pack - rhs: toText -- warn: - lhs: Data.Text.unpack - rhs: toString -- warn: - lhs: Data.Text.Lazy.pack - rhs: toLText -- warn: - lhs: Data.Text.Lazy.unpack - rhs: toString -- warn: - lhs: Data.Text.Lazy.toStrict - rhs: toText -- warn: - lhs: Data.Text.Lazy.fromStrict - rhs: toLText -- warn: - lhs: Data.Text.pack (show x) - rhs: show x -- warn: - lhs: Data.Text.Lazy.pack (show x) - rhs: show x -- warn: - lhs: Data.ByteString.Lazy.fromStrict - rhs: fromStrict -- warn: - lhs: Data.ByteString.Lazy.toStrict - rhs: toStrict -- warn: - lhs: Data.Text.Lazy.fromStrict - rhs: fromStrict -- warn: - lhs: Data.Text.Lazy.toStrict - rhs: toStrict -- warn: - lhs: Control.Applicative.Alternative - note: ! '''Alternative'' is already exported from Relude' - name: Use 'Alternative' from Relude - rhs: Alternative -- warn: - lhs: Control.Applicative.empty - note: ! '''empty'' is already exported from Relude' - name: Use 'empty' from Relude - rhs: empty -- warn: - lhs: (Control.Applicative.<|>) - note: Operator '(<|>)' is already exported from Relude - name: Use '<|>' from Relude - rhs: (<|>) -- warn: - lhs: Control.Applicative.some - note: ! '''some'' is already exported from Relude' - name: Use 'some' from Relude - rhs: some -- warn: - lhs: Control.Applicative.many - note: ! '''many'' is already exported from Relude' - name: Use 'many' from Relude - rhs: many -- warn: - lhs: Control.Applicative.Const - note: ! '''Const'' is already exported from Relude' - name: Use 'Const' from Relude - rhs: Const -- warn: - lhs: Control.Applicative.getConst - note: ! '''getConst'' is already exported from Relude' - name: Use 'getConst' from Relude - rhs: getConst -- warn: - lhs: Control.Applicative.ZipList - note: ! '''ZipList'' is already exported from Relude' - name: Use 'ZipList' from Relude - rhs: ZipList -- warn: - lhs: Control.Applicative.getZipList - note: ! '''getZipList'' is already exported from Relude' - name: Use 'getZipList' from Relude - rhs: getZipList -- warn: - lhs: Control.Applicative.liftA2 - note: ! '''liftA2'' is already exported from Relude' - name: Use 'liftA2' from Relude - rhs: liftA2 -- warn: - lhs: Control.Applicative.liftA3 - note: ! '''liftA3'' is already exported from Relude' - name: Use 'liftA3' from Relude - rhs: liftA3 -- warn: - lhs: Control.Applicative.optional - note: ! '''optional'' is already exported from Relude' - name: Use 'optional' from Relude - rhs: optional -- warn: - lhs: (Control.Applicative.<**>) - note: Operator '(<**>)' is already exported from Relude - name: Use '<**>' from Relude - rhs: (<**>) -- warn: - lhs: Data.Bits.xor - note: ! '''xor'' is already exported from Relude' - name: Use 'xor' from Relude - rhs: xor -- warn: - lhs: Data.Char.chr - note: ! '''chr'' is already exported from Relude' - name: Use 'chr' from Relude - rhs: chr -- warn: - lhs: Data.Int.Int8 - note: ! '''Int8'' is already exported from Relude' - name: Use 'Int8' from Relude - rhs: Int8 -- warn: - lhs: Data.Int.Int16 - note: ! '''Int16'' is already exported from Relude' - name: Use 'Int16' from Relude - rhs: Int16 -- warn: - lhs: Data.Int.Int32 - note: ! '''Int32'' is already exported from Relude' - name: Use 'Int32' from Relude - rhs: Int32 -- warn: - lhs: Data.Int.Int64 - note: ! '''Int64'' is already exported from Relude' - name: Use 'Int64' from Relude - rhs: Int64 -- warn: - lhs: Data.Word.Word8 - note: ! '''Word8'' is already exported from Relude' - name: Use 'Word8' from Relude - rhs: Word8 -- warn: - lhs: Data.Word.Word16 - note: ! '''Word16'' is already exported from Relude' - name: Use 'Word16' from Relude - rhs: Word16 -- warn: - lhs: Data.Word.Word32 - note: ! '''Word32'' is already exported from Relude' - name: Use 'Word32' from Relude - rhs: Word32 -- warn: - lhs: Data.Word.Word64 - note: ! '''Word64'' is already exported from Relude' - name: Use 'Word64' from Relude - rhs: Word64 -- warn: - lhs: Data.Word.byteSwap16 - note: ! '''byteSwap16'' is already exported from Relude' - name: Use 'byteSwap16' from Relude - rhs: byteSwap16 -- warn: - lhs: Data.Word.byteSwap32 - note: ! '''byteSwap32'' is already exported from Relude' - name: Use 'byteSwap32' from Relude - rhs: byteSwap32 -- warn: - lhs: Data.Word.byteSwap64 - note: ! '''byteSwap64'' is already exported from Relude' - name: Use 'byteSwap64' from Relude - rhs: byteSwap64 -- warn: - lhs: Numeric.Natural.Natural - note: ! '''Natural'' is already exported from Relude' - name: Use 'Natural' from Relude - rhs: Natural -- warn: - lhs: System.IO.Handle - note: ! '''Handle'' is already exported from Relude' - name: Use 'Handle' from Relude - rhs: Handle -- warn: - lhs: System.IO.IOMode - note: ! '''IOMode'' is already exported from Relude' - name: Use 'IOMode' from Relude - rhs: IOMode -- warn: - lhs: System.IO.ReadMode - note: ! '''ReadMode'' is already exported from Relude' - name: Use 'ReadMode' from Relude - rhs: ReadMode -- warn: - lhs: System.IO.WriteMode - note: ! '''WriteMode'' is already exported from Relude' - name: Use 'WriteMode' from Relude - rhs: WriteMode -- warn: - lhs: System.IO.AppendMode - note: ! '''AppendMode'' is already exported from Relude' - name: Use 'AppendMode' from Relude - rhs: AppendMode -- warn: - lhs: System.IO.ReadWriteMode - note: ! '''ReadWriteMode'' is already exported from Relude' - name: Use 'ReadWriteMode' from Relude - rhs: ReadWriteMode -- warn: - lhs: System.IO.stderr - note: ! '''stderr'' is already exported from Relude' - name: Use 'stderr' from Relude - rhs: stderr -- warn: - lhs: System.IO.stdin - note: ! '''stdin'' is already exported from Relude' - name: Use 'stdin' from Relude - rhs: stdin -- warn: - lhs: System.IO.stdout - note: ! '''stdout'' is already exported from Relude' - name: Use 'stdout' from Relude - rhs: stdout -- warn: - lhs: System.IO.withFile - note: ! '''withFile'' is already exported from Relude' - name: Use 'withFile' from Relude - rhs: withFile -- warn: - lhs: Data.Ord.Down - note: ! '''Down'' is already exported from Relude' - name: Use 'Down' from Relude - rhs: Down -- warn: - lhs: Data.Ord.comparing - note: ! '''comparing'' is already exported from Relude' - name: Use 'comparing' from Relude - rhs: comparing -- warn: - lhs: Data.Coerce.Coercible - note: ! '''Coercible'' is already exported from Relude' - name: Use 'Coercible' from Relude - rhs: Coercible -- warn: - lhs: Data.Coerce.coerce - note: ! '''coerce'' is already exported from Relude' - name: Use 'coerce' from Relude - rhs: coerce -- warn: - lhs: Data.Kind.Constraint - note: ! '''Constraint'' is already exported from Relude' - name: Use 'Constraint' from Relude - rhs: Constraint -- warn: - lhs: Data.Kind.Type - note: ! '''Type'' is already exported from Relude' - name: Use 'Type' from Relude - rhs: Type -- warn: - lhs: Data.Typeable.Typeable - note: ! '''Typeable'' is already exported from Relude' - name: Use 'Typeable' from Relude - rhs: Typeable -- warn: - lhs: Data.Proxy.Proxy - note: ! '''Proxy'' is already exported from Relude' - name: Use 'Proxy' from Relude - rhs: Proxy -- warn: - lhs: Data.Typeable.Typeable - note: ! '''Typeable'' is already exported from Relude' - name: Use 'Typeable' from Relude - rhs: Typeable -- warn: - lhs: Data.Void.Void - note: ! '''Void'' is already exported from Relude' - name: Use 'Void' from Relude - rhs: Void -- warn: - lhs: Data.Void.absurd - note: ! '''absurd'' is already exported from Relude' - name: Use 'absurd' from Relude - rhs: absurd -- warn: - lhs: Data.Void.vacuous - note: ! '''vacuous'' is already exported from Relude' - name: Use 'vacuous' from Relude - rhs: vacuous -- warn: - lhs: Data.Base.maxInt - note: ! '''maxInt'' is already exported from Relude' - name: Use 'maxInt' from Relude - rhs: maxInt -- warn: - lhs: Data.Base.minInt - note: ! '''minInt'' is already exported from Relude' - name: Use 'minInt' from Relude - rhs: minInt -- warn: - lhs: Data.Base.ord - note: ! '''ord'' is already exported from Relude' - name: Use 'ord' from Relude - rhs: ord -- warn: - lhs: GHC.Enum.boundedEnumFrom - note: ! '''boundedEnumFrom'' is already exported from Relude' - name: Use 'boundedEnumFrom' from Relude - rhs: boundedEnumFrom -- warn: - lhs: GHC.Enum.boundedEnumFromThen - note: ! '''boundedEnumFromThen'' is already exported from Relude' - name: Use 'boundedEnumFromThen' from Relude - rhs: boundedEnumFromThen -- warn: - lhs: GHC.Generics.Generic - note: ! '''Generic'' is already exported from Relude' - name: Use 'Generic' from Relude - rhs: Generic -- warn: - lhs: GHC.Real.Ratio - note: ! '''Ratio'' is already exported from Relude' - name: Use 'Ratio' from Relude - rhs: Ratio -- warn: - lhs: GHC.Real.Rational - note: ! '''Rational'' is already exported from Relude' - name: Use 'Rational' from Relude - rhs: Rational -- warn: - lhs: GHC.Real.denominator - note: ! '''denominator'' is already exported from Relude' - name: Use 'denominator' from Relude - rhs: denominator -- warn: - lhs: GHC.Real.numerator - note: ! '''numerator'' is already exported from Relude' - name: Use 'numerator' from Relude - rhs: numerator -- warn: - lhs: GHC.TypeNats.CmpNat - note: ! '''CmpNat'' is already exported from Relude' - name: Use 'CmpNat' from Relude - rhs: CmpNat -- warn: - lhs: GHC.TypeNats.KnownNat - note: ! '''KnownNat'' is already exported from Relude' - name: Use 'KnownNat' from Relude - rhs: KnownNat -- warn: - lhs: GHC.TypeNats.Nat - note: ! '''Nat'' is already exported from Relude' - name: Use 'Nat' from Relude - rhs: Nat -- warn: - lhs: GHC.TypeNats.SomeNat - note: ! '''SomeNat'' is already exported from Relude' - name: Use 'SomeNat' from Relude - rhs: SomeNat -- warn: - lhs: GHC.TypeNats.natVal - note: ! '''natVal'' is already exported from Relude' - name: Use 'natVal' from Relude - rhs: natVal -- warn: - lhs: GHC.TypeNats.someNatVal - note: ! '''someNatVal'' is already exported from Relude' - name: Use 'someNatVal' from Relude - rhs: someNatVal -- warn: - lhs: GHC.TypeLits.CmpNat - note: ! '''CmpNat'' is already exported from Relude' - name: Use 'CmpNat' from Relude - rhs: CmpNat -- warn: - lhs: GHC.TypeLits.KnownNat - note: ! '''KnownNat'' is already exported from Relude' - name: Use 'KnownNat' from Relude - rhs: KnownNat -- warn: - lhs: GHC.TypeLits.Nat - note: ! '''Nat'' is already exported from Relude' - name: Use 'Nat' from Relude - rhs: Nat -- warn: - lhs: GHC.TypeLits.SomeNat - note: ! '''SomeNat'' is already exported from Relude' - name: Use 'SomeNat' from Relude - rhs: SomeNat -- warn: - lhs: GHC.TypeLits.natVal - note: ! '''natVal'' is already exported from Relude' - name: Use 'natVal' from Relude - rhs: natVal -- warn: - lhs: GHC.TypeLits.someNatVal - note: ! '''someNatVal'' is already exported from Relude' - name: Use 'someNatVal' from Relude - rhs: someNatVal -- warn: - lhs: GHC.ExecutionStack.getStackTrace - note: ! '''getStackTrace'' is already exported from Relude' - name: Use 'getStackTrace' from Relude - rhs: getStackTrace -- warn: - lhs: GHC.ExecutionStack.showStackTrace - note: ! '''showStackTrace'' is already exported from Relude' - name: Use 'showStackTrace' from Relude - rhs: showStackTrace -- warn: - lhs: GHC.OverloadedLabels.IsLabel - note: ! '''IsLabel'' is already exported from Relude' - name: Use 'IsLabel' from Relude - rhs: IsLabel -- warn: - lhs: GHC.OverloadedLabels.fromLabel - note: ! '''fromLabel'' is already exported from Relude' - name: Use 'fromLabel' from Relude - rhs: fromLabel -- warn: - lhs: GHC.Stack.CallStack - note: ! '''CallStack'' is already exported from Relude' - name: Use 'CallStack' from Relude - rhs: CallStack -- warn: - lhs: GHC.Stack.HasCallStack - note: ! '''HasCallStack'' is already exported from Relude' - name: Use 'HasCallStack' from Relude - rhs: HasCallStack -- warn: - lhs: GHC.Stack.callStack - note: ! '''callStack'' is already exported from Relude' - name: Use 'callStack' from Relude - rhs: callStack -- warn: - lhs: GHC.Stack.currentCallStack - note: ! '''currentCallStack'' is already exported from Relude' - name: Use 'currentCallStack' from Relude - rhs: currentCallStack -- warn: - lhs: GHC.Stack.getCallStack - note: ! '''getCallStack'' is already exported from Relude' - name: Use 'getCallStack' from Relude - rhs: getCallStack -- warn: - lhs: GHC.Stack.prettyCallStack - note: ! '''prettyCallStack'' is already exported from Relude' - name: Use 'prettyCallStack' from Relude - rhs: prettyCallStack -- warn: - lhs: GHC.Stack.prettySrcLoc - note: ! '''prettySrcLoc'' is already exported from Relude' - name: Use 'prettySrcLoc' from Relude - rhs: prettySrcLoc -- warn: - lhs: GHC.Stack.withFrozenCallStack - note: ! '''withFrozenCallStack'' is already exported from Relude' - name: Use 'withFrozenCallStack' from Relude - rhs: withFrozenCallStack -- warn: - lhs: Data.Bifoldable.Bifoldable - note: ! '''Bifoldable'' is already exported from Relude' - name: Use 'Bifoldable' from Relude - rhs: Bifoldable -- warn: - lhs: Data.Bifoldable.bifold - note: ! '''bifold'' is already exported from Relude' - name: Use 'bifold' from Relude - rhs: bifold -- warn: - lhs: Data.Bifoldable.bifoldMap - note: ! '''bifoldMap'' is already exported from Relude' - name: Use 'bifoldMap' from Relude - rhs: bifoldMap -- warn: - lhs: Data.Bifoldable.bifoldr - note: ! '''bifoldr'' is already exported from Relude' - name: Use 'bifoldr' from Relude - rhs: bifoldr -- warn: - lhs: Data.Bifoldable.bifoldl - note: ! '''bifoldl'' is already exported from Relude' - name: Use 'bifoldl' from Relude - rhs: bifoldl -- warn: - lhs: Data.Bifoldable.bifoldl' - note: ! '''bifoldl'''' is already exported from Relude' - name: Use 'bifoldl'' from Relude - rhs: bifoldl' -- warn: - lhs: Data.Bifoldable.bifoldlM - note: ! '''bifoldlM'' is already exported from Relude' - name: Use 'bifoldlM' from Relude - rhs: bifoldlM -- warn: - lhs: Data.Bifoldable.bifoldr' - note: ! '''bifoldr'''' is already exported from Relude' - name: Use 'bifoldr'' from Relude - rhs: bifoldr' -- warn: - lhs: Data.Bifoldable.bifoldrM - note: ! '''bifoldrM'' is already exported from Relude' - name: Use 'bifoldrM' from Relude - rhs: bifoldrM -- warn: - lhs: Data.Bifoldable.bitraverse_ - note: ! '''bitraverse_'' is already exported from Relude' - name: Use 'bitraverse_' from Relude - rhs: bitraverse_ -- warn: - lhs: Data.Bifoldable.bifor_ - note: ! '''bifor_'' is already exported from Relude' - name: Use 'bifor_' from Relude - rhs: bifor_ -- warn: - lhs: Data.Bifoldable.biasum - note: ! '''biasum'' is already exported from Relude' - name: Use 'biasum' from Relude - rhs: biasum -- warn: - lhs: Data.Bifoldable.bisequence_ - note: ! '''bisequence_'' is already exported from Relude' - name: Use 'bisequence_' from Relude - rhs: bisequence_ -- warn: - lhs: Data.Bifoldable.biList - note: ! '''biList'' is already exported from Relude' - name: Use 'biList' from Relude - rhs: biList -- warn: - lhs: Data.Bifoldable.binull - note: ! '''binull'' is already exported from Relude' - name: Use 'binull' from Relude - rhs: binull -- warn: - lhs: Data.Bifoldable.bilength - note: ! '''bilength'' is already exported from Relude' - name: Use 'bilength' from Relude - rhs: bilength -- warn: - lhs: Data.Bifoldable.bielem - note: ! '''bielem'' is already exported from Relude' - name: Use 'bielem' from Relude - rhs: bielem -- warn: - lhs: Data.Bifoldable.biand - note: ! '''biand'' is already exported from Relude' - name: Use 'biand' from Relude - rhs: biand -- warn: - lhs: Data.Bifoldable.bior - note: ! '''bior'' is already exported from Relude' - name: Use 'bior' from Relude - rhs: bior -- warn: - lhs: Data.Bifoldable.biany - note: ! '''biany'' is already exported from Relude' - name: Use 'biany' from Relude - rhs: biany -- warn: - lhs: Data.Bifoldable.biall - note: ! '''biall'' is already exported from Relude' - name: Use 'biall' from Relude - rhs: biall -- warn: - lhs: Data.Bifoldable.bifind - note: ! '''bifind'' is already exported from Relude' - name: Use 'bifind' from Relude - rhs: bifind -- warn: - lhs: Data.Bitraversable.Bitraversable - note: ! '''Bitraversable'' is already exported from Relude' - name: Use 'Bitraversable' from Relude - rhs: Bitraversable -- warn: - lhs: Data.Bitraversable.bitraverse - note: ! '''bitraverse'' is already exported from Relude' - name: Use 'bitraverse' from Relude - rhs: bitraverse -- warn: - lhs: Data.Bitraversable.bisequence - note: ! '''bisequence'' is already exported from Relude' - name: Use 'bisequence' from Relude - rhs: bisequence -- warn: - lhs: Data.Bitraversable.bifor - note: ! '''bifor'' is already exported from Relude' - name: Use 'bifor' from Relude - rhs: bifor -- warn: - lhs: Data.Bitraversable.bimapDefault - note: ! '''bimapDefault'' is already exported from Relude' - name: Use 'bimapDefault' from Relude - rhs: bimapDefault -- warn: - lhs: Data.Bitraversable.bifoldMapDefault - note: ! '''bifoldMapDefault'' is already exported from Relude' - name: Use 'bifoldMapDefault' from Relude - rhs: bifoldMapDefault -- warn: - lhs: Control.Monad.guard - note: ! '''guard'' is already exported from Relude' - name: Use 'guard' from Relude - rhs: guard -- warn: - lhs: Control.Monad.unless - note: ! '''unless'' is already exported from Relude' - name: Use 'unless' from Relude - rhs: unless -- warn: - lhs: Control.Monad.when - note: ! '''when'' is already exported from Relude' - name: Use 'when' from Relude - rhs: when -- warn: - lhs: Data.Bool.bool - note: ! '''bool'' is already exported from Relude' - name: Use 'bool' from Relude - rhs: bool -- warn: - lhs: Data.Hashable.Hashable - note: ! '''Hashable'' is already exported from Relude' - name: Use 'Hashable' from Relude - rhs: Hashable -- warn: - lhs: Data.Hashable.hashWithSalt - note: ! '''hashWithSalt'' is already exported from Relude' - name: Use 'hashWithSalt' from Relude - rhs: hashWithSalt -- warn: - lhs: Data.HashMap.Strict.HashMap - note: ! '''HashMap'' is already exported from Relude' - name: Use 'HashMap' from Relude - rhs: HashMap -- warn: - lhs: Data.HashSet.HashSet - note: ! '''HashSet'' is already exported from Relude' - name: Use 'HashSet' from Relude - rhs: HashSet -- warn: - lhs: Data.IntMap.Strict.IntMap - note: ! '''IntMap'' is already exported from Relude' - name: Use 'IntMap' from Relude - rhs: IntMap -- warn: - lhs: Data.IntSet.IntSet - note: ! '''IntSet'' is already exported from Relude' - name: Use 'IntSet' from Relude - rhs: IntSet -- warn: - lhs: Data.Map.Strict.Map - note: ! '''Map'' is already exported from Relude' - name: Use 'Map' from Relude - rhs: Map -- warn: - lhs: Data.Sequence.Sequence - note: ! '''Sequence'' is already exported from Relude' - name: Use 'Sequence' from Relude - rhs: Sequence -- warn: - lhs: Data.Set.Set - note: ! '''Set'' is already exported from Relude' - name: Use 'Set' from Relude - rhs: Set -- warn: - lhs: Data.Tuple.swap - note: ! '''swap'' is already exported from Relude' - name: Use 'swap' from Relude - rhs: swap -- warn: - lhs: Data.Vector.Vector - note: ! '''Vector'' is already exported from Relude' - name: Use 'Vector' from Relude - rhs: Vector -- warn: - lhs: GHC.Exts.IsList - note: ! '''IsList'' is already exported from Relude' - name: Use 'IsList' from Relude - rhs: IsList -- warn: - lhs: GHC.Exts.fromList - note: ! '''fromList'' is already exported from Relude' - name: Use 'fromList' from Relude - rhs: fromList -- warn: - lhs: GHC.Exts.fromListN - note: ! '''fromListN'' is already exported from Relude' - name: Use 'fromListN' from Relude - rhs: fromListN -- warn: - lhs: Debug.Trace.trace - note: ! '''trace'' is already exported from Relude' - name: Use 'trace' from Relude - rhs: trace -- warn: - lhs: Debug.Trace.traceShow - note: ! '''traceShow'' is already exported from Relude' - name: Use 'traceShow' from Relude - rhs: traceShow -- warn: - lhs: Debug.Trace.traceShowId - note: ! '''traceShowId'' is already exported from Relude' - name: Use 'traceShowId' from Relude - rhs: traceShowId -- warn: - lhs: Debug.Trace.traceShowM - note: ! '''traceShowM'' is already exported from Relude' - name: Use 'traceShowM' from Relude - rhs: traceShowM -- warn: - lhs: Debug.Trace.traceM - note: ! '''traceM'' is already exported from Relude' - name: Use 'traceM' from Relude - rhs: traceM -- warn: - lhs: Debug.Trace.traceId - note: ! '''traceId'' is already exported from Relude' - name: Use 'traceId' from Relude - rhs: traceId -- warn: - lhs: Control.DeepSeq.NFData - note: ! '''NFData'' is already exported from Relude' - name: Use 'NFData' from Relude - rhs: NFData -- warn: - lhs: Control.DeepSeq.rnf - note: ! '''rnf'' is already exported from Relude' - name: Use 'rnf' from Relude - rhs: rnf -- warn: - lhs: Control.DeepSeq.deepseq - note: ! '''deepseq'' is already exported from Relude' - name: Use 'deepseq' from Relude - rhs: deepseq -- warn: - lhs: Control.DeepSeq.force - note: ! '''force'' is already exported from Relude' - name: Use 'force' from Relude - rhs: force -- warn: - lhs: (Control.DeepSeq.$!!) - note: Operator '($!!)' is already exported from Relude - name: Use '$!!' from Relude - rhs: ($!!) -- warn: - lhs: Control.Exception.Exception - note: ! '''Exception'' is already exported from Relude' - name: Use 'Exception' from Relude - rhs: Exception -- warn: - lhs: Control.Exception.SomeException - note: ! '''SomeException'' is already exported from Relude' - name: Use 'SomeException' from Relude - rhs: SomeException -- warn: - lhs: Control.Exception.toException - note: ! '''toException'' is already exported from Relude' - name: Use 'toException' from Relude - rhs: toException -- warn: - lhs: Control.Exception.fromException - note: ! '''fromException'' is already exported from Relude' - name: Use 'fromException' from Relude - rhs: fromException -- warn: - lhs: Control.Exception.displayException - note: ! '''displayException'' is already exported from Relude' - name: Use 'displayException' from Relude - rhs: displayException -- warn: - lhs: Data.Foldable.asum - note: ! '''asum'' is already exported from Relude' - name: Use 'asum' from Relude - rhs: asum -- warn: - lhs: Data.Foldable.find - note: ! '''find'' is already exported from Relude' - name: Use 'find' from Relude - rhs: find -- warn: - lhs: Data.Foldable.find - note: ! '''find'' is already exported from Relude' - name: Use 'find' from Relude - rhs: find -- warn: - lhs: Data.Foldable.fold - note: ! '''fold'' is already exported from Relude' - name: Use 'fold' from Relude - rhs: fold -- warn: - lhs: Data.Foldable.foldl' - note: ! '''foldl'''' is already exported from Relude' - name: Use 'foldl'' from Relude - rhs: foldl' -- warn: - lhs: Data.Foldable.foldrM - note: ! '''foldrM'' is already exported from Relude' - name: Use 'foldrM' from Relude - rhs: foldrM -- warn: - lhs: Data.Foldable.forM_ - note: ! '''forM_'' is already exported from Relude' - name: Use 'forM_' from Relude - rhs: forM_ -- warn: - lhs: Data.Foldable.for_ - note: ! '''for_'' is already exported from Relude' - name: Use 'for_' from Relude - rhs: for_ -- warn: - lhs: Data.Foldable.sequenceA_ - note: ! '''sequenceA_'' is already exported from Relude' - name: Use 'sequenceA_' from Relude - rhs: sequenceA_ -- warn: - lhs: Data.Foldable.toList - note: ! '''toList'' is already exported from Relude' - name: Use 'toList' from Relude - rhs: toList -- warn: - lhs: Data.Foldable.traverse_ - note: ! '''traverse_'' is already exported from Relude' - name: Use 'traverse_' from Relude - rhs: traverse_ -- warn: - lhs: Data.Traversable.forM - note: ! '''forM'' is already exported from Relude' - name: Use 'forM' from Relude - rhs: forM -- warn: - lhs: Data.Traversable.mapAccumL - note: ! '''mapAccumL'' is already exported from Relude' - name: Use 'mapAccumL' from Relude - rhs: mapAccumL -- warn: - lhs: Data.Traversable.mapAccumR - note: ! '''mapAccumR'' is already exported from Relude' - name: Use 'mapAccumR' from Relude - rhs: mapAccumR -- warn: - lhs: (Control.Arrow.&&&) - note: Operator '(&&&)' is already exported from Relude - name: Use '&&&' from Relude - rhs: (&&&) -- warn: - lhs: (Control.Category.>>>) - note: Operator '(>>>)' is already exported from Relude - name: Use '>>>' from Relude - rhs: (>>>) -- warn: - lhs: (Control.Category.<<<) - note: Operator '(<<<)' is already exported from Relude - name: Use '<<<' from Relude - rhs: (<<<) -- warn: - lhs: Data.Function.fix - note: ! '''fix'' is already exported from Relude' - name: Use 'fix' from Relude - rhs: fix -- warn: - lhs: Data.Function.on - note: ! '''on'' is already exported from Relude' - name: Use 'on' from Relude - rhs: 'on' -- warn: - lhs: Data.Bifunctor.Bifunctor - note: ! '''Bifunctor'' is already exported from Relude' - name: Use 'Bifunctor' from Relude - rhs: Bifunctor -- warn: - lhs: Data.Bifunctor.bimap - note: ! '''bimap'' is already exported from Relude' - name: Use 'bimap' from Relude - rhs: bimap -- warn: - lhs: Data.Bifunctor.first - note: ! '''first'' is already exported from Relude' - name: Use 'first' from Relude - rhs: first -- warn: - lhs: Data.Bifunctor.second - note: ! '''second'' is already exported from Relude' - name: Use 'second' from Relude - rhs: second -- warn: - lhs: Data.Functor.void - note: ! '''void'' is already exported from Relude' - name: Use 'void' from Relude - rhs: void -- warn: - lhs: (Data.Functor.$>) - note: Operator '($>)' is already exported from Relude - name: Use '$>' from Relude - rhs: ($>) -- warn: - lhs: (Data.Functor.<&>) - note: Operator '(<&>)' is already exported from Relude - name: Use '<&>' from Relude - rhs: (<&>) -- warn: - lhs: Data.Functor.Compose.Compose - note: ! '''Compose'' is already exported from Relude' - name: Use 'Compose' from Relude - rhs: Compose -- warn: - lhs: Data.Functor.Compose.getCompose - note: ! '''getCompose'' is already exported from Relude' - name: Use 'getCompose' from Relude - rhs: getCompose -- warn: - lhs: Data.Functor.Identity.Identity - note: ! '''Identity'' is already exported from Relude' - name: Use 'Identity' from Relude - rhs: Identity -- warn: - lhs: Data.Functor.Identity.runIdentity - note: ! '''runIdentity'' is already exported from Relude' - name: Use 'runIdentity' from Relude - rhs: runIdentity -- warn: - lhs: Control.Concurrent.MVar.MVar - note: ! '''MVar'' is already exported from Relude' - name: Use 'MVar' from Relude - rhs: MVar -- warn: - lhs: Control.Concurrent.MVar.newEmptyMVar - note: ! '''newEmptyMVar'' is already exported from Relude' - name: Use 'newEmptyMVar' from Relude - rhs: newEmptyMVar -- warn: - lhs: Control.Concurrent.MVar.newMVar - note: ! '''newMVar'' is already exported from Relude' - name: Use 'newMVar' from Relude - rhs: newMVar -- warn: - lhs: Control.Concurrent.MVar.putMVar - note: ! '''putMVar'' is already exported from Relude' - name: Use 'putMVar' from Relude - rhs: putMVar -- warn: - lhs: Control.Concurrent.MVar.readMVar - note: ! '''readMVar'' is already exported from Relude' - name: Use 'readMVar' from Relude - rhs: readMVar -- warn: - lhs: Control.Concurrent.MVar.swapMVar - note: ! '''swapMVar'' is already exported from Relude' - name: Use 'swapMVar' from Relude - rhs: swapMVar -- warn: - lhs: Control.Concurrent.MVar.takeMVar - note: ! '''takeMVar'' is already exported from Relude' - name: Use 'takeMVar' from Relude - rhs: takeMVar -- warn: - lhs: Control.Concurrent.MVar.tryPutMVar - note: ! '''tryPutMVar'' is already exported from Relude' - name: Use 'tryPutMVar' from Relude - rhs: tryPutMVar -- warn: - lhs: Control.Concurrent.MVar.tryReadMVar - note: ! '''tryReadMVar'' is already exported from Relude' - name: Use 'tryReadMVar' from Relude - rhs: tryReadMVar -- warn: - lhs: Control.Concurrent.MVar.tryTakeMVar - note: ! '''tryTakeMVar'' is already exported from Relude' - name: Use 'tryTakeMVar' from Relude - rhs: tryTakeMVar -- warn: - lhs: Control.Monad.STM.STM - note: ! '''STM'' is already exported from Relude' - name: Use 'STM' from Relude - rhs: STM -- warn: - lhs: Control.Monad.STM.atomically - note: ! '''atomically'' is already exported from Relude' - name: Use 'atomically' from Relude - rhs: atomically -- warn: - lhs: Control.Concurrent.STM.TVar.TVar - note: ! '''TVar'' is already exported from Relude' - name: Use 'TVar' from Relude - rhs: TVar -- warn: - lhs: Control.Concurrent.STM.TVar.newTVarIO - note: ! '''newTVarIO'' is already exported from Relude' - name: Use 'newTVarIO' from Relude - rhs: newTVarIO -- warn: - lhs: Control.Concurrent.STM.TVar.readTVarIO - note: ! '''readTVarIO'' is already exported from Relude' - name: Use 'readTVarIO' from Relude - rhs: readTVarIO -- warn: - lhs: Control.Concurrent.STM.TVar.modifyTVar' - note: ! '''modifyTVar'''' is already exported from Relude' - name: Use 'modifyTVar'' from Relude - rhs: modifyTVar' -- warn: - lhs: Control.Concurrent.STM.TVar.newTVar - note: ! '''newTVar'' is already exported from Relude' - name: Use 'newTVar' from Relude - rhs: newTVar -- warn: - lhs: Control.Concurrent.STM.TVar.readTVar - note: ! '''readTVar'' is already exported from Relude' - name: Use 'readTVar' from Relude - rhs: readTVar -- warn: - lhs: Control.Concurrent.STM.TVar.writeTVar - note: ! '''writeTVar'' is already exported from Relude' - name: Use 'writeTVar' from Relude - rhs: writeTVar -- warn: - lhs: Data.IORef.IORef - note: ! '''IORef'' is already exported from Relude' - name: Use 'IORef' from Relude - rhs: IORef -- warn: - lhs: Data.IORef.atomicModifyIORef - note: ! '''atomicModifyIORef'' is already exported from Relude' - name: Use 'atomicModifyIORef' from Relude - rhs: atomicModifyIORef -- warn: - lhs: Data.IORef.atomicModifyIORef' - note: ! '''atomicModifyIORef'''' is already exported from Relude' - name: Use 'atomicModifyIORef'' from Relude - rhs: atomicModifyIORef' -- warn: - lhs: Data.IORef.atomicWriteIORef - note: ! '''atomicWriteIORef'' is already exported from Relude' - name: Use 'atomicWriteIORef' from Relude - rhs: atomicWriteIORef -- warn: - lhs: Data.IORef.modifyIORef - note: ! '''modifyIORef'' is already exported from Relude' - name: Use 'modifyIORef' from Relude - rhs: modifyIORef -- warn: - lhs: Data.IORef.modifyIORef' - note: ! '''modifyIORef'''' is already exported from Relude' - name: Use 'modifyIORef'' from Relude - rhs: modifyIORef' -- warn: - lhs: Data.IORef.newIORef - note: ! '''newIORef'' is already exported from Relude' - name: Use 'newIORef' from Relude - rhs: newIORef -- warn: - lhs: Data.IORef.readIORef - note: ! '''readIORef'' is already exported from Relude' - name: Use 'readIORef' from Relude - rhs: readIORef -- warn: - lhs: Data.IORef.writeIORef - note: ! '''writeIORef'' is already exported from Relude' - name: Use 'writeIORef' from Relude - rhs: writeIORef -- warn: - lhs: Data.Text.IO.getLine - note: ! '''getLine'' is already exported from Relude' - name: Use 'getLine' from Relude - rhs: getLine -- warn: - lhs: Data.List.genericDrop - note: ! '''genericDrop'' is already exported from Relude' - name: Use 'genericDrop' from Relude - rhs: genericDrop -- warn: - lhs: Data.List.genericLength - note: ! '''genericLength'' is already exported from Relude' - name: Use 'genericLength' from Relude - rhs: genericLength -- warn: - lhs: Data.List.genericReplicate - note: ! '''genericReplicate'' is already exported from Relude' - name: Use 'genericReplicate' from Relude - rhs: genericReplicate -- warn: - lhs: Data.List.genericSplitAt - note: ! '''genericSplitAt'' is already exported from Relude' - name: Use 'genericSplitAt' from Relude - rhs: genericSplitAt -- warn: - lhs: Data.List.genericTake - note: ! '''genericTake'' is already exported from Relude' - name: Use 'genericTake' from Relude - rhs: genericTake -- warn: - lhs: Data.List.group - note: ! '''group'' is already exported from Relude' - name: Use 'group' from Relude - rhs: group -- warn: - lhs: Data.List.inits - note: ! '''inits'' is already exported from Relude' - name: Use 'inits' from Relude - rhs: inits -- warn: - lhs: Data.List.intercalate - note: ! '''intercalate'' is already exported from Relude' - name: Use 'intercalate' from Relude - rhs: intercalate -- warn: - lhs: Data.List.intersperse - note: ! '''intersperse'' is already exported from Relude' - name: Use 'intersperse' from Relude - rhs: intersperse -- warn: - lhs: Data.List.isPrefixOf - note: ! '''isPrefixOf'' is already exported from Relude' - name: Use 'isPrefixOf' from Relude - rhs: isPrefixOf -- warn: - lhs: Data.List.permutations - note: ! '''permutations'' is already exported from Relude' - name: Use 'permutations' from Relude - rhs: permutations -- warn: - lhs: Data.List.sort - note: ! '''sort'' is already exported from Relude' - name: Use 'sort' from Relude - rhs: sort -- warn: - lhs: Data.List.sortBy - note: ! '''sortBy'' is already exported from Relude' - name: Use 'sortBy' from Relude - rhs: sortBy -- warn: - lhs: Data.List.sortOn - note: ! '''sortOn'' is already exported from Relude' - name: Use 'sortOn' from Relude - rhs: sortOn -- warn: - lhs: Data.List.subsequences - note: ! '''subsequences'' is already exported from Relude' - name: Use 'subsequences' from Relude - rhs: subsequences -- warn: - lhs: Data.List.tails - note: ! '''tails'' is already exported from Relude' - name: Use 'tails' from Relude - rhs: tails -- warn: - lhs: Data.List.transpose - note: ! '''transpose'' is already exported from Relude' - name: Use 'transpose' from Relude - rhs: transpose -- warn: - lhs: Data.List.uncons - note: ! '''uncons'' is already exported from Relude' - name: Use 'uncons' from Relude - rhs: uncons -- warn: - lhs: Data.List.unfoldr - note: ! '''unfoldr'' is already exported from Relude' - name: Use 'unfoldr' from Relude - rhs: unfoldr -- warn: - lhs: Data.NonEmpty.NonEmpty - note: ! '''NonEmpty'' is already exported from Relude' - name: Use 'NonEmpty' from Relude - rhs: NonEmpty -- warn: - lhs: (Data.NonEmpty.:|) - note: Operator '(:|)' is already exported from Relude - name: Use ':|' from Relude - rhs: (:|) -- warn: - lhs: Data.NonEmpty.nonEmpty - note: ! '''nonEmpty'' is already exported from Relude' - name: Use 'nonEmpty' from Relude - rhs: nonEmpty -- warn: - lhs: Data.NonEmpty.head - note: ! '''head'' is already exported from Relude' - name: Use 'head' from Relude - rhs: head -- warn: - lhs: Data.NonEmpty.init - note: ! '''init'' is already exported from Relude' - name: Use 'init' from Relude - rhs: init -- warn: - lhs: Data.NonEmpty.last - note: ! '''last'' is already exported from Relude' - name: Use 'last' from Relude - rhs: last -- warn: - lhs: Data.NonEmpty.tail - note: ! '''tail'' is already exported from Relude' - name: Use 'tail' from Relude - rhs: tail -- warn: - lhs: GHC.Exts.sortWith - note: ! '''sortWith'' is already exported from Relude' - name: Use 'sortWith' from Relude - rhs: sortWith -- warn: - lhs: Control.Monad.Except.ExceptT - note: ! '''ExceptT'' is already exported from Relude' - name: Use 'ExceptT' from Relude - rhs: ExceptT -- warn: - lhs: Control.Monad.Except.runExceptT - note: ! '''runExceptT'' is already exported from Relude' - name: Use 'runExceptT' from Relude - rhs: runExceptT -- warn: - lhs: Control.Monad.Reader.MonadReader - note: ! '''MonadReader'' is already exported from Relude' - name: Use 'MonadReader' from Relude - rhs: MonadReader -- warn: - lhs: Control.Monad.Reader.Reader - note: ! '''Reader'' is already exported from Relude' - name: Use 'Reader' from Relude - rhs: Reader -- warn: - lhs: Control.Monad.Reader.ReaderT - note: ! '''ReaderT'' is already exported from Relude' - name: Use 'ReaderT' from Relude - rhs: ReaderT -- warn: - lhs: Control.Monad.Reader.runReaderT - note: ! '''runReaderT'' is already exported from Relude' - name: Use 'runReaderT' from Relude - rhs: runReaderT -- warn: - lhs: Control.Monad.Reader.ask - note: ! '''ask'' is already exported from Relude' - name: Use 'ask' from Relude - rhs: ask -- warn: - lhs: Control.Monad.Reader.asks - note: ! '''asks'' is already exported from Relude' - name: Use 'asks' from Relude - rhs: asks -- warn: - lhs: Control.Monad.Reader.local - note: ! '''local'' is already exported from Relude' - name: Use 'local' from Relude - rhs: local -- warn: - lhs: Control.Monad.Reader.reader - note: ! '''reader'' is already exported from Relude' - name: Use 'reader' from Relude - rhs: reader -- warn: - lhs: Control.Monad.Reader.runReader - note: ! '''runReader'' is already exported from Relude' - name: Use 'runReader' from Relude - rhs: runReader -- warn: - lhs: Control.Monad.Reader.withReader - note: ! '''withReader'' is already exported from Relude' - name: Use 'withReader' from Relude - rhs: withReader -- warn: - lhs: Control.Monad.Reader.withReaderT - note: ! '''withReaderT'' is already exported from Relude' - name: Use 'withReaderT' from Relude - rhs: withReaderT -- warn: - lhs: Control.Monad.State.Strict.MonadState - note: ! '''MonadState'' is already exported from Relude' - name: Use 'MonadState' from Relude - rhs: MonadState -- warn: - lhs: Control.Monad.State.Strict.State - note: ! '''State'' is already exported from Relude' - name: Use 'State' from Relude - rhs: State -- warn: - lhs: Control.Monad.State.Strict.StateT - note: ! '''StateT'' is already exported from Relude' - name: Use 'StateT' from Relude - rhs: StateT -- warn: - lhs: Control.Monad.State.Strict.runStateT - note: ! '''runStateT'' is already exported from Relude' - name: Use 'runStateT' from Relude - rhs: runStateT -- warn: - lhs: Control.Monad.State.Strict.evalState - note: ! '''evalState'' is already exported from Relude' - name: Use 'evalState' from Relude - rhs: evalState -- warn: - lhs: Control.Monad.State.Strict.evalStateT - note: ! '''evalStateT'' is already exported from Relude' - name: Use 'evalStateT' from Relude - rhs: evalStateT -- warn: - lhs: Control.Monad.State.Strict.execState - note: ! '''execState'' is already exported from Relude' - name: Use 'execState' from Relude - rhs: execState -- warn: - lhs: Control.Monad.State.Strict.execStateT - note: ! '''execStateT'' is already exported from Relude' - name: Use 'execStateT' from Relude - rhs: execStateT -- warn: - lhs: Control.Monad.State.Strict.get - note: ! '''get'' is already exported from Relude' - name: Use 'get' from Relude - rhs: get -- warn: - lhs: Control.Monad.State.Strict.gets - note: ! '''gets'' is already exported from Relude' - name: Use 'gets' from Relude - rhs: gets -- warn: - lhs: Control.Monad.State.Strict.modify - note: ! '''modify'' is already exported from Relude' - name: Use 'modify' from Relude - rhs: modify -- warn: - lhs: Control.Monad.State.Strict.modify' - note: ! '''modify'''' is already exported from Relude' - name: Use 'modify'' from Relude - rhs: modify' -- warn: - lhs: Control.Monad.State.Strict.put - note: ! '''put'' is already exported from Relude' - name: Use 'put' from Relude - rhs: put -- warn: - lhs: Control.Monad.State.Strict.runState - note: ! '''runState'' is already exported from Relude' - name: Use 'runState' from Relude - rhs: runState -- warn: - lhs: Control.Monad.State.Strict.state - note: ! '''state'' is already exported from Relude' - name: Use 'state' from Relude - rhs: state -- warn: - lhs: Control.Monad.State.Strict.withState - note: ! '''withState'' is already exported from Relude' - name: Use 'withState' from Relude - rhs: withState -- warn: - lhs: Control.Monad.Trans.MonadIO - note: ! '''MonadIO'' is already exported from Relude' - name: Use 'MonadIO' from Relude - rhs: MonadIO -- warn: - lhs: Control.Monad.Trans.MonadTrans - note: ! '''MonadTrans'' is already exported from Relude' - name: Use 'MonadTrans' from Relude - rhs: MonadTrans -- warn: - lhs: Control.Monad.Trans.lift - note: ! '''lift'' is already exported from Relude' - name: Use 'lift' from Relude - rhs: lift -- warn: - lhs: Control.Monad.Trans.liftIO - note: ! '''liftIO'' is already exported from Relude' - name: Use 'liftIO' from Relude - rhs: liftIO -- warn: - lhs: Control.Monad.Trans.Identity.IdentityT - note: ! '''IdentityT'' is already exported from Relude' - name: Use 'IdentityT' from Relude - rhs: IdentityT -- warn: - lhs: Control.Monad.Trans.Identity.runIdentityT - note: ! '''runIdentityT'' is already exported from Relude' - name: Use 'runIdentityT' from Relude - rhs: runIdentityT -- warn: - lhs: Control.Monad.Trans.Maybe.MaybeT - note: ! '''MaybeT'' is already exported from Relude' - name: Use 'MaybeT' from Relude - rhs: MaybeT -- warn: - lhs: Control.Monad.Trans.Maybe.maybeToExceptT - note: ! '''maybeToExceptT'' is already exported from Relude' - name: Use 'maybeToExceptT' from Relude - rhs: maybeToExceptT -- warn: - lhs: Control.Monad.Trans.Maybe.exceptToMaybeT - note: ! '''exceptToMaybeT'' is already exported from Relude' - name: Use 'exceptToMaybeT' from Relude - rhs: exceptToMaybeT -- warn: - lhs: Control.Monad.MonadPlus - note: ! '''MonadPlus'' is already exported from Relude' - name: Use 'MonadPlus' from Relude - rhs: MonadPlus -- warn: - lhs: Control.Monad.mzero - note: ! '''mzero'' is already exported from Relude' - name: Use 'mzero' from Relude - rhs: mzero -- warn: - lhs: Control.Monad.mplus - note: ! '''mplus'' is already exported from Relude' - name: Use 'mplus' from Relude - rhs: mplus -- warn: - lhs: Control.Monad.filterM - note: ! '''filterM'' is already exported from Relude' - name: Use 'filterM' from Relude - rhs: filterM -- warn: - lhs: Control.Monad.forever - note: ! '''forever'' is already exported from Relude' - name: Use 'forever' from Relude - rhs: forever -- warn: - lhs: Control.Monad.join - note: ! '''join'' is already exported from Relude' - name: Use 'join' from Relude - rhs: join -- warn: - lhs: Control.Monad.mapAndUnzipM - note: ! '''mapAndUnzipM'' is already exported from Relude' - name: Use 'mapAndUnzipM' from Relude - rhs: mapAndUnzipM -- warn: - lhs: Control.Monad.mfilter - note: ! '''mfilter'' is already exported from Relude' - name: Use 'mfilter' from Relude - rhs: mfilter -- warn: - lhs: Control.Monad.replicateM - note: ! '''replicateM'' is already exported from Relude' - name: Use 'replicateM' from Relude - rhs: replicateM -- warn: - lhs: Control.Monad.replicateM_ - note: ! '''replicateM_'' is already exported from Relude' - name: Use 'replicateM_' from Relude - rhs: replicateM_ -- warn: - lhs: Control.Monad.zipWithM - note: ! '''zipWithM'' is already exported from Relude' - name: Use 'zipWithM' from Relude - rhs: zipWithM -- warn: - lhs: Control.Monad.zipWithM_ - note: ! '''zipWithM_'' is already exported from Relude' - name: Use 'zipWithM_' from Relude - rhs: zipWithM_ -- warn: - lhs: (Control.Monad.<$!>) - note: Operator '(<$!>)' is already exported from Relude - name: Use '<$!>' from Relude - rhs: (<$!>) -- warn: - lhs: (Control.Monad.<=<) - note: Operator '(<=<)' is already exported from Relude - name: Use '<=<' from Relude - rhs: (<=<) -- warn: - lhs: (Control.Monad.=<<) - note: Operator '(=<<)' is already exported from Relude - name: Use '=<<' from Relude - rhs: (=<<) -- warn: - lhs: (Control.Monad.>=>) - note: Operator '(>=>)' is already exported from Relude - name: Use '>=>' from Relude - rhs: (>=>) -- warn: - lhs: Control.Monad.Fail.MonadFail - note: ! '''MonadFail'' is already exported from Relude' - name: Use 'MonadFail' from Relude - rhs: MonadFail -- warn: - lhs: Data.Maybe.catMaybes - note: ! '''catMaybes'' is already exported from Relude' - name: Use 'catMaybes' from Relude - rhs: catMaybes -- warn: - lhs: Data.Maybe.fromMaybe - note: ! '''fromMaybe'' is already exported from Relude' - name: Use 'fromMaybe' from Relude - rhs: fromMaybe -- warn: - lhs: Data.Maybe.isJust - note: ! '''isJust'' is already exported from Relude' - name: Use 'isJust' from Relude - rhs: isJust -- warn: - lhs: Data.Maybe.isNothing - note: ! '''isNothing'' is already exported from Relude' - name: Use 'isNothing' from Relude - rhs: isNothing -- warn: - lhs: Data.Maybe.listToMaybe - note: ! '''listToMaybe'' is already exported from Relude' - name: Use 'listToMaybe' from Relude - rhs: listToMaybe -- warn: - lhs: Data.Maybe.mapMaybe - note: ! '''mapMaybe'' is already exported from Relude' - name: Use 'mapMaybe' from Relude - rhs: mapMaybe -- warn: - lhs: Data.Maybe.maybeToList - note: ! '''maybeToList'' is already exported from Relude' - name: Use 'maybeToList' from Relude - rhs: maybeToList -- warn: - lhs: Data.Either.isLeft - note: ! '''isLeft'' is already exported from Relude' - name: Use 'isLeft' from Relude - rhs: isLeft -- warn: - lhs: Data.Either.isRight - note: ! '''isRight'' is already exported from Relude' - name: Use 'isRight' from Relude - rhs: isRight -- warn: - lhs: Data.Either.lefts - note: ! '''lefts'' is already exported from Relude' - name: Use 'lefts' from Relude - rhs: lefts -- warn: - lhs: Data.Either.partitionEithers - note: ! '''partitionEithers'' is already exported from Relude' - name: Use 'partitionEithers' from Relude - rhs: partitionEithers -- warn: - lhs: Data.Either.rights - note: ! '''rights'' is already exported from Relude' - name: Use 'rights' from Relude - rhs: rights -- warn: - lhs: Data.Monoid.All - note: ! '''All'' is already exported from Relude' - name: Use 'All' from Relude - rhs: All -- warn: - lhs: Data.Monoid.getAll - note: ! '''getAll'' is already exported from Relude' - name: Use 'getAll' from Relude - rhs: getAll -- warn: - lhs: Data.Monoid.Alt - note: ! '''Alt'' is already exported from Relude' - name: Use 'Alt' from Relude - rhs: Alt -- warn: - lhs: Data.Monoid.getAlt - note: ! '''getAlt'' is already exported from Relude' - name: Use 'getAlt' from Relude - rhs: getAlt -- warn: - lhs: Data.Monoid.Any - note: ! '''Any'' is already exported from Relude' - name: Use 'Any' from Relude - rhs: Any -- warn: - lhs: Data.Monoid.getAny - note: ! '''getAny'' is already exported from Relude' - name: Use 'getAny' from Relude - rhs: getAny -- warn: - lhs: Data.Monoid.Dual - note: ! '''Dual'' is already exported from Relude' - name: Use 'Dual' from Relude - rhs: Dual -- warn: - lhs: Data.Monoid.getDual - note: ! '''getDual'' is already exported from Relude' - name: Use 'getDual' from Relude - rhs: getDual -- warn: - lhs: Data.Monoid.Endo - note: ! '''Endo'' is already exported from Relude' - name: Use 'Endo' from Relude - rhs: Endo -- warn: - lhs: Data.Monoid.appEndo - note: ! '''appEndo'' is already exported from Relude' - name: Use 'appEndo' from Relude - rhs: appEndo -- warn: - lhs: Data.Monoid.First - note: ! '''First'' is already exported from Relude' - name: Use 'First' from Relude - rhs: First -- warn: - lhs: Data.Monoid.getFirst - note: ! '''getFirst'' is already exported from Relude' - name: Use 'getFirst' from Relude - rhs: getFirst -- warn: - lhs: Data.Monoid.Last - note: ! '''Last'' is already exported from Relude' - name: Use 'Last' from Relude - rhs: Last -- warn: - lhs: Data.Monoid.getLast - note: ! '''getLast'' is already exported from Relude' - name: Use 'getLast' from Relude - rhs: getLast -- warn: - lhs: Data.Monoid.Product - note: ! '''Product'' is already exported from Relude' - name: Use 'Product' from Relude - rhs: Product -- warn: - lhs: Data.Monoid.getProduct - note: ! '''getProduct'' is already exported from Relude' - name: Use 'getProduct' from Relude - rhs: getProduct -- warn: - lhs: Data.Monoid.Sum - note: ! '''Sum'' is already exported from Relude' - name: Use 'Sum' from Relude - rhs: Sum -- warn: - lhs: Data.Monoid.getSum - note: ! '''getSum'' is already exported from Relude' - name: Use 'getSum' from Relude - rhs: getSum -- warn: - lhs: Data.Semigroup.Option - note: ! '''Option'' is already exported from Relude' - name: Use 'Option' from Relude - rhs: Option -- warn: - lhs: Data.Semigroup.getOption - note: ! '''getOption'' is already exported from Relude' - name: Use 'getOption' from Relude - rhs: getOption -- warn: - lhs: Data.Semigroup.Semigroup - note: ! '''Semigroup'' is already exported from Relude' - name: Use 'Semigroup' from Relude - rhs: Semigroup -- warn: - lhs: Data.Semigroup.sconcat - note: ! '''sconcat'' is already exported from Relude' - name: Use 'sconcat' from Relude - rhs: sconcat -- warn: - lhs: Data.Semigroup.stimes - note: ! '''stimes'' is already exported from Relude' - name: Use 'stimes' from Relude - rhs: stimes -- warn: - lhs: (Data.Semigroup.<>) - note: Operator '(<>)' is already exported from Relude - name: Use '<>' from Relude - rhs: (<>) -- warn: - lhs: Data.Semigroup.WrappedMonoid - note: ! '''WrappedMonoid'' is already exported from Relude' - name: Use 'WrappedMonoid' from Relude - rhs: WrappedMonoid -- warn: - lhs: Data.Semigroup.cycle1 - note: ! '''cycle1'' is already exported from Relude' - name: Use 'cycle1' from Relude - rhs: cycle1 -- warn: - lhs: Data.Semigroup.mtimesDefault - note: ! '''mtimesDefault'' is already exported from Relude' - name: Use 'mtimesDefault' from Relude - rhs: mtimesDefault -- warn: - lhs: Data.Semigroup.stimesIdempotent - note: ! '''stimesIdempotent'' is already exported from Relude' - name: Use 'stimesIdempotent' from Relude - rhs: stimesIdempotent -- warn: - lhs: Data.Semigroup.stimesIdempotentMonoid - note: ! '''stimesIdempotentMonoid'' is already exported from Relude' - name: Use 'stimesIdempotentMonoid' from Relude - rhs: stimesIdempotentMonoid -- warn: - lhs: Data.Semigroup.stimesMonoid - note: ! '''stimesMonoid'' is already exported from Relude' - name: Use 'stimesMonoid' from Relude - rhs: stimesMonoid -- warn: - lhs: Data.ByteString.ByteString - note: ! '''ByteString'' is already exported from Relude' - name: Use 'ByteString' from Relude - rhs: ByteString -- warn: - lhs: Data.String.IsString - note: ! '''IsString'' is already exported from Relude' - name: Use 'IsString' from Relude - rhs: IsString -- warn: - lhs: Data.String.fromString - note: ! '''fromString'' is already exported from Relude' - name: Use 'fromString' from Relude - rhs: fromString -- warn: - lhs: Data.Text.Text - note: ! '''Text'' is already exported from Relude' - name: Use 'Text' from Relude - rhs: Text -- warn: - lhs: Data.Text.lines - note: ! '''lines'' is already exported from Relude' - name: Use 'lines' from Relude - rhs: lines -- warn: - lhs: Data.Text.unlines - note: ! '''unlines'' is already exported from Relude' - name: Use 'unlines' from Relude - rhs: unlines -- warn: - lhs: Data.Text.words - note: ! '''words'' is already exported from Relude' - name: Use 'words' from Relude - rhs: words -- warn: - lhs: Data.Text.unwords - note: ! '''unwords'' is already exported from Relude' - name: Use 'unwords' from Relude - rhs: unwords -- warn: - lhs: Data.Text.Encoding.decodeUtf8' - note: ! '''decodeUtf8'''' is already exported from Relude' - name: Use 'decodeUtf8'' from Relude - rhs: decodeUtf8' -- warn: - lhs: Data.Text.Encoding.decodeUtf8With - note: ! '''decodeUtf8With'' is already exported from Relude' - name: Use 'decodeUtf8With' from Relude - rhs: decodeUtf8With -- warn: - lhs: Data.Text.Encoding.Error.OnDecodeError - note: ! '''OnDecodeError'' is already exported from Relude' - name: Use 'OnDecodeError' from Relude - rhs: OnDecodeError -- warn: - lhs: Data.Text.Encoding.Error.OnError - note: ! '''OnError'' is already exported from Relude' - name: Use 'OnError' from Relude - rhs: OnError -- warn: - lhs: Data.Text.Encoding.Error.UnicodeException - note: ! '''UnicodeException'' is already exported from Relude' - name: Use 'UnicodeException' from Relude - rhs: UnicodeException -- warn: - lhs: Data.Text.Encoding.Error.lenientDecode - note: ! '''lenientDecode'' is already exported from Relude' - name: Use 'lenientDecode' from Relude - rhs: lenientDecode -- warn: - lhs: Data.Text.Encoding.Error.strictDecode - note: ! '''strictDecode'' is already exported from Relude' - name: Use 'strictDecode' from Relude - rhs: strictDecode -- warn: - lhs: Text.Read.Read - note: ! '''Read'' is already exported from Relude' - name: Use 'Read' from Relude - rhs: Read -- warn: - lhs: Text.Read.readMaybe - note: ! '''readMaybe'' is already exported from Relude' - name: Use 'readMaybe' from Relude - rhs: readMaybe -- warn: - lhs: (liftIO (newEmptyMVar )) - note: If you import 'newEmptyMVar' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: newEmptyMVar -- warn: - lhs: (liftIO (newMVar x)) - note: If you import 'newMVar' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: newMVar -- warn: - lhs: (liftIO (putMVar x y)) - note: If you import 'putMVar' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: putMVar -- warn: - lhs: (liftIO (readMVar x)) - note: If you import 'readMVar' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: readMVar -- warn: - lhs: (liftIO (swapMVar x y)) - note: If you import 'swapMVar' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: swapMVar -- warn: - lhs: (liftIO (takeMVar x)) - note: If you import 'takeMVar' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: takeMVar -- warn: - lhs: (liftIO (tryPutMVar x y)) - note: If you import 'tryPutMVar' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: tryPutMVar -- warn: - lhs: (liftIO (tryReadMVar x)) - note: If you import 'tryReadMVar' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: tryReadMVar -- warn: - lhs: (liftIO (tryTakeMVar x)) - note: If you import 'tryTakeMVar' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: tryTakeMVar -- warn: - lhs: (liftIO (atomically x)) - note: If you import 'atomically' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: atomically -- warn: - lhs: (liftIO (newTVarIO x)) - note: If you import 'newTVarIO' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: newTVarIO -- warn: - lhs: (liftIO (readTVarIO x)) - note: If you import 'readTVarIO' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: readTVarIO -- warn: - lhs: (liftIO (exitWith x)) - note: If you import 'exitWith' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: exitWith -- warn: - lhs: (liftIO (exitFailure )) - note: If you import 'exitFailure' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: exitFailure -- warn: - lhs: (liftIO (exitSuccess )) - note: If you import 'exitSuccess' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: exitSuccess -- warn: - lhs: (liftIO (die x)) - note: If you import 'die' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: die -- warn: - lhs: (liftIO (readFile x)) - note: If you import 'readFile' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: readFile -- warn: - lhs: (liftIO (writeFile x y)) - note: If you import 'writeFile' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: writeFile -- warn: - lhs: (liftIO (appendFile x y)) - note: If you import 'appendFile' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: appendFile -- warn: - lhs: (liftIO (readFileText x)) - note: If you import 'readFileText' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: readFileText -- warn: - lhs: (liftIO (writeFileText x y)) - note: If you import 'writeFileText' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: writeFileText -- warn: - lhs: (liftIO (appendFileText x y)) - note: If you import 'appendFileText' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: appendFileText -- warn: - lhs: (liftIO (readFileLText x)) - note: If you import 'readFileLText' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: readFileLText -- warn: - lhs: (liftIO (writeFileLText x y)) - note: If you import 'writeFileLText' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: writeFileLText -- warn: - lhs: (liftIO (appendFileLText x y)) - note: If you import 'appendFileLText' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: appendFileLText -- warn: - lhs: (liftIO (readFileBS x)) - note: If you import 'readFileBS' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: readFileBS -- warn: - lhs: (liftIO (writeFileBS x y)) - note: If you import 'writeFileBS' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: writeFileBS -- warn: - lhs: (liftIO (appendFileBS x y)) - note: If you import 'appendFileBS' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: appendFileBS -- warn: - lhs: (liftIO (readFileLBS x)) - note: If you import 'readFileLBS' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: readFileLBS -- warn: - lhs: (liftIO (writeFileLBS x y)) - note: If you import 'writeFileLBS' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: writeFileLBS -- warn: - lhs: (liftIO (appendFileLBS x y)) - note: If you import 'appendFileLBS' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: appendFileLBS -- warn: - lhs: (liftIO (newIORef x)) - note: If you import 'newIORef' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: newIORef -- warn: - lhs: (liftIO (readIORef x)) - note: If you import 'readIORef' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: readIORef -- warn: - lhs: (liftIO (writeIORef x y)) - note: If you import 'writeIORef' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: writeIORef -- warn: - lhs: (liftIO (modifyIORef x y)) - note: If you import 'modifyIORef' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: modifyIORef -- warn: - lhs: (liftIO (modifyIORef' x y)) - note: If you import 'modifyIORef'' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: modifyIORef' -- warn: - lhs: (liftIO (atomicModifyIORef x y)) - note: If you import 'atomicModifyIORef' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: atomicModifyIORef -- warn: - lhs: (liftIO (atomicModifyIORef' x y)) - note: If you import 'atomicModifyIORef'' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: atomicModifyIORef' -- warn: - lhs: (liftIO (atomicWriteIORef x y)) - note: If you import 'atomicWriteIORef' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: atomicWriteIORef -- warn: - lhs: (liftIO (getLine )) - note: If you import 'getLine' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: getLine -- warn: - lhs: (liftIO (print x)) - note: If you import 'print' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: print -- warn: - lhs: (liftIO (putStr x)) - note: If you import 'putStr' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: putStr -- warn: - lhs: (liftIO (putStrLn x)) - note: If you import 'putStrLn' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: putStrLn -- warn: - lhs: (liftIO (putText x)) - note: If you import 'putText' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: putText -- warn: - lhs: (liftIO (putTextLn x)) - note: If you import 'putTextLn' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: putTextLn -- warn: - lhs: (liftIO (putLText x)) - note: If you import 'putLText' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: putLText -- warn: - lhs: (liftIO (putLTextLn x)) - note: If you import 'putLTextLn' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: putLTextLn -- warn: - lhs: (liftIO (putBS x)) - note: If you import 'putBS' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: putBS -- warn: - lhs: (liftIO (putBSLn x)) - note: If you import 'putBSLn' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: putBSLn -- warn: - lhs: (liftIO (putLBS x)) - note: If you import 'putLBS' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: putLBS -- warn: - lhs: (liftIO (putLBSLn x)) - note: If you import 'putLBSLn' from Relude, it's already lifted - name: ! '''liftIO'' is not needed' - rhs: putLBSLn diff --git a/package.yaml b/package.yaml index aba0679..740b68b 100644 --- a/package.yaml +++ b/package.yaml @@ -33,7 +33,7 @@ library: - template-haskell - text - uuid - - wreq + - safe-exceptions-checked source-dirs: src exposed-modules: - Aws.Lambda.Runtime @@ -64,6 +64,8 @@ default-extensions: - ScopedTypeVariables - DeriveGeneric - TypeApplications + - FlexibleContexts + - DeriveAnyClass ghc-options: - -Wall diff --git a/src/Aws/Lambda/API.hs b/src/Aws/Lambda/API.hs new file mode 100644 index 0000000..d1ba4bb --- /dev/null +++ b/src/Aws/Lambda/API.hs @@ -0,0 +1,21 @@ +module Aws.Lambda.API + () where + +import qualified Network.HTTP.Client as Http +import qualified Data.ByteString as ByteString +import qualified Data.ByteString.Lazy as LazyByteString + +type LByteString = LazyByteString.ByteString + +getApiData :: String -> IO (Wreq.Response LByteString) +getApiData endpoint = + keepRetrying (Wreq.getWith opts $ nextInvocationEndpoint endpoint) + where + opts = Wreq.defaults + & Wreq.manager .~ Left (Http.defaultManagerSettings { Http.managerResponseTimeout = Http.responseTimeoutNone }) + keepRetrying :: IO (Wreq.Response LByteString) -> IO (Wreq.Response LByteString) + keepRetrying f = do + result <- (liftIO $ try f) :: IO (Either IOException (Wreq.Response LByteString)) + case result of + Right x -> return x + _ -> keepRetrying f diff --git a/src/Aws/Lambda/Runtime.hs b/src/Aws/Lambda/Runtime.hs index 5712278..18073ee 100644 --- a/src/Aws/Lambda/Runtime.hs +++ b/src/Aws/Lambda/Runtime.hs @@ -1,15 +1,12 @@ module Aws.Lambda.Runtime where -import Control.Exception (Exception, IOException, try) -import Control.Monad.Except (ExceptT, catchError, throwError) import Data.Aeson import System.Exit (ExitCode (..)) -import qualified Data.Text as Text -import Data.Text (Text) +import qualified Data.String as String +import Data.String (String) import GHC.Generics import qualified Data.ByteString.Lazy as LazyByteString import qualified Data.ByteString as ByteString -import Control.Monad.Trans import Text.Read (readMaybe) import qualified Data.Text.Encoding as Encoding import Control.Monad @@ -19,162 +16,48 @@ import Data.Monoid ((<>)) import qualified Data.CaseInsensitive as CI import Lens.Micro.Platform hiding ((.=)) -import qualified Network.Wreq as Wreq import qualified Network.HTTP.Client as Http import qualified System.Environment as Environment import qualified System.Process as Process import qualified Data.UUID as UUID import qualified Data.UUID.V4 as UUID import System.IO (hFlush, stdout) +import Control.Exception.Safe.Checked + +import qualified Aws.Lambda.Runtime.Error as RuntimeError +import qualified Aws.Lambda.Runtime.Environment as Environment type LByteString = LazyByteString.ByteString type ByteString = ByteString.ByteString -type App a = - ExceptT RuntimeError IO a - - -data RuntimeError - = EnvironmentVariableNotSet Text - | ApiConnectionError - | ApiHeaderNotSet Text - | ParseError Text Text - | InvocationError Text - deriving (Show) -instance Exception RuntimeError - -instance ToJSON RuntimeError where - toJSON (EnvironmentVariableNotSet msg) = object - [ "errorType" .= ("EnvironmentVariableNotSet" :: Text) - , "errorMessage" .= msg - ] - - toJSON ApiConnectionError = object - [ "errorType" .= ("ApiConnectionError" :: Text) - , "errorMessage" .= ("Could not connect to API to retrieve AWS Lambda parameters" :: Text) - ] - - toJSON (ApiHeaderNotSet headerName) = object - [ "errorType" .= ("ApiHeaderNotSet" :: Text) - , "errorMessage" .= headerName - ] - - toJSON (ParseError objectBeingParsed value) = object - [ "errorType" .= ("ParseError" :: Text) - , "errorMessage" .= ("Parse error for " <> objectBeingParsed <> ", could not parse value '" <> value <> "'") - ] - - -- We return the user error as it is - toJSON (InvocationError err) = toJSON err - - - -data Context = Context - { memoryLimitInMb :: !Int - , functionName :: !Text - , functionVersion :: !Text - , invokedFunctionArn :: !Text - , awsRequestId :: !Text - , xrayTraceId :: !Text - , logStreamName :: !Text - , logGroupName :: !Text - , deadline :: !Int - } deriving (Generic) -instance FromJSON Context -instance ToJSON Context - - -newtype LambdaResult = - LambdaResult Text - - -awsLambdaVersion :: String -awsLambdaVersion = "2018-06-01" - -nextInvocationEndpoint :: Text -> String -nextInvocationEndpoint endpoint = - "http://" <> Text.unpack endpoint <> "/"<> awsLambdaVersion <>"/runtime/invocation/next" +-- extractHeader :: Wreq.Response LByteString -> String -> String +-- extractHeader apiData header = +-- Encoding.decodeUtf8 (apiData ^. Wreq.responseHeader (CI.mk $ Encoding.encodeUtf8 header)) -responseEndpoint :: Text -> Text -> String -responseEndpoint lambdaApi requestId = - "http://"<> Text.unpack lambdaApi <> "/" <> awsLambdaVersion <> "/runtime/invocation/"<> Text.unpack requestId <> "/response" +-- extractIntHeader :: Throws RuntimeError.Value => Wreq.Response LByteString -> String -> IO Int +-- extractIntHeader apiData headerName = do +-- let header = extractHeader apiData headerName +-- case readMaybe header of +-- Just value -> pure value +-- Nothing -> throw (ParseError "deadline" header) -invocationErrorEndpoint :: Text -> Text -> String -invocationErrorEndpoint lambdaApi requestId = - "http://"<> Text.unpack lambdaApi <> "/" <> awsLambdaVersion <> "/runtime/invocation/"<> Text.unpack requestId <> "/error" +-- extractBody :: Wreq.Response LByteString -> String +-- extractBody apiData = +-- Encoding.decodeUtf8 $ LazyByteString.toStrict (apiData ^. Wreq.responseBody) -runtimeInitErrorEndpoint :: Text -> String -runtimeInitErrorEndpoint lambdaApi = - "http://"<> Text.unpack lambdaApi <> "/" <> awsLambdaVersion <> "/runtime/init/error" - -readEnvironmentVariable :: Text -> App Text -readEnvironmentVariable envVar = do - v <- lift (Environment.lookupEnv $ Text.unpack envVar) - case v of - Nothing -> throwError (EnvironmentVariableNotSet envVar) - Just value -> pure (Text.pack value) - - -readFunctionMemory :: App Int -readFunctionMemory = do - let envVar = "AWS_LAMBDA_FUNCTION_MEMORY_SIZE" - let parseMemory txt = readMaybe (Text.unpack txt) - memoryValue <- readEnvironmentVariable envVar - case parseMemory memoryValue of - Just value -> pure value - Nothing -> throwError (ParseError envVar memoryValue) - - -getApiData :: Text -> App (Wreq.Response LByteString) -getApiData endpoint = - keepRetrying (Wreq.getWith opts $ nextInvocationEndpoint endpoint) - where - opts = Wreq.defaults - & Wreq.manager .~ Left (Http.defaultManagerSettings { Http.managerResponseTimeout = Http.responseTimeoutNone }) - keepRetrying :: IO (Wreq.Response LByteString) -> App (Wreq.Response LByteString) - keepRetrying f = do - result <- (liftIO $ try f) :: App (Either IOException (Wreq.Response LByteString)) - case result of - Right x -> return x - _ -> keepRetrying f - - -extractHeader :: Wreq.Response LByteString -> Text -> Text -extractHeader apiData header = - Encoding.decodeUtf8 (apiData ^. Wreq.responseHeader (CI.mk $ Encoding.encodeUtf8 header)) - - -extractIntHeader :: Wreq.Response LByteString -> Text -> App Int -extractIntHeader apiData headerName = do - let header = extractHeader apiData headerName - case readMaybe $ Text.unpack header of - Nothing -> throwError (ParseError "deadline" header) - Just value -> pure value - - -extractBody :: Wreq.Response LByteString -> Text -extractBody apiData = - Encoding.decodeUtf8 $ LazyByteString.toStrict (apiData ^. Wreq.responseBody) - - -propagateXRayTrace :: Text -> App () -propagateXRayTrace xrayTraceId = - liftIO $ Environment.setEnv "_X_AMZN_TRACE_ID" $ Text.unpack xrayTraceId - - -initializeContext :: Wreq.Response LByteString -> App Context +initializeContext :: Wreq.Response LByteString -> IO Context initializeContext apiData = do - functionName <- readEnvironmentVariable "AWS_LAMBDA_FUNCTION_NAME" - version <- readEnvironmentVariable "AWS_LAMBDA_FUNCTION_VERSION" - logStream <- readEnvironmentVariable "AWS_LAMBDA_LOG_STREAM_NAME" - logGroup <- readEnvironmentVariable "AWS_LAMBDA_LOG_GROUP_NAME" - memoryLimitInMb <- readFunctionMemory + functionName <- Environment.functionName + version <- Environment.functionVersion + logStream <- Environment.logStreamName + logGroup <- Environment.logGroup + memoryLimitInMb <- Environment.functionMemory deadline <- extractIntHeader apiData "Lambda-Runtime-Deadline-Ms" let xrayTraceId = extractHeader apiData "Lambda-Runtime-Trace-Id" let awsRequestId = extractHeader apiData "Lambda-Runtime-Aws-Request-Id" @@ -193,14 +76,14 @@ initializeContext apiData = do } -getFunctionResult :: UUID.UUID -> Text -> App (Maybe Text) +getFunctionResult :: UUID.UUID -> String -> IO (Maybe String) getFunctionResult u stdOut = do - let out = Text.lines stdOut + let out = String.lines stdOut out & takeWhile (/= uuid) & mapM_ ( \t -> do - liftIO $ putStrLn $ Text.unpack t + liftIO $ putStrLn t liftIO $ hFlush stdout) out @@ -209,50 +92,52 @@ getFunctionResult u stdOut = do & listToMaybe & return where - uuid = Text.pack $ UUID.toString u + uuid = String.pack $ UUID.toString u -invoke :: Text -> Context -> App LambdaResult +invoke :: String -> Context -> IO LambdaResult invoke event context = do - handlerName <- readEnvironmentVariable "_HANDLER" - runningDirectory <- readEnvironmentVariable "LAMBDA_TASK_ROOT" + handlerName <- Environment.handlerName + runningDirectory <- Environment.taskRoot let contextJSON = Encoding.decodeUtf8 $ LazyByteString.toStrict $ encode context uuid <- liftIO UUID.nextRandom - out <- liftIO $ Process.readProcessWithExitCode (Text.unpack runningDirectory <> "/haskell_lambda") - [ "--eventObject", Text.unpack event - , "--contextObject", Text.unpack contextJSON - , "--functionHandler", Text.unpack handlerName + out <- liftIO $ Process.readProcessWithExitCode (String.unpack runningDirectory <> "/haskell_lambda") + [ "--eventObject", String.unpack event + , "--contextObject", String.unpack contextJSON + , "--functionHandler", String.unpack handlerName , "--executionUuid", UUID.toString uuid ] "" case out of (ExitSuccess, stdOut, _) -> do - res <- getFunctionResult uuid (Text.pack stdOut) + res <- getFunctionResult uuid (String.pack stdOut) case res of - Nothing -> throwError (ParseError "parsing result" $ Text.pack stdOut) + Nothing -> throwError (ParseError "parsing result" $ String.pack stdOut) Just value -> pure (LambdaResult value) (_, stdOut, stdErr) -> if stdErr /= "" - then throwError (InvocationError $ Text.pack stdErr) + then throwError (InvocationError $ String.pack stdErr) else do - res <- getFunctionResult uuid (Text.pack stdOut) + res <- getFunctionResult uuid (String.pack stdOut) case res of - Nothing -> throwError (ParseError "parsing error" $ Text.pack stdOut) + Nothing -> throwError (ParseError "parsing error" $ String.pack stdOut) Just value -> throwError (InvocationError value) +newtype LambdaResult = + LambdaResult Text -publishResult :: Context -> Text -> LambdaResult -> App () +publishResult :: Context -> String -> LambdaResult -> IO () publishResult Context {..} lambdaApi (LambdaResult result) = void $ liftIO $ Wreq.post (responseEndpoint lambdaApi awsRequestId) (Encoding.encodeUtf8 result) -invokeAndPublish :: Context -> Text -> Text -> App () +invokeAndPublish :: Context -> String -> String -> IO () invokeAndPublish ctx event lambdaApiEndpoint = do res <- invoke event ctx publishResult ctx lambdaApiEndpoint res -publishError :: Context -> Text -> RuntimeError -> App () +publishError :: Context -> String -> RuntimeError -> IO () publishError Context {..} lambdaApiEndpoint (InvocationError err) = void (liftIO $ Wreq.post (invocationErrorEndpoint lambdaApiEndpoint awsRequestId) (Encoding.encodeUtf8 err)) @@ -263,7 +148,7 @@ publishError Context {..} lambdaApiEndpoint err = void (liftIO $ Wreq.post (runtimeInitErrorEndpoint lambdaApiEndpoint) (toJSON err)) -lambdaRunner :: App () +lambdaRunner :: IO () lambdaRunner = do lambdaApiEndpoint <- readEnvironmentVariable "AWS_LAMBDA_RUNTIME_API" apiData <- getApiData lambdaApiEndpoint diff --git a/src/Aws/Lambda/Runtime/API/Endpoints.hs b/src/Aws/Lambda/Runtime/API/Endpoints.hs new file mode 100644 index 0000000..784e25a --- /dev/null +++ b/src/Aws/Lambda/Runtime/API/Endpoints.hs @@ -0,0 +1,60 @@ +module Aws.Lambda.Runtime.API.Endpoints + ( response + , invocationError + , runtimeInitError + , nextInvocation + ) where + +import qualified Aws.Lambda.Runtime.API.Version as Version + +newtype Endpoint = + Endpoint String + deriving (Show) + +-- | Endpoint that provides the ID of the next invocation +nextInvocation :: String -> Endpoint +nextInvocation lambdaApi = + Endpoint $ concat + [ "http://" + , lambdaApi + , "/" + , Version.value + , "/runtime/invocation/next" + ] + +-- | Where the response of the Lambda gets published +response :: String -> String -> Endpoint +response lambdaApi requestId = + Endpoint $ concat + [ "http://" + , lambdaApi + , "/" + , Version.value + , "/runtime/invocation/" + , requestId + , "/response" + ] + +-- | Invocation (runtime) errors should be published here +invocationError :: String -> String -> Endpoint +invocationError lambdaApi requestId = + Endpoint $ concat + [ "http://" + , lambdaApi + , "/" + , Version.value + , "/runtime/invocation/" + , requestId + , "/error" + ] + +-- | Runtime initialization errors should go here +runtimeInitError :: String -> Endpoint +runtimeInitError lambdaApi = + Endpoint $ concat + [ "http://" + , lambdaApi + , "/" + , Version.value + , "/runtime/init/error" + ] diff --git a/src/Aws/Lambda/Runtime/API/Version.hs b/src/Aws/Lambda/Runtime/API/Version.hs new file mode 100644 index 0000000..c1f2060 --- /dev/null +++ b/src/Aws/Lambda/Runtime/API/Version.hs @@ -0,0 +1,7 @@ +module Aws.Lambda.Runtime.API.Version + ( value + ) where + +-- | Version of the AWS Lambda runtime REST API +value :: String +value = "2018-06-01" diff --git a/src/Aws/Lambda/Runtime/ApiInfo.hs b/src/Aws/Lambda/Runtime/ApiInfo.hs new file mode 100644 index 0000000..31006b5 --- /dev/null +++ b/src/Aws/Lambda/Runtime/ApiInfo.hs @@ -0,0 +1,56 @@ +module Aws.Lambda.Runtime.ApiInfo + ( Event(..) + , fetchEvent + ) where + +import qualified Network.HTTP.Client as Http +import qualified Data.ByteString.Lazy as Lazy + +import qualified Aws.Lambda.Runtime.Api.Endpoints as Endpoints + +data Event = Event + { deadlineMs :: !Int + , traceId :: !String + , awsRequestId :: !String + , invokedFunctionArn :: !String + , event :: !Lazy.ByteString + } + +fetchEvent :: Http.Manager -> String -> IO Event +fetchEvent manager lambdaApi = do + response <- fetchApiData manager lambdaApi + body <- Http.responseBody response + headers <- Http.responseHeaders response + foldM reduceEvent (Event { event = body }) headers + +fetchApiData :: Http.Manager -> String -> IO (Response Lazy.ByteString) +fetchApiData manager lambdaApi = do + request <- Http.parseRequest (Endpoints.nextInvocation lambdaApi) + Http.httpLbs request manager + +reduceEvent :: Event -> Http.Header -> IO Event +reduceEvent event header = + case header of + ("Lambda-Runtime-Deadline-Ms", value) -> + case Read.readMaybe value of + Just ms -> pure event { deadlineMs = ms } + Nothing -> throw (Error.Parsing "deadlineMs" value) + + ("Lambda-Runtime-Trace-Id", value) -> + event { traceId = decodeUtf8 value } + + ("Lambda-Runtime-Aws-Request-Id", value) -> + event { awsRequest = decodeUtf8 value } + + ("Lambda-Runtime-Invoked-Function-Arn", value) -> + event { invokedFunctionArn = decodeUtf8 value } + + _ -> + event + +httpManagerSettings :: Http.ManagerSettings +httpManagerSettings = + -- We set the timeout to none, as AWS Lambda freezes the containers. + Http.defaultManagerSettings + { Http.managerResponseTimeout = Http.responseTimeoutNone + } diff --git a/src/Aws/Lambda/Runtime/Context.hs b/src/Aws/Lambda/Runtime/Context.hs new file mode 100644 index 0000000..a204208 --- /dev/null +++ b/src/Aws/Lambda/Runtime/Context.hs @@ -0,0 +1,19 @@ +module Aws.Lambda.Runtime.Context + ( Context(..) + ) where + +import Data.Aeson (FromJSON(..), ToJSON(..)) +import Data.Text (Text) +import GHC.Generics (Generic) + +data Context = Context + { memoryLimitInMb :: !Int + , functionName :: !Text + , functionVersion :: !Text + , invokedFunctionArn :: !Text + , awsRequestId :: !Text + , xrayTraceId :: !Text + , logStreamName :: !Text + , logGroupName :: !Text + , deadline :: !Int + } deriving (Generic, FromJSON, ToJSON) diff --git a/src/Aws/Lambda/Runtime/Environment.hs b/src/Aws/Lambda/Runtime/Environment.hs new file mode 100644 index 0000000..c1457d2 --- /dev/null +++ b/src/Aws/Lambda/Runtime/Environment.hs @@ -0,0 +1,61 @@ +module Aws.Lambda.Runtime.Environment + ( functionMemory + , apiEndpoint + , handlerName + , taskRoot + , functionName + , functionVersion + , logStreamName + , logGroupName + ) where + +import qualified Aws.Lambda.Runtime.Error as Error +import Control.Exception.Safe.Checked +import qualified System.Environment as Environment +import qualified Text.Read as Read + +logGroupName :: Throws Error.EnvironmentVariableNotSet => IO String +logGroupName = + readEnvironmentVariable "AWS_LAMBDA_LOG_GROUP_NAME" + +logStreamName :: Throws Error.EnvironmentVariableNotSet => IO String +logStreamName = + readEnvironmentVariable "AWS_LAMBDA_LOG_STREAM_NAME" + +functionVersion :: Throws Error.EnvironmentVariableNotSet => IO String +functionVersion = + readEnvironmentVariable "AWS_LAMBDA_FUNCTION_VERSION" + +functionName :: Throws Error.EnvironmentVariableNotSet => IO String +functionName = + readEnvironmentVariable "AWS_LAMBDA_FUNCTION_NAME" + +setXRayTrace :: String -> IO () +setXRayTrace = Environment.setEnv "_X_AMZN_TRACE_ID" + +taskRoot :: Throws Error.EnvironmentVariableNotSet => IO String +taskRoot = + readEnvironmentVariable "LAMBDA_TASK_ROOT" + +handlerName :: Throws Error.EnvironmentVariableNotSet => IO String +handlerName = + readEnvironmentVariable "_HANDLER" + +apiEndpoint :: Throws Error.EnvironmentVariableNotSet => IO String +apiEndpoint = + readEnvironmentVariable "AWS_LAMBDA_RUNTIME_API" + +functionMemory :: Throws Error.Parsing => Throws Error.EnvironmentVariableNotSet => IO Int +functionMemory = do + let envVar = "AWS_LAMBDA_FUNCTION_MEMORY_SIZE" + memoryValue <- readEnvironmentVariable envVar + case Read.readMaybe memoryValue of + Just value -> pure value + Nothing -> throw (Error.Parsing envVar memoryValue) + +readEnvironmentVariable :: Throws Error.EnvironmentVariableNotSet => String -> IO String +readEnvironmentVariable envVar = do + v <- Environment.lookupEnv envVar + case v of + Just value -> pure value + Nothing -> throw (Error.EnvironmentVariableNotSet envVar) diff --git a/src/Aws/Lambda/Runtime/Error.hs b/src/Aws/Lambda/Runtime/Error.hs new file mode 100644 index 0000000..2a12f53 --- /dev/null +++ b/src/Aws/Lambda/Runtime/Error.hs @@ -0,0 +1,59 @@ +module Aws.Lambda.Runtime.Error + ( EnvironmentVariableNotSet(..) + , ApiConnection(..) + , ApiHeaderNotSet(..) + , Parsing(..) + , InvocationError(..) + ) where + +import Data.Aeson (ToJSON(..), object, (.=)) +import Control.Exception.Safe.Checked + +newtype EnvironmentVariableNotSet = + EnvironmentVariableNotSet String + deriving (Show, Exception) + +instance ToJSON EnvironmentVariableNotSet where + toJSON (EnvironmentVariableNotSet msg) = object + [ "errorType" .= ("EnvironmentVariableNotSet" :: String) + , "errorMessage" .= msg + ] + +data ApiConnection = + ApiConnection + deriving (Show, Exception) + +instance ToJSON ApiConnection where + toJSON ApiConnection = object + [ "errorType" .= ("ApiConnection" :: String) + , "errorMessage" .= ("Could not connect to API to retrieve AWS Lambda parameters" :: String) + ] + +newtype ApiHeaderNotSet = + ApiHeaderNotSet String + deriving (Show, Exception) + +instance ToJSON ApiHeaderNotSet where + toJSON (ApiHeaderNotSet headerName) = object + [ "errorType" .= ("ApiHeaderNotSet" :: String) + , "errorMessage" .= headerName + ] + +data Parsing = Parsing + { errorMessage :: String + , actualValue :: String + } deriving (Show, Exception) + +instance ToJSON Parsing where + toJSON (Parsing objectBeingParsed value) = object + [ "errorType" .= ("Parsing" :: String) + , "errorMessage" .= ("Parse error for " <> objectBeingParsed <> ", could not parse value '" <> value <> "'") + ] + +newtype InvocationError = + InvocationError String + deriving (Show, Exception) + +instance ToJSON InvocationError where + -- We return the user error as it is + toJSON (InvocationError err) = toJSON err diff --git a/stack.yaml b/stack.yaml index 498b8d3..4213fef 100644 --- a/stack.yaml +++ b/stack.yaml @@ -17,7 +17,7 @@ # # resolver: ./custom-snapshot.yaml # resolver: https://example.com/snapshots/2018-01-01.yaml -resolver: lts-13.0 +resolver: lts-13.25 # User packages to be built. # Various formats can be used as shown in the example below. From f975bd7321a33ed15dd1299adb2fd187fc1c2a4b Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Sat, 15 Jun 2019 16:31:09 +0100 Subject: [PATCH 02/11] Make library compile --- app/Main.hs | 7 +- package.yaml | 11 ++ src/Aws/Lambda/API.hs | 21 --- src/Aws/Lambda/Configuration.hs | 1 - src/Aws/Lambda/Runtime.hs | 183 ++++-------------------- src/Aws/Lambda/Runtime/API/Endpoints.hs | 1 + src/Aws/Lambda/Runtime/ApiInfo.hs | 61 +++++--- src/Aws/Lambda/Runtime/Context.hs | 41 ++++-- src/Aws/Lambda/Runtime/Environment.hs | 1 + src/Aws/Lambda/Runtime/Error.hs | 10 +- src/Aws/Lambda/Runtime/IPC.hs | 77 ++++++++++ src/Aws/Lambda/Runtime/Publish.hs | 43 ++++++ src/Aws/Lambda/Runtime/Result.hs | 12 ++ 13 files changed, 256 insertions(+), 213 deletions(-) delete mode 100644 src/Aws/Lambda/API.hs create mode 100644 src/Aws/Lambda/Runtime/IPC.hs create mode 100644 src/Aws/Lambda/Runtime/Publish.hs create mode 100644 src/Aws/Lambda/Runtime/Result.hs diff --git a/app/Main.hs b/app/Main.hs index 283f9d5..5cb8c4b 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -6,8 +6,5 @@ import Aws.Lambda.Runtime main :: IO () -main = forever $ do - res <- runExceptT lambdaRunner - case res of - Right _ -> return () - Left err -> putStrLn $ show err +main = + forever runLambda diff --git a/package.yaml b/package.yaml index 740b68b..151630e 100644 --- a/package.yaml +++ b/package.yaml @@ -27,6 +27,7 @@ library: - directory - filepath - http-client + - http-types - microlens-platform - optparse-generic - process @@ -69,4 +70,14 @@ default-extensions: ghc-options: - -Wall + - -fno-warn-orphans - -optP-Wno-nonportable-include-path + - -Wincomplete-uni-patterns + - -Wincomplete-record-updates + - -Wcompat + - -Widentities + - -Wredundant-constraints + - -Wmissing-export-lists + - -Wpartial-fields + - -fhide-source-paths + - -freverse-errors diff --git a/src/Aws/Lambda/API.hs b/src/Aws/Lambda/API.hs deleted file mode 100644 index d1ba4bb..0000000 --- a/src/Aws/Lambda/API.hs +++ /dev/null @@ -1,21 +0,0 @@ -module Aws.Lambda.API - () where - -import qualified Network.HTTP.Client as Http -import qualified Data.ByteString as ByteString -import qualified Data.ByteString.Lazy as LazyByteString - -type LByteString = LazyByteString.ByteString - -getApiData :: String -> IO (Wreq.Response LByteString) -getApiData endpoint = - keepRetrying (Wreq.getWith opts $ nextInvocationEndpoint endpoint) - where - opts = Wreq.defaults - & Wreq.manager .~ Left (Http.defaultManagerSettings { Http.managerResponseTimeout = Http.responseTimeoutNone }) - keepRetrying :: IO (Wreq.Response LByteString) -> IO (Wreq.Response LByteString) - keepRetrying f = do - result <- (liftIO $ try f) :: IO (Either IOException (Wreq.Response LByteString)) - case result of - Right x -> return x - _ -> keepRetrying f diff --git a/src/Aws/Lambda/Configuration.hs b/src/Aws/Lambda/Configuration.hs index 201376d..32203a4 100644 --- a/src/Aws/Lambda/Configuration.hs +++ b/src/Aws/Lambda/Configuration.hs @@ -28,7 +28,6 @@ import Control.Monad import qualified Data.Text.Encoding as Encoding import qualified Data.ByteString.Lazy as LazyByteString import Data.Void -import Data.Monoid diff --git a/src/Aws/Lambda/Runtime.hs b/src/Aws/Lambda/Runtime.hs index 18073ee..b341d58 100644 --- a/src/Aws/Lambda/Runtime.hs +++ b/src/Aws/Lambda/Runtime.hs @@ -1,157 +1,36 @@ -module Aws.Lambda.Runtime where +module Aws.Lambda.Runtime + ( runLambda + ) where -import Data.Aeson -import System.Exit (ExitCode (..)) -import qualified Data.String as String -import Data.String (String) -import GHC.Generics -import qualified Data.ByteString.Lazy as LazyByteString -import qualified Data.ByteString as ByteString -import Text.Read (readMaybe) -import qualified Data.Text.Encoding as Encoding -import Control.Monad -import Data.Function ((&)) -import Data.Maybe (listToMaybe) -import Data.Monoid ((<>)) - -import qualified Data.CaseInsensitive as CI -import Lens.Micro.Platform hiding ((.=)) import qualified Network.HTTP.Client as Http -import qualified System.Environment as Environment -import qualified System.Process as Process -import qualified Data.UUID as UUID -import qualified Data.UUID.V4 as UUID -import System.IO (hFlush, stdout) -import Control.Exception.Safe.Checked - -import qualified Aws.Lambda.Runtime.Error as RuntimeError -import qualified Aws.Lambda.Runtime.Environment as Environment - - -type LByteString = LazyByteString.ByteString -type ByteString = ByteString.ByteString - - --- extractHeader :: Wreq.Response LByteString -> String -> String --- extractHeader apiData header = --- Encoding.decodeUtf8 (apiData ^. Wreq.responseHeader (CI.mk $ Encoding.encodeUtf8 header)) - - --- extractIntHeader :: Throws RuntimeError.Value => Wreq.Response LByteString -> String -> IO Int --- extractIntHeader apiData headerName = do --- let header = extractHeader apiData headerName --- case readMaybe header of --- Just value -> pure value --- Nothing -> throw (ParseError "deadline" header) - - --- extractBody :: Wreq.Response LByteString -> String --- extractBody apiData = --- Encoding.decodeUtf8 $ LazyByteString.toStrict (apiData ^. Wreq.responseBody) - - - -initializeContext :: Wreq.Response LByteString -> IO Context -initializeContext apiData = do - functionName <- Environment.functionName - version <- Environment.functionVersion - logStream <- Environment.logStreamName - logGroup <- Environment.logGroup - memoryLimitInMb <- Environment.functionMemory - deadline <- extractIntHeader apiData "Lambda-Runtime-Deadline-Ms" - let xrayTraceId = extractHeader apiData "Lambda-Runtime-Trace-Id" - let awsRequestId = extractHeader apiData "Lambda-Runtime-Aws-Request-Id" - let invokedFunctionArn = extractHeader apiData "Lambda-Runtime-Invoked-Function-Arn" - propagateXRayTrace xrayTraceId - pure $ Context - { functionName = functionName - , functionVersion = version - , logStreamName = logStream - , logGroupName = logGroup - , memoryLimitInMb = memoryLimitInMb - , invokedFunctionArn = invokedFunctionArn - , xrayTraceId = xrayTraceId - , awsRequestId = awsRequestId - , deadline = deadline - } -getFunctionResult :: UUID.UUID -> String -> IO (Maybe String) -getFunctionResult u stdOut = do - let out = String.lines stdOut - - out - & takeWhile (/= uuid) - & mapM_ ( \t -> do - liftIO $ putStrLn t - liftIO $ hFlush stdout) - - out - & dropWhile (/= uuid) - & dropWhile (== uuid) - & listToMaybe - & return - where - uuid = String.pack $ UUID.toString u - - -invoke :: String -> Context -> IO LambdaResult -invoke event context = do - handlerName <- Environment.handlerName - runningDirectory <- Environment.taskRoot - let contextJSON = Encoding.decodeUtf8 $ LazyByteString.toStrict $ encode context - uuid <- liftIO UUID.nextRandom - out <- liftIO $ Process.readProcessWithExitCode (String.unpack runningDirectory <> "/haskell_lambda") - [ "--eventObject", String.unpack event - , "--contextObject", String.unpack contextJSON - , "--functionHandler", String.unpack handlerName - , "--executionUuid", UUID.toString uuid - ] - "" - case out of - (ExitSuccess, stdOut, _) -> do - res <- getFunctionResult uuid (String.pack stdOut) - case res of - Nothing -> throwError (ParseError "parsing result" $ String.pack stdOut) - Just value -> pure (LambdaResult value) - (_, stdOut, stdErr) -> - if stdErr /= "" - then throwError (InvocationError $ String.pack stdErr) - else do - res <- getFunctionResult uuid (String.pack stdOut) - case res of - Nothing -> throwError (ParseError "parsing error" $ String.pack stdOut) - Just value -> throwError (InvocationError value) - -newtype LambdaResult = - LambdaResult Text - -publishResult :: Context -> String -> LambdaResult -> IO () -publishResult Context {..} lambdaApi (LambdaResult result) = - void $ liftIO $ Wreq.post (responseEndpoint lambdaApi awsRequestId) (Encoding.encodeUtf8 result) - - -invokeAndPublish :: Context -> String -> String -> IO () -invokeAndPublish ctx event lambdaApiEndpoint = do - res <- invoke event ctx - publishResult ctx lambdaApiEndpoint res - - -publishError :: Context -> String -> RuntimeError -> IO () -publishError Context {..} lambdaApiEndpoint (InvocationError err) = - void (liftIO $ Wreq.post (invocationErrorEndpoint lambdaApiEndpoint awsRequestId) (Encoding.encodeUtf8 err)) - -publishError Context {..} lambdaApiEndpoint (ParseError t t2) = - void (liftIO $ Wreq.post (invocationErrorEndpoint lambdaApiEndpoint awsRequestId) (toJSON $ ParseError t t2)) - -publishError Context {..} lambdaApiEndpoint err = - void (liftIO $ Wreq.post (runtimeInitErrorEndpoint lambdaApiEndpoint) (toJSON err)) - +import Control.Exception.Safe.Checked -lambdaRunner :: IO () -lambdaRunner = do - lambdaApiEndpoint <- readEnvironmentVariable "AWS_LAMBDA_RUNTIME_API" - apiData <- getApiData lambdaApiEndpoint - let event = extractBody apiData - ctx <- initializeContext apiData - invokeAndPublish ctx event lambdaApiEndpoint `catchError` publishError ctx lambdaApiEndpoint +import qualified Aws.Lambda.Runtime.Error as Error +import qualified Aws.Lambda.Runtime.Environment as Environment +import qualified Aws.Lambda.Runtime.ApiInfo as ApiInfo +import qualified Aws.Lambda.Runtime.Context as Context +import qualified Aws.Lambda.Runtime.IPC as IPC +import qualified Aws.Lambda.Runtime.Publish as Publish + + +httpManagerSettings :: Http.ManagerSettings +httpManagerSettings = + -- We set the timeout to none, as AWS Lambda freezes the containers. + Http.defaultManagerSettings + { Http.managerResponseTimeout = Http.responseTimeoutNone + } + +runLambda + :: Throws Error.EnvironmentVariableNotSet + => Throws Error.Parsing + => Throws Error.Invocation + => IO () +runLambda = do + manager <- Http.newManager httpManagerSettings + lambdaApi <- Environment.apiEndpoint + event <- ApiInfo.fetchEvent manager lambdaApi + context <- Context.initialize event + result <- IPC.invoke (ApiInfo.event event) context + Publish.result result lambdaApi context manager diff --git a/src/Aws/Lambda/Runtime/API/Endpoints.hs b/src/Aws/Lambda/Runtime/API/Endpoints.hs index 784e25a..37e7326 100644 --- a/src/Aws/Lambda/Runtime/API/Endpoints.hs +++ b/src/Aws/Lambda/Runtime/API/Endpoints.hs @@ -3,6 +3,7 @@ module Aws.Lambda.Runtime.API.Endpoints , invocationError , runtimeInitError , nextInvocation + , Endpoint(..) ) where import qualified Aws.Lambda.Runtime.API.Version as Version diff --git a/src/Aws/Lambda/Runtime/ApiInfo.hs b/src/Aws/Lambda/Runtime/ApiInfo.hs index 31006b5..4cb195d 100644 --- a/src/Aws/Lambda/Runtime/ApiInfo.hs +++ b/src/Aws/Lambda/Runtime/ApiInfo.hs @@ -3,10 +3,19 @@ module Aws.Lambda.Runtime.ApiInfo , fetchEvent ) where +import qualified Text.Read as Read +import qualified Control.Monad as Monad + import qualified Network.HTTP.Client as Http -import qualified Data.ByteString.Lazy as Lazy +import qualified Network.HTTP.Types.Header as Http +import qualified Data.ByteString.Lazy.Char8 as Lazy +import qualified Data.ByteString.Char8 as ByteString +import Data.ByteString.Char8 (ByteString) +import Control.Exception.Safe.Checked +import Control.Exception (IOException) -import qualified Aws.Lambda.Runtime.Api.Endpoints as Endpoints +import qualified Aws.Lambda.Runtime.API.Endpoints as Endpoints +import qualified Aws.Lambda.Runtime.Error as Error data Event = Event { deadlineMs :: !Int @@ -16,41 +25,51 @@ data Event = Event , event :: !Lazy.ByteString } -fetchEvent :: Http.Manager -> String -> IO Event +fetchEvent :: Throws Error.Parsing => Http.Manager -> String -> IO Event fetchEvent manager lambdaApi = do response <- fetchApiData manager lambdaApi - body <- Http.responseBody response - headers <- Http.responseHeaders response - foldM reduceEvent (Event { event = body }) headers + let body = Http.responseBody response + let headers = Http.responseHeaders response + Monad.foldM reduceEvent (initialEvent body) headers -fetchApiData :: Http.Manager -> String -> IO (Response Lazy.ByteString) +fetchApiData :: Http.Manager -> String -> IO (Http.Response Lazy.ByteString) fetchApiData manager lambdaApi = do - request <- Http.parseRequest (Endpoints.nextInvocation lambdaApi) - Http.httpLbs request manager + let Endpoints.Endpoint endpoint = Endpoints.nextInvocation lambdaApi + request <- Http.parseRequest endpoint + keepRetrying $ Http.httpLbs request manager -reduceEvent :: Event -> Http.Header -> IO Event +reduceEvent :: Throws Error.Parsing => Event -> (Http.HeaderName, ByteString) -> IO Event reduceEvent event header = case header of ("Lambda-Runtime-Deadline-Ms", value) -> - case Read.readMaybe value of + case Read.readMaybe $ ByteString.unpack value of Just ms -> pure event { deadlineMs = ms } - Nothing -> throw (Error.Parsing "deadlineMs" value) + Nothing -> throw (Error.Parsing "deadlineMs" $ ByteString.unpack value) ("Lambda-Runtime-Trace-Id", value) -> - event { traceId = decodeUtf8 value } + pure event { traceId = ByteString.unpack value } ("Lambda-Runtime-Aws-Request-Id", value) -> - event { awsRequest = decodeUtf8 value } + pure event { awsRequestId = ByteString.unpack value } ("Lambda-Runtime-Invoked-Function-Arn", value) -> - event { invokedFunctionArn = decodeUtf8 value } + pure event { invokedFunctionArn = ByteString.unpack value } _ -> - event + pure event -httpManagerSettings :: Http.ManagerSettings -httpManagerSettings = - -- We set the timeout to none, as AWS Lambda freezes the containers. - Http.defaultManagerSettings - { Http.managerResponseTimeout = Http.responseTimeoutNone +initialEvent :: Lazy.ByteString -> Event +initialEvent body = Event + { deadlineMs = 0 + , traceId = "" + , awsRequestId = "" + , invokedFunctionArn = "" + , event = body } + +keepRetrying :: IO (Http.Response Lazy.ByteString) -> IO (Http.Response Lazy.ByteString) +keepRetrying action = do + result <- try action :: IO (Either IOException (Http.Response Lazy.ByteString)) + case result of + Right success -> pure success + _ -> keepRetrying action \ No newline at end of file diff --git a/src/Aws/Lambda/Runtime/Context.hs b/src/Aws/Lambda/Runtime/Context.hs index a204208..64fedfe 100644 --- a/src/Aws/Lambda/Runtime/Context.hs +++ b/src/Aws/Lambda/Runtime/Context.hs @@ -1,19 +1,44 @@ module Aws.Lambda.Runtime.Context ( Context(..) + , initialize ) where import Data.Aeson (FromJSON(..), ToJSON(..)) -import Data.Text (Text) import GHC.Generics (Generic) +import Control.Exception.Safe.Checked + +import qualified Aws.Lambda.Runtime.ApiInfo as ApiInfo +import qualified Aws.Lambda.Runtime.Environment as Environment +import qualified Aws.Lambda.Runtime.Error as Error data Context = Context { memoryLimitInMb :: !Int - , functionName :: !Text - , functionVersion :: !Text - , invokedFunctionArn :: !Text - , awsRequestId :: !Text - , xrayTraceId :: !Text - , logStreamName :: !Text - , logGroupName :: !Text + , functionName :: !String + , functionVersion :: !String + , invokedFunctionArn :: !String + , awsRequestId :: !String + , xrayTraceId :: !String + , logStreamName :: !String + , logGroupName :: !String , deadline :: !Int } deriving (Generic, FromJSON, ToJSON) + +initialize :: Throws Error.Parsing => Throws Error.EnvironmentVariableNotSet => ApiInfo.Event -> IO Context +initialize ApiInfo.Event{..} = do + functionName <- Environment.functionName + version <- Environment.functionVersion + logStream <- Environment.logStreamName + logGroup <- Environment.logGroupName + memoryLimitInMb <- Environment.functionMemory + Environment.setXRayTrace traceId + pure Context + { functionName = functionName + , functionVersion = version + , logStreamName = logStream + , logGroupName = logGroup + , memoryLimitInMb = memoryLimitInMb + , invokedFunctionArn = invokedFunctionArn + , xrayTraceId = traceId + , awsRequestId = awsRequestId + , deadline = deadlineMs + } \ No newline at end of file diff --git a/src/Aws/Lambda/Runtime/Environment.hs b/src/Aws/Lambda/Runtime/Environment.hs index c1457d2..c935716 100644 --- a/src/Aws/Lambda/Runtime/Environment.hs +++ b/src/Aws/Lambda/Runtime/Environment.hs @@ -7,6 +7,7 @@ module Aws.Lambda.Runtime.Environment , functionVersion , logStreamName , logGroupName + , setXRayTrace ) where import qualified Aws.Lambda.Runtime.Error as Error diff --git a/src/Aws/Lambda/Runtime/Error.hs b/src/Aws/Lambda/Runtime/Error.hs index 2a12f53..1473c1b 100644 --- a/src/Aws/Lambda/Runtime/Error.hs +++ b/src/Aws/Lambda/Runtime/Error.hs @@ -3,7 +3,7 @@ module Aws.Lambda.Runtime.Error , ApiConnection(..) , ApiHeaderNotSet(..) , Parsing(..) - , InvocationError(..) + , Invocation(..) ) where import Data.Aeson (ToJSON(..), object, (.=)) @@ -50,10 +50,10 @@ instance ToJSON Parsing where , "errorMessage" .= ("Parse error for " <> objectBeingParsed <> ", could not parse value '" <> value <> "'") ] -newtype InvocationError = - InvocationError String +newtype Invocation = + Invocation String deriving (Show, Exception) -instance ToJSON InvocationError where +instance ToJSON Invocation where -- We return the user error as it is - toJSON (InvocationError err) = toJSON err + toJSON (Invocation err) = toJSON err diff --git a/src/Aws/Lambda/Runtime/IPC.hs b/src/Aws/Lambda/Runtime/IPC.hs new file mode 100644 index 0000000..43cfd07 --- /dev/null +++ b/src/Aws/Lambda/Runtime/IPC.hs @@ -0,0 +1,77 @@ +module Aws.Lambda.Runtime.IPC + ( invoke + ) where + + +import Data.Function ((&)) +import qualified System.Process as Process +import qualified System.Exit as Exit +import qualified Data.String as String +import qualified System.IO as IO +import qualified Data.Maybe as Maybe + +import Control.Exception.Safe.Checked +import Data.Aeson +import qualified Data.ByteString.Lazy.Char8 as ByteString +import qualified Data.UUID as UUID +import qualified Data.UUID.V4 as UUID + +import Aws.Lambda.Runtime.Context (Context(..)) +import Aws.Lambda.Runtime.Result (LambdaResult(..)) +import qualified Aws.Lambda.Runtime.Environment as Environment +import qualified Aws.Lambda.Runtime.Error as Error + +invoke + :: Throws Error.Invocation + => Throws Error.Parsing + => Throws Error.EnvironmentVariableNotSet + => ByteString.ByteString + -> Context + -> IO LambdaResult +invoke event context = do + handlerName <- Environment.handlerName + runningDirectory <- Environment.taskRoot + let contextJSON = ByteString.unpack $ encode context + uuid <- UUID.nextRandom + out <- Process.readProcessWithExitCode (runningDirectory <> "/haskell_lambda") + [ "--eventObject", ByteString.unpack event + , "--contextObject", contextJSON + , "--functionHandler", handlerName + , "--executionUuid", UUID.toString uuid + ] + "" + case out of + (Exit.ExitSuccess, stdOut, _) -> do + res <- getFunctionResult uuid stdOut + case res of + Nothing -> throw (Error.Parsing "parsing result" stdOut) + Just value -> pure (LambdaResult value) + (_, stdOut, stdErr) -> + if stdErr /= "" + then throw (Error.Invocation stdErr) + else do + res <- getFunctionResult uuid stdOut + case res of + Nothing -> throw (Error.Parsing "parsing error" stdOut) + Just value -> throw (Error.Invocation value) + +getFunctionResult :: UUID.UUID -> String -> IO (Maybe String) +getFunctionResult u stdOut = do + let out = String.lines stdOut + let uuid = UUID.toString u + printAfterUuid uuid out + returnAfterUuid uuid out + where + printAfterUuid uuid out = + out + & takeWhile (/= uuid) + & mapM_ ( \t -> do + putStrLn t + IO.hFlush IO.stdout ) + + returnAfterUuid uuid out = + out + & dropWhile (/= uuid) + & dropWhile (== uuid) + & Maybe.listToMaybe + & pure \ No newline at end of file diff --git a/src/Aws/Lambda/Runtime/Publish.hs b/src/Aws/Lambda/Runtime/Publish.hs new file mode 100644 index 0000000..4d6c176 --- /dev/null +++ b/src/Aws/Lambda/Runtime/Publish.hs @@ -0,0 +1,43 @@ +module Aws.Lambda.Runtime.Publish + ( result + , invocationError + , parsingError + , runtimeInitError + ) where + +import Data.Aeson +import Control.Monad (void) +import qualified Network.HTTP.Client as Http + +import Aws.Lambda.Runtime.Context (Context(..)) +import qualified Aws.Lambda.Runtime.API.Endpoints as Endpoints +import qualified Aws.Lambda.Runtime.Error as Error +import Aws.Lambda.Runtime.Result (LambdaResult(..)) + +result :: LambdaResult -> String -> Context -> Http.Manager -> IO () +result res lambdaApi context = + publish res (Endpoints.response lambdaApi $ awsRequestId context) + context + +invocationError :: Error.Invocation -> String -> Context -> Http.Manager -> IO () +invocationError err lambdaApi context = + publish err (Endpoints.invocationError lambdaApi $ awsRequestId context) + context + +parsingError :: Error.Parsing -> String -> Context -> Http.Manager -> IO () +parsingError err lambdaApi context = + publish err (Endpoints.invocationError lambdaApi $ awsRequestId context) + context + +runtimeInitError :: ToJSON err => err -> String -> Context -> Http.Manager -> IO () +runtimeInitError err lambdaApi = + publish err (Endpoints.runtimeInitError lambdaApi) + +publish :: ToJSON err => err -> Endpoints.Endpoint -> Context -> Http.Manager -> IO () +publish err (Endpoints.Endpoint endpoint) Context{..} manager = do + rawRequest <- Http.parseRequest endpoint + let request = rawRequest + { Http.method = "POST" + , Http.requestBody = Http.RequestBodyLBS (encode err) + } + void $ Http.httpNoBody request manager \ No newline at end of file diff --git a/src/Aws/Lambda/Runtime/Result.hs b/src/Aws/Lambda/Runtime/Result.hs new file mode 100644 index 0000000..37937f5 --- /dev/null +++ b/src/Aws/Lambda/Runtime/Result.hs @@ -0,0 +1,12 @@ +module Aws.Lambda.Runtime.Result + ( LambdaResult(..) + ) where + +import Data.Aeson + +newtype LambdaResult = + LambdaResult String + +instance ToJSON LambdaResult where + toJSON (LambdaResult result) = + toJSON result From c1e8f3eb79727ef7e302c025545f1a8e2db1fde5 Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Sat, 15 Jun 2019 22:41:12 +0100 Subject: [PATCH 03/11] Allow compiling with new refactor --- app/Main.hs | 18 +++++++--- package.yaml | 2 +- src/Aws/Lambda/Configuration.hs | 30 ++++++++--------- src/Aws/Lambda/Runtime.hs | 55 ++++++++++++++++++------------- src/Aws/Lambda/Runtime/ApiInfo.hs | 22 ++++++------- src/Aws/Lambda/Runtime/Context.hs | 6 ++-- src/Aws/Lambda/Runtime/Error.hs | 4 +-- src/Aws/Lambda/Runtime/IPC.hs | 16 ++++----- src/Aws/Lambda/Runtime/Publish.hs | 8 ++--- src/Aws/Lambda/ThHelpers.hs | 4 +-- 10 files changed, 93 insertions(+), 72 deletions(-) diff --git a/app/Main.hs b/app/Main.hs index 5cb8c4b..ccb300e 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -1,10 +1,20 @@ -module Main where +module Main + ( main + ) where import Control.Monad -import Control.Monad.Except +import qualified Network.HTTP.Client as Http import Aws.Lambda.Runtime +httpManagerSettings :: Http.ManagerSettings +httpManagerSettings = + -- We set the timeout to none, as AWS Lambda freezes the containers. + Http.defaultManagerSettings + { Http.managerResponseTimeout = Http.responseTimeoutNone + } + main :: IO () -main = - forever runLambda +main = do + manager <- Http.newManager httpManagerSettings + forever (runLambda manager) diff --git a/package.yaml b/package.yaml index 151630e..4341404 100644 --- a/package.yaml +++ b/package.yaml @@ -17,6 +17,7 @@ description: Please see the README on GitHub at = 4.7 && < 5 - mtl + - http-client library: dependencies: @@ -26,7 +27,6 @@ library: - conduit - directory - filepath - - http-client - http-types - microlens-platform - optparse-generic diff --git a/src/Aws/Lambda/Configuration.hs b/src/Aws/Lambda/Configuration.hs index 32203a4..4c80da1 100644 --- a/src/Aws/Lambda/Configuration.hs +++ b/src/Aws/Lambda/Configuration.hs @@ -11,23 +11,23 @@ where import Data.Aeson -import qualified Data.Text as Text +import Control.Monad +import Control.Monad.Trans +import qualified Data.ByteString.Lazy as LazyByteString +import qualified Data.Conduit as Conduit +import Data.Function ((&)) import Data.Text (Text) +import qualified Data.Text as Text +import qualified Data.Text.Encoding as Encoding +import Data.Void import GHC.Generics -import Data.Function ((&)) import Language.Haskell.TH import qualified Options.Generic as Options -import qualified Data.Conduit as Conduit import qualified System.Directory as Directory +import System.Exit (exitFailure, exitSuccess) import System.FilePath (()) +import System.IO (hFlush, stderr, stdout) import System.IO.Error -import System.IO (hFlush, stdout, stderr) -import System.Exit (exitSuccess, exitFailure) -import Control.Monad.Trans -import Control.Monad -import qualified Data.Text.Encoding as Encoding -import qualified Data.ByteString.Lazy as LazyByteString -import Data.Void @@ -120,7 +120,7 @@ returnAndSucceed uuid v = do decodeObj :: FromJSON a => Text -> a decodeObj x = case (eitherDecode $ LazyByteString.fromStrict $ Encoding.encodeUtf8 x) of - Left e -> error e + Left e -> error e Right v -> v @@ -158,9 +158,9 @@ myVisitor = loop [] loop n = do r <- Conduit.await case r of - Nothing -> return n - Just result -> loop (process result <> n) - process (DirData _ (DirError _)) = [] + Nothing -> return n + Just result -> loop (process result <> n) + process (DirData _ (DirError _)) = [] process (DirData dir (DirList _ files)) = map (\f -> dir <> "/" <> f) files @@ -180,4 +180,4 @@ getHandlers = do containsHandler :: Text -> IO Bool containsHandler file = do fileContents <- readFile $ Text.unpack file - return $ "handler :: " `Text.isInfixOf` Text.pack fileContents \ No newline at end of file + return $ "handler :: " `Text.isInfixOf` Text.pack fileContents diff --git a/src/Aws/Lambda/Runtime.hs b/src/Aws/Lambda/Runtime.hs index b341d58..c762c56 100644 --- a/src/Aws/Lambda/Runtime.hs +++ b/src/Aws/Lambda/Runtime.hs @@ -2,35 +2,46 @@ module Aws.Lambda.Runtime ( runLambda ) where -import qualified Network.HTTP.Client as Http - - import Control.Exception.Safe.Checked +import qualified Network.HTTP.Client as Http -import qualified Aws.Lambda.Runtime.Error as Error -import qualified Aws.Lambda.Runtime.Environment as Environment import qualified Aws.Lambda.Runtime.ApiInfo as ApiInfo import qualified Aws.Lambda.Runtime.Context as Context +import qualified Aws.Lambda.Runtime.Environment as Environment +import qualified Aws.Lambda.Runtime.Error as Error import qualified Aws.Lambda.Runtime.IPC as IPC import qualified Aws.Lambda.Runtime.Publish as Publish - -httpManagerSettings :: Http.ManagerSettings -httpManagerSettings = - -- We set the timeout to none, as AWS Lambda freezes the containers. - Http.defaultManagerSettings - { Http.managerResponseTimeout = Http.responseTimeoutNone - } - runLambda - :: Throws Error.EnvironmentVariableNotSet - => Throws Error.Parsing + :: Http.Manager + -> IO () +runLambda manager = do + lambdaApi <- Environment.apiEndpoint `catch` variableNotSet + event <- ApiInfo.fetchEvent manager lambdaApi `catch` errorParsing + context <- Context.initialize event `catch` errorParsing `catch` variableNotSet + ((invokeAndRun manager lambdaApi event context + `catch` \err -> Publish.parsingError err lambdaApi context manager) + `catch` \err -> Publish.invocationError err lambdaApi context manager) + `catch` \(err :: Error.EnvironmentVariableNotSet) -> Publish.runtimeInitError err lambdaApi context manager + +invokeAndRun + :: Throws Error.Parsing => Throws Error.Invocation - => IO () -runLambda = do - manager <- Http.newManager httpManagerSettings - lambdaApi <- Environment.apiEndpoint - event <- ApiInfo.fetchEvent manager lambdaApi - context <- Context.initialize event - result <- IPC.invoke (ApiInfo.event event) context + => Throws Error.EnvironmentVariableNotSet + => Http.Manager + -> String + -> ApiInfo.Event + -> Context.Context + -> IO () +invokeAndRun manager lambdaApi event context = do + result <- IPC.invoke (ApiInfo.event event) context Publish.result result lambdaApi context manager + `catch` \err -> Publish.invocationError err lambdaApi context manager + +variableNotSet :: Error.EnvironmentVariableNotSet -> IO a +variableNotSet (Error.EnvironmentVariableNotSet env) = + error ("Error initializing, variable not set: " <> env) + +errorParsing :: Error.Parsing -> IO a +errorParsing Error.Parsing{..} = + error ("Failed parsing " <> errorMessage <> ", got" <> actualValue) diff --git a/src/Aws/Lambda/Runtime/ApiInfo.hs b/src/Aws/Lambda/Runtime/ApiInfo.hs index 4cb195d..61694c3 100644 --- a/src/Aws/Lambda/Runtime/ApiInfo.hs +++ b/src/Aws/Lambda/Runtime/ApiInfo.hs @@ -3,26 +3,26 @@ module Aws.Lambda.Runtime.ApiInfo , fetchEvent ) where -import qualified Text.Read as Read import qualified Control.Monad as Monad +import qualified Text.Read as Read +import Control.Exception (IOException) +import Control.Exception.Safe.Checked +import Data.ByteString.Char8 (ByteString) +import qualified Data.ByteString.Char8 as ByteString +import qualified Data.ByteString.Lazy.Char8 as Lazy import qualified Network.HTTP.Client as Http import qualified Network.HTTP.Types.Header as Http -import qualified Data.ByteString.Lazy.Char8 as Lazy -import qualified Data.ByteString.Char8 as ByteString -import Data.ByteString.Char8 (ByteString) -import Control.Exception.Safe.Checked -import Control.Exception (IOException) import qualified Aws.Lambda.Runtime.API.Endpoints as Endpoints import qualified Aws.Lambda.Runtime.Error as Error data Event = Event - { deadlineMs :: !Int - , traceId :: !String - , awsRequestId :: !String + { deadlineMs :: !Int + , traceId :: !String + , awsRequestId :: !String , invokedFunctionArn :: !String - , event :: !Lazy.ByteString + , event :: !Lazy.ByteString } fetchEvent :: Throws Error.Parsing => Http.Manager -> String -> IO Event @@ -72,4 +72,4 @@ keepRetrying action = do result <- try action :: IO (Either IOException (Http.Response Lazy.ByteString)) case result of Right success -> pure success - _ -> keepRetrying action \ No newline at end of file + _ -> keepRetrying action diff --git a/src/Aws/Lambda/Runtime/Context.hs b/src/Aws/Lambda/Runtime/Context.hs index 64fedfe..830ef3a 100644 --- a/src/Aws/Lambda/Runtime/Context.hs +++ b/src/Aws/Lambda/Runtime/Context.hs @@ -3,9 +3,9 @@ module Aws.Lambda.Runtime.Context , initialize ) where -import Data.Aeson (FromJSON(..), ToJSON(..)) -import GHC.Generics (Generic) import Control.Exception.Safe.Checked +import Data.Aeson (FromJSON (..), ToJSON (..)) +import GHC.Generics (Generic) import qualified Aws.Lambda.Runtime.ApiInfo as ApiInfo import qualified Aws.Lambda.Runtime.Environment as Environment @@ -41,4 +41,4 @@ initialize ApiInfo.Event{..} = do , xrayTraceId = traceId , awsRequestId = awsRequestId , deadline = deadlineMs - } \ No newline at end of file + } diff --git a/src/Aws/Lambda/Runtime/Error.hs b/src/Aws/Lambda/Runtime/Error.hs index 1473c1b..2f37348 100644 --- a/src/Aws/Lambda/Runtime/Error.hs +++ b/src/Aws/Lambda/Runtime/Error.hs @@ -6,8 +6,8 @@ module Aws.Lambda.Runtime.Error , Invocation(..) ) where -import Data.Aeson (ToJSON(..), object, (.=)) import Control.Exception.Safe.Checked +import Data.Aeson (ToJSON (..), object, (.=)) newtype EnvironmentVariableNotSet = EnvironmentVariableNotSet String @@ -41,7 +41,7 @@ instance ToJSON ApiHeaderNotSet where data Parsing = Parsing { errorMessage :: String - , actualValue :: String + , actualValue :: String } deriving (Show, Exception) instance ToJSON Parsing where diff --git a/src/Aws/Lambda/Runtime/IPC.hs b/src/Aws/Lambda/Runtime/IPC.hs index 43cfd07..310d079 100644 --- a/src/Aws/Lambda/Runtime/IPC.hs +++ b/src/Aws/Lambda/Runtime/IPC.hs @@ -4,11 +4,11 @@ module Aws.Lambda.Runtime.IPC import Data.Function ((&)) -import qualified System.Process as Process -import qualified System.Exit as Exit +import qualified Data.Maybe as Maybe import qualified Data.String as String +import qualified System.Exit as Exit import qualified System.IO as IO -import qualified Data.Maybe as Maybe +import qualified System.Process as Process import Control.Exception.Safe.Checked import Data.Aeson @@ -16,10 +16,10 @@ import qualified Data.ByteString.Lazy.Char8 as ByteString import qualified Data.UUID as UUID import qualified Data.UUID.V4 as UUID -import Aws.Lambda.Runtime.Context (Context(..)) -import Aws.Lambda.Runtime.Result (LambdaResult(..)) +import Aws.Lambda.Runtime.Context (Context (..)) import qualified Aws.Lambda.Runtime.Environment as Environment import qualified Aws.Lambda.Runtime.Error as Error +import Aws.Lambda.Runtime.Result (LambdaResult (..)) invoke :: Throws Error.Invocation @@ -44,7 +44,7 @@ invoke event context = do (Exit.ExitSuccess, stdOut, _) -> do res <- getFunctionResult uuid stdOut case res of - Nothing -> throw (Error.Parsing "parsing result" stdOut) + Nothing -> throw (Error.Parsing "parsing result" stdOut) Just value -> pure (LambdaResult value) (_, stdOut, stdErr) -> if stdErr /= "" @@ -52,7 +52,7 @@ invoke event context = do else do res <- getFunctionResult uuid stdOut case res of - Nothing -> throw (Error.Parsing "parsing error" stdOut) + Nothing -> throw (Error.Parsing "parsing error" stdOut) Just value -> throw (Error.Invocation value) getFunctionResult :: UUID.UUID -> String -> IO (Maybe String) @@ -74,4 +74,4 @@ getFunctionResult u stdOut = do & dropWhile (/= uuid) & dropWhile (== uuid) & Maybe.listToMaybe - & pure \ No newline at end of file + & pure diff --git a/src/Aws/Lambda/Runtime/Publish.hs b/src/Aws/Lambda/Runtime/Publish.hs index 4d6c176..61a5107 100644 --- a/src/Aws/Lambda/Runtime/Publish.hs +++ b/src/Aws/Lambda/Runtime/Publish.hs @@ -5,14 +5,14 @@ module Aws.Lambda.Runtime.Publish , runtimeInitError ) where -import Data.Aeson import Control.Monad (void) +import Data.Aeson import qualified Network.HTTP.Client as Http -import Aws.Lambda.Runtime.Context (Context(..)) import qualified Aws.Lambda.Runtime.API.Endpoints as Endpoints +import Aws.Lambda.Runtime.Context (Context (..)) import qualified Aws.Lambda.Runtime.Error as Error -import Aws.Lambda.Runtime.Result (LambdaResult(..)) +import Aws.Lambda.Runtime.Result (LambdaResult (..)) result :: LambdaResult -> String -> Context -> Http.Manager -> IO () result res lambdaApi context = @@ -40,4 +40,4 @@ publish err (Endpoints.Endpoint endpoint) Context{..} manager = do { Http.method = "POST" , Http.requestBody = Http.RequestBodyLBS (encode err) } - void $ Http.httpNoBody request manager \ No newline at end of file + void $ Http.httpNoBody request manager diff --git a/src/Aws/Lambda/ThHelpers.hs b/src/Aws/Lambda/ThHelpers.hs index df20427..edfdd2c 100644 --- a/src/Aws/Lambda/ThHelpers.hs +++ b/src/Aws/Lambda/ThHelpers.hs @@ -4,9 +4,9 @@ module Aws.Lambda.ThHelpers , recordQ ) where -import Language.Haskell.TH -import qualified Data.Text as Text import Data.Text (Text) +import qualified Data.Text as Text +import Language.Haskell.TH -- | Helper for defining names in declarations -- think of @myValue@ in @myValue = 2@ From f9d9f0251861a7d6d6092955ac6f263ca2ab8858 Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Sat, 15 Jun 2019 22:44:37 +0100 Subject: [PATCH 04/11] Bump version --- app/Main.hs | 2 +- package.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/Main.hs b/app/Main.hs index ccb300e..d2ba0fb 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -2,9 +2,9 @@ module Main ( main ) where +import Aws.Lambda.Runtime import Control.Monad import qualified Network.HTTP.Client as Http -import Aws.Lambda.Runtime httpManagerSettings :: Http.ManagerSettings diff --git a/package.yaml b/package.yaml index 4341404..9f5abdd 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: aws-lambda-haskell-runtime -version: 1.0.10 +version: 1.1.0 github: "theam/aws-lambda-haskell-runtime" license: Apache-2.0 author: Nikita Tchayka From 9ad3a95eb97e56339602127280e5240f0c10b95c Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Mon, 17 Jun 2019 10:53:29 +0100 Subject: [PATCH 05/11] Use proper BS --- package.yaml | 2 +- src/Aws/Lambda.hs | 6 ++++++ src/Aws/Lambda/Runtime/Publish.hs | 12 +++++++++--- 3 files changed, 16 insertions(+), 4 deletions(-) create mode 100644 src/Aws/Lambda.hs diff --git a/package.yaml b/package.yaml index 9f5abdd..68b7019 100644 --- a/package.yaml +++ b/package.yaml @@ -37,8 +37,8 @@ library: - safe-exceptions-checked source-dirs: src exposed-modules: + - Aws.Lambda - Aws.Lambda.Runtime - - Aws.Lambda.Configuration executables: bootstrap: diff --git a/src/Aws/Lambda.hs b/src/Aws/Lambda.hs new file mode 100644 index 0000000..a1c1dc5 --- /dev/null +++ b/src/Aws/Lambda.hs @@ -0,0 +1,6 @@ +module Aws.Lambda + ( module Reexported + ) where + +import Aws.Lambda.Runtime.Context as Reexported +import Aws.Lambda.Configuration as Reexported \ No newline at end of file diff --git a/src/Aws/Lambda/Runtime/Publish.hs b/src/Aws/Lambda/Runtime/Publish.hs index 61a5107..525370e 100644 --- a/src/Aws/Lambda/Runtime/Publish.hs +++ b/src/Aws/Lambda/Runtime/Publish.hs @@ -8,6 +8,7 @@ module Aws.Lambda.Runtime.Publish import Control.Monad (void) import Data.Aeson import qualified Network.HTTP.Client as Http +import qualified Data.ByteString.Char8 as ByteString import qualified Aws.Lambda.Runtime.API.Endpoints as Endpoints import Aws.Lambda.Runtime.Context (Context (..)) @@ -15,9 +16,14 @@ import qualified Aws.Lambda.Runtime.Error as Error import Aws.Lambda.Runtime.Result (LambdaResult (..)) result :: LambdaResult -> String -> Context -> Http.Manager -> IO () -result res lambdaApi context = - publish res (Endpoints.response lambdaApi $ awsRequestId context) - context +result (LambdaResult res) lambdaApi context manager = do + let Endpoints.Endpoint endpoint = Endpoints.response lambdaApi (awsRequestId context) + rawRequest <- Http.parseRequest endpoint + let request = rawRequest + { Http.method = "POST" + , Http.requestBody = Http.RequestBodyBS (ByteString.pack res) + } + void $ Http.httpNoBody request manager invocationError :: Error.Invocation -> String -> Context -> Http.Manager -> IO () invocationError err lambdaApi context = From f2ae0f3693ac4cf2d7ecd491c98c7f5b2dbd3e9a Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Mon, 17 Jun 2019 11:33:56 +0100 Subject: [PATCH 06/11] Remove ToJSON instance --- src/Aws/Lambda/Runtime/Result.hs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Aws/Lambda/Runtime/Result.hs b/src/Aws/Lambda/Runtime/Result.hs index 37937f5..c4a6a3b 100644 --- a/src/Aws/Lambda/Runtime/Result.hs +++ b/src/Aws/Lambda/Runtime/Result.hs @@ -2,11 +2,5 @@ module Aws.Lambda.Runtime.Result ( LambdaResult(..) ) where -import Data.Aeson - newtype LambdaResult = - LambdaResult String - -instance ToJSON LambdaResult where - toJSON (LambdaResult result) = - toJSON result + LambdaResult String \ No newline at end of file From 69b6cf5225a5dc899acdffbe2a65aafc2c90d02a Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Mon, 17 Jun 2019 12:03:33 +0100 Subject: [PATCH 07/11] Remove weeds --- .hlint.yaml | 67 +++++++++++++++++++++++++++++++++ package.yaml | 12 +++--- src/Aws/Lambda/Configuration.hs | 60 ++++------------------------- src/Aws/Lambda/Runtime/Error.hs | 22 ----------- 4 files changed, 79 insertions(+), 82 deletions(-) create mode 100644 .hlint.yaml diff --git a/.hlint.yaml b/.hlint.yaml new file mode 100644 index 0000000..1e0b5c8 --- /dev/null +++ b/.hlint.yaml @@ -0,0 +1,67 @@ +# HLint configuration file +# https://github.com/ndmitchell/hlint +########################## + +# This file contains a template configuration file, which is typically +# placed as .hlint.yaml in the root of your project + + +# Specify additional command line arguments +# +# - arguments: [--color, --cpp-simple, -XQuasiQuotes] + + +# Control which extensions/flags/modules/functions can be used +# +- extensions: + - name: + - TemplateHaskell + - OverloadedStrings + - RecordWildCards + - ScopedTypeVariables + - DeriveGeneric + - TypeApplications + - FlexibleContexts + - DeriveAnyClass + - QuasiQuotes +# +# - flags: +# - {name: -w, within: []} # -w is allowed nowhere +# +# - modules: +# - {name: [Data.Set, Data.HashSet], as: Set} # if you import Data.Set qualified, it must be as 'Set' +# - {name: Control.Arrow, within: []} # Certain modules are banned entirely +# +# - functions: +# - {name: unsafePerformIO, within: []} # unsafePerformIO can only appear in no modules + + +# Add custom hints for this project +# +# Will suggest replacing "wibbleMany [myvar]" with "wibbleOne myvar" +# - error: {lhs: "wibbleMany [x]", rhs: wibbleOne x} + + +# Turn on hints that are off by default +# +# Ban "module X(module X) where", to require a real export list +# - warn: {name: Use explicit module export list} +# +# Replace a $ b $ c with a . b $ c +# - group: {name: dollar, enabled: true} +# +# Generalise map to fmap, ++ to <> +# - group: {name: generalise, enabled: true} + + +# Ignore some builtin hints +# - ignore: {name: Use let} +# - ignore: {name: Use const, within: SpecialModule} # Only within certain modules + + +# Define some custom infix operators +# - fixity: infixr 3 ~^#^~ + + +# To generate a suitable file for HLint do: +# $ hlint --default > .hlint.yaml diff --git a/package.yaml b/package.yaml index 68b7019..a4e9f8f 100644 --- a/package.yaml +++ b/package.yaml @@ -16,25 +16,21 @@ description: Please see the README on GitHub at = 4.7 && < 5 - - mtl - - http-client library: dependencies: - aeson - bytestring - - case-insensitive - - conduit - - directory - - filepath + - http-client - http-types - - microlens-platform - optparse-generic - process - template-haskell - text - uuid - safe-exceptions-checked + - path + - path-io source-dirs: src exposed-modules: - Aws.Lambda @@ -46,6 +42,7 @@ executables: main: Main.hs dependencies: - aws-lambda-haskell-runtime + - http-client tests: aws-lambda-haskell-runtime-test: @@ -67,6 +64,7 @@ default-extensions: - TypeApplications - FlexibleContexts - DeriveAnyClass + - QuasiQuotes ghc-options: - -Wall diff --git a/src/Aws/Lambda/Configuration.hs b/src/Aws/Lambda/Configuration.hs index 4c80da1..c9f8ead 100644 --- a/src/Aws/Lambda/Configuration.hs +++ b/src/Aws/Lambda/Configuration.hs @@ -12,24 +12,19 @@ where import Data.Aeson import Control.Monad -import Control.Monad.Trans import qualified Data.ByteString.Lazy as LazyByteString -import qualified Data.Conduit as Conduit import Data.Function ((&)) import Data.Text (Text) import qualified Data.Text as Text import qualified Data.Text.Encoding as Encoding -import Data.Void import GHC.Generics import Language.Haskell.TH import qualified Options.Generic as Options -import qualified System.Directory as Directory import System.Exit (exitFailure, exitSuccess) -import System.FilePath (()) import System.IO (hFlush, stderr, stdout) -import System.IO.Error - +import Path +import qualified Path.IO as PathIO import Aws.Lambda.ThHelpers @@ -69,12 +64,11 @@ dispatcherCaseQ fileNames = do handlerCaseQ :: Text -> Q Match handlerCaseQ lambdaHandler = do - let pattern = LitP (StringL $ Text.unpack lambdaHandler) + let pat = LitP (StringL $ Text.unpack lambdaHandler) body <- [e|do result <- $(eName qualifiedName) (decodeObj $(eName "eventObject")) (decodeObj $(eName "contextObject")) - either (returnAndFail $(eName "executionUuid")) (returnAndSucceed $(eName "executionUuid")) result - |] - pure $ Match pattern (NormalB body) [] + either (returnAndFail $(eName "executionUuid")) (returnAndSucceed $(eName "executionUuid")) result |] + pure $ Match pat (NormalB body) [] where qualifiedName = lambdaHandler @@ -123,51 +117,11 @@ decodeObj x = Left e -> error e Right v -> v - -data DirContent = DirList [FilePath] [FilePath] - | DirError IOError -data DirData = DirData FilePath DirContent - - --- Produces directory data -walk :: FilePath -> Conduit.ConduitM () DirData IO () -walk path = do - result <- lift $ tryIOError listdir - case result of - Right dl@(DirList subdirs _) -> do - Conduit.yield (DirData path dl) - forM_ subdirs (walk . (path )) - Right e -> Conduit.yield (DirData path e) - Left e -> Conduit.yield (DirData path (DirError e)) - where - listdir = do - entries <- filterHidden <$> Directory.getDirectoryContents path - subdirs <- filterM isDir entries - files <- filterM isFile entries - return $ DirList subdirs files - where - isFile entry = Directory.doesFileExist (path entry) - isDir entry = Directory.doesDirectoryExist (path entry) - filterHidden paths = filter (\p -> head p /= '.' && p /= "node_modules") paths - - --- Consume directories -myVisitor :: Conduit.ConduitM DirData Void IO [FilePath] -myVisitor = loop [] - where - loop n = do - r <- Conduit.await - case r of - Nothing -> return n - Just result -> loop (process result <> n) - process (DirData _ (DirError _)) = [] - process (DirData dir (DirList _ files)) = map (\f -> dir <> "/" <> f) files - - getHandlers :: IO [Text] getHandlers = do - files <- Conduit.runConduit $ walk "." Conduit..| myVisitor + (_, files) <- PathIO.listDirRecurRel [reldir|.|] handlerFiles <- files + & fmap toFilePath & fmap Text.pack & filter (Text.isSuffixOf ".hs") & filterM containsHandler diff --git a/src/Aws/Lambda/Runtime/Error.hs b/src/Aws/Lambda/Runtime/Error.hs index 2f37348..a6609b3 100644 --- a/src/Aws/Lambda/Runtime/Error.hs +++ b/src/Aws/Lambda/Runtime/Error.hs @@ -1,7 +1,5 @@ module Aws.Lambda.Runtime.Error ( EnvironmentVariableNotSet(..) - , ApiConnection(..) - , ApiHeaderNotSet(..) , Parsing(..) , Invocation(..) ) where @@ -19,26 +17,6 @@ instance ToJSON EnvironmentVariableNotSet where , "errorMessage" .= msg ] -data ApiConnection = - ApiConnection - deriving (Show, Exception) - -instance ToJSON ApiConnection where - toJSON ApiConnection = object - [ "errorType" .= ("ApiConnection" :: String) - , "errorMessage" .= ("Could not connect to API to retrieve AWS Lambda parameters" :: String) - ] - -newtype ApiHeaderNotSet = - ApiHeaderNotSet String - deriving (Show, Exception) - -instance ToJSON ApiHeaderNotSet where - toJSON (ApiHeaderNotSet headerName) = object - [ "errorType" .= ("ApiHeaderNotSet" :: String) - , "errorMessage" .= headerName - ] - data Parsing = Parsing { errorMessage :: String , actualValue :: String From edf2d4975fe1ea1c8470271530c4badbf577c436 Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Mon, 17 Jun 2019 14:05:36 +0100 Subject: [PATCH 08/11] wip: Refactor Configuration --- .hlint.yaml | 33 ++++--- src/Aws/Lambda/Configuration.hs | 95 ++----------------- .../Lambda/{ThHelpers.hs => Meta/Common.hs} | 26 ++--- src/Aws/Lambda/Meta/Discover.hs | 39 ++++++++ src/Aws/Lambda/Meta/Dispatch.hs | 41 ++++++++ src/Aws/Lambda/Meta/Main.hs | 25 +++++ src/Aws/Lambda/Meta/Run.hs | 16 ++++ 7 files changed, 163 insertions(+), 112 deletions(-) rename src/Aws/Lambda/{ThHelpers.hs => Meta/Common.hs} (60%) create mode 100644 src/Aws/Lambda/Meta/Discover.hs create mode 100644 src/Aws/Lambda/Meta/Dispatch.hs create mode 100644 src/Aws/Lambda/Meta/Main.hs create mode 100644 src/Aws/Lambda/Meta/Run.hs diff --git a/.hlint.yaml b/.hlint.yaml index 1e0b5c8..fd49751 100644 --- a/.hlint.yaml +++ b/.hlint.yaml @@ -8,22 +8,31 @@ # Specify additional command line arguments # -# - arguments: [--color, --cpp-simple, -XQuasiQuotes] +- arguments: + - TemplateHaskell + - OverloadedStrings + - RecordWildCards + - ScopedTypeVariables + - DeriveGeneric + - TypeApplications + - FlexibleContexts + - DeriveAnyClass + - QuasiQuotes # Control which extensions/flags/modules/functions can be used # -- extensions: - - name: - - TemplateHaskell - - OverloadedStrings - - RecordWildCards - - ScopedTypeVariables - - DeriveGeneric - - TypeApplications - - FlexibleContexts - - DeriveAnyClass - - QuasiQuotes +# - extensions: +# - name: +# - TemplateHaskell +# - OverloadedStrings +# - RecordWildCards +# - ScopedTypeVariables +# - DeriveGeneric +# - TypeApplications +# - FlexibleContexts +# - DeriveAnyClass +# - QuasiQuotes # # - flags: # - {name: -w, within: []} # -w is allowed nowhere diff --git a/src/Aws/Lambda/Configuration.hs b/src/Aws/Lambda/Configuration.hs index c9f8ead..65f9b8f 100644 --- a/src/Aws/Lambda/Configuration.hs +++ b/src/Aws/Lambda/Configuration.hs @@ -1,97 +1,37 @@ {-# OPTIONS_GHC -fno-warn-unused-pattern-binds #-} module Aws.Lambda.Configuration - ( LambdaOptions (..) + ( Main.LambdaOptions(..) + , Main.generate + , Main.getRecord , configureLambda , returnAndFail , returnAndSucceed , decodeObj - , Options.getRecord ) where import Data.Aeson -import Control.Monad import qualified Data.ByteString.Lazy as LazyByteString -import Data.Function ((&)) import Data.Text (Text) import qualified Data.Text as Text import qualified Data.Text.Encoding as Encoding -import GHC.Generics import Language.Haskell.TH -import qualified Options.Generic as Options import System.Exit (exitFailure, exitSuccess) import System.IO (hFlush, stderr, stdout) -import Path -import qualified Path.IO as PathIO - -import Aws.Lambda.ThHelpers +import qualified Aws.Lambda.Meta.Main as Main +import qualified Aws.Lambda.Meta.Run as Run putTextLn :: Text -> IO () putTextLn = putStrLn . Text.unpack -data LambdaOptions = LambdaOptions - { eventObject :: Text - , contextObject :: Text - , functionHandler :: Text - , executionUuid :: Text - } deriving (Generic) -instance Options.ParseRecord LambdaOptions - - --- This function is the reason why we disable the warning on top of the module -mkMain :: Q [Dec] -mkMain = [d| - $(pName "main") = getRecord "" >>= run - |] - -mkRun :: Q Dec -mkRun = do - handlers <- runIO getHandlers - clause' <- recordQ "LambdaOptions" ["functionHandler", "contextObject", "eventObject", "executionUuid"] - body <- dispatcherCaseQ handlers - pure $ FunD (mkName "run") [Clause [clause'] (NormalB body) []] - - -dispatcherCaseQ :: [Text] -> Q Exp -dispatcherCaseQ fileNames = do - caseExp <- eName "functionHandler" - matches <- traverse handlerCaseQ fileNames - unmatched <- unmatchedCaseQ - pure $ CaseE caseExp (matches <> [unmatched]) - - -handlerCaseQ :: Text -> Q Match -handlerCaseQ lambdaHandler = do - let pat = LitP (StringL $ Text.unpack lambdaHandler) - body <- [e|do - result <- $(eName qualifiedName) (decodeObj $(eName "eventObject")) (decodeObj $(eName "contextObject")) - either (returnAndFail $(eName "executionUuid")) (returnAndSucceed $(eName "executionUuid")) result |] - pure $ Match pat (NormalB body) [] - where - qualifiedName = - lambdaHandler - & Text.dropWhile (/= '/') - & Text.drop 1 - & Text.replace "/" "." - - -unmatchedCaseQ :: Q Match -unmatchedCaseQ = do - let pattern = WildP - body <- [e| - returnAndFail $(eName "executionUuid") ("Handler " <> $(eName "functionHandler") <> " does not exist on project") - |] - pure $ Match pattern (NormalB body) [] - configureLambda :: Q [Dec] configureLambda = do - main <- mkMain - run <- mkRun + main <- Main.generate + run <- Run.generate return $ main <> [run] - returnAndFail :: ToJSON a => Text -> a -> IO () returnAndFail uuid v = do hFlush stdout @@ -115,23 +55,4 @@ decodeObj :: FromJSON a => Text -> a decodeObj x = case (eitherDecode $ LazyByteString.fromStrict $ Encoding.encodeUtf8 x) of Left e -> error e - Right v -> v - -getHandlers :: IO [Text] -getHandlers = do - (_, files) <- PathIO.listDirRecurRel [reldir|.|] - handlerFiles <- files - & fmap toFilePath - & fmap Text.pack - & filter (Text.isSuffixOf ".hs") - & filterM containsHandler - & fmap (fmap $ Text.dropEnd 3) - & fmap (fmap $ Text.drop 2) - & fmap (fmap (<> ".handler")) - return handlerFiles - - -containsHandler :: Text -> IO Bool -containsHandler file = do - fileContents <- readFile $ Text.unpack file - return $ "handler :: " `Text.isInfixOf` Text.pack fileContents + Right v -> v \ No newline at end of file diff --git a/src/Aws/Lambda/ThHelpers.hs b/src/Aws/Lambda/Meta/Common.hs similarity index 60% rename from src/Aws/Lambda/ThHelpers.hs rename to src/Aws/Lambda/Meta/Common.hs index edfdd2c..d8c5b38 100644 --- a/src/Aws/Lambda/ThHelpers.hs +++ b/src/Aws/Lambda/Meta/Common.hs @@ -1,7 +1,7 @@ -module Aws.Lambda.ThHelpers - ( pName - , eName - , recordQ +module Aws.Lambda.Meta.Common + ( declarationName + , expressionName + , getFieldsFrom ) where import Data.Text (Text) @@ -10,13 +10,13 @@ import Language.Haskell.TH -- | Helper for defining names in declarations -- think of @myValue@ in @myValue = 2@ -pName :: Text -> Q Pat -pName = pure . VarP . mkName . Text.unpack +declarationName :: Text -> Q Pat +declarationName = pure . VarP . mkName . Text.unpack -- | Helper for defining names in expressions -- think of @myFunction@ in @quux = myFunction 3@ -eName :: Text -> Q Exp -eName = pure . VarE . mkName . Text.unpack +expressionName :: Text -> Q Exp +expressionName = pure . VarE . mkName . Text.unpack -- | Helper for extracting fields of a specified record @@ -24,12 +24,12 @@ eName = pure . VarE . mkName . Text.unpack -- and the list of fields to bring into scope as second -- think of @Person@, and @personAge@, @personName@ in -- @myFunction Person { personAge, personName } = ...@ -recordQ :: Text -> [Text] -> Q Pat -recordQ name fields = do - extractedFields <- traverse fName fields +getFieldsFrom :: Text -> [Text] -> Q Pat +getFieldsFrom name fields = do + extractedFields <- traverse extractField fields pure $ RecP (mkName $ Text.unpack name) extractedFields where -- | Helper for extracting fields of records -- think of @personAge@ in @myFunction Person { personAge = personAge } = ...@ - fName :: Text -> Q FieldPat - fName n = pure (mkName $ Text.unpack n, VarP $ mkName $ Text.unpack n) + extractField :: Text -> Q FieldPat + extractField n = pure (mkName $ Text.unpack n, VarP $ mkName $ Text.unpack n) diff --git a/src/Aws/Lambda/Meta/Discover.hs b/src/Aws/Lambda/Meta/Discover.hs new file mode 100644 index 0000000..5d4bb61 --- /dev/null +++ b/src/Aws/Lambda/Meta/Discover.hs @@ -0,0 +1,39 @@ +module Aws.Lambda.Meta.Discover + (handlers) where + +import Data.Text (Text) +import Data.Function ((&)) +import qualified Data.Text as Text +import qualified Control.Monad as Monad +import qualified Data.Maybe as Maybe + +import Path +import qualified Path.IO as PathIO + +handlers :: IO [Text] +handlers = do + (_, files) <- PathIO.listDirRecurRel [reldir|.|] + handlerFiles <- modulesWithHandler files + pure (handlerNames handlerFiles) + +modulesWithHandler :: [Path Rel File] -> IO [Path Rel File] +modulesWithHandler files = + filter isHaskellModule files + & Monad.filterM containsHandler + where + isHaskellModule file = + fileExtension file == ".hs" + +handlerNames :: [Path Rel File] -> [Text] +handlerNames modules = + fmap changeExtensionToHandler modules + & fmap (Text.pack . toFilePath) + where + changeExtensionToHandler file = + setFileExtension ".handler" file + & Maybe.fromJust + +containsHandler :: Path Rel File -> IO Bool +containsHandler file = do + fileContents <- readFile $ toFilePath file + pure $ "handler :: " `Text.isInfixOf` Text.pack fileContents \ No newline at end of file diff --git a/src/Aws/Lambda/Meta/Dispatch.hs b/src/Aws/Lambda/Meta/Dispatch.hs new file mode 100644 index 0000000..0d55add --- /dev/null +++ b/src/Aws/Lambda/Meta/Dispatch.hs @@ -0,0 +1,41 @@ +module Aws.Lambda.Meta.Dispatch + (generate) where + +import Data.Function ((&)) +import Data.Text (Text) +import qualified Data.Text as Text + +import qualified Language.Haskell.TH as Meta + +import Aws.Lambda.Meta.Common + +generate :: [Text] -> Meta.ExpQ +generate fileNames = do + caseExp <- expressionName "functionHandler" + matches <- traverse handlerCase fileNames + unmatched <- unmatchedCase + pure $ Meta.CaseE caseExp (matches <> [unmatched]) + + +handlerCase :: Text -> Meta.MatchQ +handlerCase lambdaHandler = do + let pat = Meta.LitP (Meta.StringL $ Text.unpack lambdaHandler) + body <- [e|do + result <- $(expressionName qualifiedName) (decodeObj $(expressionName "eventObject")) (decodeObj $(expressionName "contextObject")) + either (returnAndFail $(expressionName "executionUuid")) (returnAndSucceed $(expressionName "executionUuid")) result |] + pure $ Meta.Match pat (Meta.NormalB body) [] + where + qualifiedName = + lambdaHandler + & Text.dropWhile (/= '/') + & Text.drop 1 + & Text.replace "/" "." + + +unmatchedCase :: Meta.MatchQ +unmatchedCase = do + let pattern = Meta.WildP + body <- [e| + returnAndFail $(expressionName "executionUuid") ("Handler " <> $(expressionName "functionHandler") <> " does not exist on project") + |] + pure $ Meta.Match pattern (Meta.NormalB body) [] diff --git a/src/Aws/Lambda/Meta/Main.hs b/src/Aws/Lambda/Meta/Main.hs new file mode 100644 index 0000000..2f98bc1 --- /dev/null +++ b/src/Aws/Lambda/Meta/Main.hs @@ -0,0 +1,25 @@ +module Aws.Lambda.Meta.Main + ( LambdaOptions(..) + , generate + , Options.getRecord + ) where + +import Data.Text (Text) +import GHC.Generics (Generic) + +import qualified Options.Generic as Options +import qualified Language.Haskell.TH as Meta + +import Aws.Lambda.Meta.Common + +data LambdaOptions = LambdaOptions + { eventObject :: !Text + , contextObject :: !Text + , functionHandler :: !Text + , executionUuid :: !Text + } deriving (Generic, Options.ParseRecord) + +generate :: Meta.DecsQ +generate = [d| + $(declarationName "main") = getRecord "" >>= run + |] diff --git a/src/Aws/Lambda/Meta/Run.hs b/src/Aws/Lambda/Meta/Run.hs new file mode 100644 index 0000000..914b8c7 --- /dev/null +++ b/src/Aws/Lambda/Meta/Run.hs @@ -0,0 +1,16 @@ +module Aws.Lambda.Meta.Run + ( generate + ) where + +import qualified Language.Haskell.TH as Meta + +import Aws.Lambda.Meta.Common +import qualified Aws.Lambda.Meta.Dispatch as Dispatch +import qualified Aws.Lambda.Meta.Discover as Discover + +generate :: Meta.DecQ +generate = do + handlers <- Meta.runIO Discover.handlers + clause' <- getFieldsFrom "LambdaOptions" ["functionHandler", "contextObject", "eventObject", "executionUuid"] + body <- Dispatch.generate handlers + pure $ Meta.FunD (Meta.mkName "run") [Meta.Clause [clause'] (Meta.NormalB body) []] From eb115b05d7bce2b99bd8687fa2035053424db9c2 Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Mon, 17 Jun 2019 15:24:53 +0100 Subject: [PATCH 09/11] Refactor Configuration --- package.yaml | 1 + src/Aws/Lambda/Configuration.hs | 48 +++++---------------------------- src/Aws/Lambda/Meta/Discover.hs | 2 +- src/Aws/Lambda/Meta/Dispatch.hs | 14 +++++++--- src/Aws/Lambda/Meta/Main.hs | 9 +++---- src/Aws/Lambda/Runtime/IPC.hs | 21 +++++++++++++++ 6 files changed, 45 insertions(+), 50 deletions(-) diff --git a/package.yaml b/package.yaml index a4e9f8f..40b87df 100644 --- a/package.yaml +++ b/package.yaml @@ -79,3 +79,4 @@ ghc-options: - -Wpartial-fields - -fhide-source-paths - -freverse-errors + - -O2 diff --git a/src/Aws/Lambda/Configuration.hs b/src/Aws/Lambda/Configuration.hs index 65f9b8f..c801ee7 100644 --- a/src/Aws/Lambda/Configuration.hs +++ b/src/Aws/Lambda/Configuration.hs @@ -4,55 +4,21 @@ module Aws.Lambda.Configuration , Main.generate , Main.getRecord , configureLambda - , returnAndFail - , returnAndSucceed - , decodeObj + , IPC.returnAndFail + , IPC.returnAndSucceed + , Dispatch.decodeObj ) where -import Data.Aeson - -import qualified Data.ByteString.Lazy as LazyByteString -import Data.Text (Text) -import qualified Data.Text as Text -import qualified Data.Text.Encoding as Encoding -import Language.Haskell.TH -import System.Exit (exitFailure, exitSuccess) -import System.IO (hFlush, stderr, stdout) +import qualified Language.Haskell.TH as Meta import qualified Aws.Lambda.Meta.Main as Main import qualified Aws.Lambda.Meta.Run as Run +import qualified Aws.Lambda.Meta.Dispatch as Dispatch +import qualified Aws.Lambda.Runtime.IPC as IPC -putTextLn :: Text -> IO () -putTextLn = putStrLn . Text.unpack - -configureLambda :: Q [Dec] +configureLambda :: Meta.DecsQ configureLambda = do main <- Main.generate run <- Run.generate return $ main <> [run] - -returnAndFail :: ToJSON a => Text -> a -> IO () -returnAndFail uuid v = do - hFlush stdout - putTextLn uuid - hFlush stdout - putTextLn (Encoding.decodeUtf8 $ LazyByteString.toStrict $ encode v) - hFlush stdout - hFlush stderr - exitFailure - -returnAndSucceed :: ToJSON a => Text -> a -> IO () -returnAndSucceed uuid v = do - hFlush stdout - putTextLn uuid - hFlush stdout - putTextLn (Encoding.decodeUtf8 $ LazyByteString.toStrict $ encode v) - hFlush stdout - exitSuccess - -decodeObj :: FromJSON a => Text -> a -decodeObj x = - case (eitherDecode $ LazyByteString.fromStrict $ Encoding.encodeUtf8 x) of - Left e -> error e - Right v -> v \ No newline at end of file diff --git a/src/Aws/Lambda/Meta/Discover.hs b/src/Aws/Lambda/Meta/Discover.hs index 5d4bb61..a46ce28 100644 --- a/src/Aws/Lambda/Meta/Discover.hs +++ b/src/Aws/Lambda/Meta/Discover.hs @@ -31,7 +31,7 @@ handlerNames modules = where changeExtensionToHandler file = setFileExtension ".handler" file - & Maybe.fromJust + & Maybe.fromJust -- The path will be always parsable, as we just replace the extension containsHandler :: Path Rel File -> IO Bool containsHandler file = do diff --git a/src/Aws/Lambda/Meta/Dispatch.hs b/src/Aws/Lambda/Meta/Dispatch.hs index 0d55add..119c26b 100644 --- a/src/Aws/Lambda/Meta/Dispatch.hs +++ b/src/Aws/Lambda/Meta/Dispatch.hs @@ -1,14 +1,24 @@ module Aws.Lambda.Meta.Dispatch - (generate) where + ( generate + , decodeObj + ) where import Data.Function ((&)) import Data.Text (Text) import qualified Data.Text as Text +import Data.Aeson +import qualified Data.ByteString.Lazy.Char8 as LazyByteString import qualified Language.Haskell.TH as Meta import Aws.Lambda.Meta.Common +decodeObj :: FromJSON a => String -> a +decodeObj x = + case (eitherDecode $ LazyByteString.pack x) of + Left e -> error e + Right v -> v + generate :: [Text] -> Meta.ExpQ generate fileNames = do caseExp <- expressionName "functionHandler" @@ -16,7 +26,6 @@ generate fileNames = do unmatched <- unmatchedCase pure $ Meta.CaseE caseExp (matches <> [unmatched]) - handlerCase :: Text -> Meta.MatchQ handlerCase lambdaHandler = do let pat = Meta.LitP (Meta.StringL $ Text.unpack lambdaHandler) @@ -31,7 +40,6 @@ handlerCase lambdaHandler = do & Text.drop 1 & Text.replace "/" "." - unmatchedCase :: Meta.MatchQ unmatchedCase = do let pattern = Meta.WildP diff --git a/src/Aws/Lambda/Meta/Main.hs b/src/Aws/Lambda/Meta/Main.hs index 2f98bc1..cafe0c5 100644 --- a/src/Aws/Lambda/Meta/Main.hs +++ b/src/Aws/Lambda/Meta/Main.hs @@ -4,7 +4,6 @@ module Aws.Lambda.Meta.Main , Options.getRecord ) where -import Data.Text (Text) import GHC.Generics (Generic) import qualified Options.Generic as Options @@ -13,10 +12,10 @@ import qualified Language.Haskell.TH as Meta import Aws.Lambda.Meta.Common data LambdaOptions = LambdaOptions - { eventObject :: !Text - , contextObject :: !Text - , functionHandler :: !Text - , executionUuid :: !Text + { eventObject :: !String + , contextObject :: !String + , functionHandler :: !String + , executionUuid :: !String } deriving (Generic, Options.ParseRecord) generate :: Meta.DecsQ diff --git a/src/Aws/Lambda/Runtime/IPC.hs b/src/Aws/Lambda/Runtime/IPC.hs index 310d079..f574890 100644 --- a/src/Aws/Lambda/Runtime/IPC.hs +++ b/src/Aws/Lambda/Runtime/IPC.hs @@ -1,5 +1,7 @@ module Aws.Lambda.Runtime.IPC ( invoke + , returnAndFail + , returnAndSucceed ) where @@ -21,6 +23,25 @@ import qualified Aws.Lambda.Runtime.Environment as Environment import qualified Aws.Lambda.Runtime.Error as Error import Aws.Lambda.Runtime.Result (LambdaResult (..)) +returnAndFail :: ToJSON a => String -> a -> IO () +returnAndFail uuid v = do + IO.hFlush IO.stdout + putStrLn uuid + IO.hFlush IO.stdout + putStrLn (ByteString.unpack $ encode v) + IO.hFlush IO.stdout + IO.hFlush IO.stderr + Exit.exitFailure + +returnAndSucceed :: ToJSON a => String -> a -> IO () +returnAndSucceed uuid v = do + IO.hFlush IO.stdout + putStrLn uuid + IO.hFlush IO.stdout + putStrLn (ByteString.unpack $ encode v) + IO.hFlush IO.stdout + Exit.exitSuccess + invoke :: Throws Error.Invocation => Throws Error.Parsing From 628e02ce6c2f2f056b316df7ee50515bc978da70 Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Mon, 17 Jun 2019 15:28:46 +0100 Subject: [PATCH 10/11] Some formatting --- src/Aws/Lambda.hs | 2 +- src/Aws/Lambda/Configuration.hs | 2 +- src/Aws/Lambda/Meta/Discover.hs | 8 ++++---- src/Aws/Lambda/Meta/Main.hs | 2 +- src/Aws/Lambda/Meta/Run.hs | 2 +- src/Aws/Lambda/Runtime/Publish.hs | 2 +- src/Aws/Lambda/Runtime/Result.hs | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Aws/Lambda.hs b/src/Aws/Lambda.hs index a1c1dc5..3b6b9e6 100644 --- a/src/Aws/Lambda.hs +++ b/src/Aws/Lambda.hs @@ -2,5 +2,5 @@ module Aws.Lambda ( module Reexported ) where +import Aws.Lambda.Configuration as Reexported import Aws.Lambda.Runtime.Context as Reexported -import Aws.Lambda.Configuration as Reexported \ No newline at end of file diff --git a/src/Aws/Lambda/Configuration.hs b/src/Aws/Lambda/Configuration.hs index c801ee7..5898e95 100644 --- a/src/Aws/Lambda/Configuration.hs +++ b/src/Aws/Lambda/Configuration.hs @@ -12,9 +12,9 @@ where import qualified Language.Haskell.TH as Meta +import qualified Aws.Lambda.Meta.Dispatch as Dispatch import qualified Aws.Lambda.Meta.Main as Main import qualified Aws.Lambda.Meta.Run as Run -import qualified Aws.Lambda.Meta.Dispatch as Dispatch import qualified Aws.Lambda.Runtime.IPC as IPC configureLambda :: Meta.DecsQ diff --git a/src/Aws/Lambda/Meta/Discover.hs b/src/Aws/Lambda/Meta/Discover.hs index a46ce28..9d407bf 100644 --- a/src/Aws/Lambda/Meta/Discover.hs +++ b/src/Aws/Lambda/Meta/Discover.hs @@ -1,11 +1,11 @@ module Aws.Lambda.Meta.Discover (handlers) where -import Data.Text (Text) -import Data.Function ((&)) -import qualified Data.Text as Text import qualified Control.Monad as Monad +import Data.Function ((&)) import qualified Data.Maybe as Maybe +import Data.Text (Text) +import qualified Data.Text as Text import Path import qualified Path.IO as PathIO @@ -36,4 +36,4 @@ handlerNames modules = containsHandler :: Path Rel File -> IO Bool containsHandler file = do fileContents <- readFile $ toFilePath file - pure $ "handler :: " `Text.isInfixOf` Text.pack fileContents \ No newline at end of file + pure $ "handler :: " `Text.isInfixOf` Text.pack fileContents diff --git a/src/Aws/Lambda/Meta/Main.hs b/src/Aws/Lambda/Meta/Main.hs index cafe0c5..80faf21 100644 --- a/src/Aws/Lambda/Meta/Main.hs +++ b/src/Aws/Lambda/Meta/Main.hs @@ -6,8 +6,8 @@ module Aws.Lambda.Meta.Main import GHC.Generics (Generic) -import qualified Options.Generic as Options import qualified Language.Haskell.TH as Meta +import qualified Options.Generic as Options import Aws.Lambda.Meta.Common diff --git a/src/Aws/Lambda/Meta/Run.hs b/src/Aws/Lambda/Meta/Run.hs index 914b8c7..733653f 100644 --- a/src/Aws/Lambda/Meta/Run.hs +++ b/src/Aws/Lambda/Meta/Run.hs @@ -5,8 +5,8 @@ module Aws.Lambda.Meta.Run import qualified Language.Haskell.TH as Meta import Aws.Lambda.Meta.Common -import qualified Aws.Lambda.Meta.Dispatch as Dispatch import qualified Aws.Lambda.Meta.Discover as Discover +import qualified Aws.Lambda.Meta.Dispatch as Dispatch generate :: Meta.DecQ generate = do diff --git a/src/Aws/Lambda/Runtime/Publish.hs b/src/Aws/Lambda/Runtime/Publish.hs index 525370e..a6381e8 100644 --- a/src/Aws/Lambda/Runtime/Publish.hs +++ b/src/Aws/Lambda/Runtime/Publish.hs @@ -7,8 +7,8 @@ module Aws.Lambda.Runtime.Publish import Control.Monad (void) import Data.Aeson -import qualified Network.HTTP.Client as Http import qualified Data.ByteString.Char8 as ByteString +import qualified Network.HTTP.Client as Http import qualified Aws.Lambda.Runtime.API.Endpoints as Endpoints import Aws.Lambda.Runtime.Context (Context (..)) diff --git a/src/Aws/Lambda/Runtime/Result.hs b/src/Aws/Lambda/Runtime/Result.hs index c4a6a3b..0073a94 100644 --- a/src/Aws/Lambda/Runtime/Result.hs +++ b/src/Aws/Lambda/Runtime/Result.hs @@ -3,4 +3,4 @@ module Aws.Lambda.Runtime.Result ) where newtype LambdaResult = - LambdaResult String \ No newline at end of file + LambdaResult String From 8f319bd7ee293333c33bffac0618f51827b4ef65 Mon Sep 17 00:00:00 2001 From: Nikita Tchayka Date: Mon, 17 Jun 2019 16:55:28 +0100 Subject: [PATCH 11/11] Document the code --- app/Main.hs | 1 + default.nix | 24 ------------------------ shell.nix | 1 - src/Aws/Lambda/Configuration.hs | 5 ++++- src/Aws/Lambda/Meta/Common.hs | 1 + src/Aws/Lambda/Meta/Discover.hs | 14 +++++++++++++- src/Aws/Lambda/Meta/Dispatch.hs | 16 ++++++++++++++-- src/Aws/Lambda/Meta/Main.hs | 3 +++ src/Aws/Lambda/Meta/Run.hs | 6 ++++++ src/Aws/Lambda/Runtime.hs | 2 ++ src/Aws/Lambda/Runtime/ApiInfo.hs | 2 ++ src/Aws/Lambda/Runtime/Context.hs | 2 ++ src/Aws/Lambda/Runtime/Environment.hs | 3 +++ src/Aws/Lambda/Runtime/Error.hs | 2 ++ src/Aws/Lambda/Runtime/IPC.hs | 18 ++++++++++++++++++ src/Aws/Lambda/Runtime/Publish.hs | 6 ++++++ src/Aws/Lambda/Runtime/Result.hs | 1 + 17 files changed, 78 insertions(+), 29 deletions(-) delete mode 100644 default.nix delete mode 100644 shell.nix diff --git a/app/Main.hs b/app/Main.hs index d2ba0fb..cb0a53f 100644 --- a/app/Main.hs +++ b/app/Main.hs @@ -1,3 +1,4 @@ +-- | Main entry point for the layer module Main ( main ) where diff --git a/default.nix b/default.nix deleted file mode 100644 index cfa3328..0000000 --- a/default.nix +++ /dev/null @@ -1,24 +0,0 @@ -{ compiler ? "ghc863", pkgs ? import {} }: - -let - - haskellPackages = pkgs.haskell.packages.${compiler}.extend ( self: super: { - }); - - runtime = haskellPackages.callCabal2nix "aws-lambda-haskell-runtime" ./. {}; - -in -{ - aws-lambda-haskell-runtime = runtime; - aws-lambda-haskell-runtime-shell = haskellPackages.shellFor { - packages = p: [runtime]; - buildInputs = with pkgs; [ - cabal-install - hlint - stack - haskellPackages.ghcid - haskellPackages.weeder - zlib - ]; - }; -} diff --git a/shell.nix b/shell.nix deleted file mode 100644 index f3d8316..0000000 --- a/shell.nix +++ /dev/null @@ -1 +0,0 @@ -(import ./. {}).aws-lambda-haskell-runtime-shell diff --git a/src/Aws/Lambda/Configuration.hs b/src/Aws/Lambda/Configuration.hs index 5898e95..38bf4d1 100644 --- a/src/Aws/Lambda/Configuration.hs +++ b/src/Aws/Lambda/Configuration.hs @@ -17,8 +17,11 @@ import qualified Aws.Lambda.Meta.Main as Main import qualified Aws.Lambda.Meta.Run as Run import qualified Aws.Lambda.Runtime.IPC as IPC +{-| Generates a @main@ function to be used with the +AWS Lambda layer. +-} configureLambda :: Meta.DecsQ configureLambda = do main <- Main.generate run <- Run.generate - return $ main <> [run] + return (main <> [run]) diff --git a/src/Aws/Lambda/Meta/Common.hs b/src/Aws/Lambda/Meta/Common.hs index d8c5b38..bae9c3f 100644 --- a/src/Aws/Lambda/Meta/Common.hs +++ b/src/Aws/Lambda/Meta/Common.hs @@ -1,3 +1,4 @@ +{-| Helper functions to make code generation easier -} module Aws.Lambda.Meta.Common ( declarationName , expressionName diff --git a/src/Aws/Lambda/Meta/Discover.hs b/src/Aws/Lambda/Meta/Discover.hs index 9d407bf..1cd8b44 100644 --- a/src/Aws/Lambda/Meta/Discover.hs +++ b/src/Aws/Lambda/Meta/Discover.hs @@ -1,5 +1,10 @@ +{-| Discovery of AWS Lambda handlers +A handler is basically a function that has a type definition that +starts with "handler :: ". + -} module Aws.Lambda.Meta.Discover - (handlers) where + ( handlers + ) where import qualified Control.Monad as Monad import Data.Function ((&)) @@ -10,6 +15,13 @@ import qualified Data.Text as Text import Path import qualified Path.IO as PathIO +{-| Returns a list of handler paths that look like + +@src/Foo/Bar/Quux.handler@ + +It is the path to the source file, but changing the +extension for ".handler" +-} handlers :: IO [Text] handlers = do (_, files) <- PathIO.listDirRecurRel [reldir|.|] diff --git a/src/Aws/Lambda/Meta/Dispatch.hs b/src/Aws/Lambda/Meta/Dispatch.hs index 119c26b..d69a1a1 100644 --- a/src/Aws/Lambda/Meta/Dispatch.hs +++ b/src/Aws/Lambda/Meta/Dispatch.hs @@ -1,3 +1,4 @@ +{-| Dispatcher generation -} module Aws.Lambda.Meta.Dispatch ( generate , decodeObj @@ -13,16 +14,27 @@ import qualified Language.Haskell.TH as Meta import Aws.Lambda.Meta.Common +{-| Helper function that the dispatcher will use to +decode the JSON that comes as an AWS Lambda event into the +appropriate type expected by the handler. +-} decodeObj :: FromJSON a => String -> a decodeObj x = case (eitherDecode $ LazyByteString.pack x) of Left e -> error e Right v -> v +{-| Generates the dispatcher out of a list of +handler names in the form @src/Foo/Bar.handler@ + +This dispatcher has a case for each of the handlers that calls +the appropriate qualified function. In the case of the example above, +the dispatcher will call @Foo.Bar.handler@. +-} generate :: [Text] -> Meta.ExpQ -generate fileNames = do +generate handlerNames = do caseExp <- expressionName "functionHandler" - matches <- traverse handlerCase fileNames + matches <- traverse handlerCase handlerNames unmatched <- unmatchedCase pure $ Meta.CaseE caseExp (matches <> [unmatched]) diff --git a/src/Aws/Lambda/Meta/Main.hs b/src/Aws/Lambda/Meta/Main.hs index 80faf21..7f64e78 100644 --- a/src/Aws/Lambda/Meta/Main.hs +++ b/src/Aws/Lambda/Meta/Main.hs @@ -1,3 +1,4 @@ +{-| main function generation for interoperation with the layer -} module Aws.Lambda.Meta.Main ( LambdaOptions(..) , generate @@ -11,6 +12,7 @@ import qualified Options.Generic as Options import Aws.Lambda.Meta.Common +-- | Options that the generated main expects data LambdaOptions = LambdaOptions { eventObject :: !String , contextObject :: !String @@ -18,6 +20,7 @@ data LambdaOptions = LambdaOptions , executionUuid :: !String } deriving (Generic, Options.ParseRecord) +-- | Generate the main function that the layer will call generate :: Meta.DecsQ generate = [d| $(declarationName "main") = getRecord "" >>= run diff --git a/src/Aws/Lambda/Meta/Run.hs b/src/Aws/Lambda/Meta/Run.hs index 733653f..7277add 100644 --- a/src/Aws/Lambda/Meta/Run.hs +++ b/src/Aws/Lambda/Meta/Run.hs @@ -8,6 +8,12 @@ import Aws.Lambda.Meta.Common import qualified Aws.Lambda.Meta.Discover as Discover import qualified Aws.Lambda.Meta.Dispatch as Dispatch +{-| Generate the run function + +It will create a dispatcher that is a huge @case@ expression that +expects the name of the handler provided by AWS Lambda, and will +execute the appropriate user function + -} generate :: Meta.DecQ generate = do handlers <- Meta.runIO Discover.handlers diff --git a/src/Aws/Lambda/Runtime.hs b/src/Aws/Lambda/Runtime.hs index c762c56..9922ece 100644 --- a/src/Aws/Lambda/Runtime.hs +++ b/src/Aws/Lambda/Runtime.hs @@ -12,6 +12,8 @@ import qualified Aws.Lambda.Runtime.Error as Error import qualified Aws.Lambda.Runtime.IPC as IPC import qualified Aws.Lambda.Runtime.Publish as Publish +-- | Runs the user @haskell_lambda@ executable and posts back the +-- results runLambda :: Http.Manager -> IO () diff --git a/src/Aws/Lambda/Runtime/ApiInfo.hs b/src/Aws/Lambda/Runtime/ApiInfo.hs index 61694c3..45576b8 100644 --- a/src/Aws/Lambda/Runtime/ApiInfo.hs +++ b/src/Aws/Lambda/Runtime/ApiInfo.hs @@ -17,6 +17,7 @@ import qualified Network.HTTP.Types.Header as Http import qualified Aws.Lambda.Runtime.API.Endpoints as Endpoints import qualified Aws.Lambda.Runtime.Error as Error +-- | Event that is fetched out of the AWS Lambda API data Event = Event { deadlineMs :: !Int , traceId :: !String @@ -25,6 +26,7 @@ data Event = Event , event :: !Lazy.ByteString } +-- | Performs a GET to the endpoint that provides the next event fetchEvent :: Throws Error.Parsing => Http.Manager -> String -> IO Event fetchEvent manager lambdaApi = do response <- fetchApiData manager lambdaApi diff --git a/src/Aws/Lambda/Runtime/Context.hs b/src/Aws/Lambda/Runtime/Context.hs index 830ef3a..9737c9a 100644 --- a/src/Aws/Lambda/Runtime/Context.hs +++ b/src/Aws/Lambda/Runtime/Context.hs @@ -11,6 +11,7 @@ import qualified Aws.Lambda.Runtime.ApiInfo as ApiInfo import qualified Aws.Lambda.Runtime.Environment as Environment import qualified Aws.Lambda.Runtime.Error as Error +-- | Context that is passed to all the handlers data Context = Context { memoryLimitInMb :: !Int , functionName :: !String @@ -23,6 +24,7 @@ data Context = Context , deadline :: !Int } deriving (Generic, FromJSON, ToJSON) +-- | Initializes the context out of the environment initialize :: Throws Error.Parsing => Throws Error.EnvironmentVariableNotSet => ApiInfo.Event -> IO Context initialize ApiInfo.Event{..} = do functionName <- Environment.functionName diff --git a/src/Aws/Lambda/Runtime/Environment.hs b/src/Aws/Lambda/Runtime/Environment.hs index c935716..94a4381 100644 --- a/src/Aws/Lambda/Runtime/Environment.hs +++ b/src/Aws/Lambda/Runtime/Environment.hs @@ -1,3 +1,6 @@ +{-| Provides all the values out of +the environment variables of the system +-} module Aws.Lambda.Runtime.Environment ( functionMemory , apiEndpoint diff --git a/src/Aws/Lambda/Runtime/Error.hs b/src/Aws/Lambda/Runtime/Error.hs index a6609b3..90535d9 100644 --- a/src/Aws/Lambda/Runtime/Error.hs +++ b/src/Aws/Lambda/Runtime/Error.hs @@ -1,3 +1,5 @@ +{-| All the errors that the runtime can throw +-} module Aws.Lambda.Runtime.Error ( EnvironmentVariableNotSet(..) , Parsing(..) diff --git a/src/Aws/Lambda/Runtime/IPC.hs b/src/Aws/Lambda/Runtime/IPC.hs index f574890..5fcb7e5 100644 --- a/src/Aws/Lambda/Runtime/IPC.hs +++ b/src/Aws/Lambda/Runtime/IPC.hs @@ -1,3 +1,18 @@ +{-| Inter-Process Communication + +Used for when the user project is called from a layer. + +This is used to call the @haskell_lambda@ executable, which is +provided by the user, when they want to use the layer. + +This IPC protocol is based on printing an UUID that is +created by the layer, and then the result. So everything that +is printed before the UUID, is considered STDOUT printed by +the lambda, while what comes after the UUID is considered. + +In the case that the lambda execution fails, the exit code +won't be 0 (exit-success), so it will use the STDERR. +-} module Aws.Lambda.Runtime.IPC ( invoke , returnAndFail @@ -23,6 +38,7 @@ import qualified Aws.Lambda.Runtime.Environment as Environment import qualified Aws.Lambda.Runtime.Error as Error import Aws.Lambda.Runtime.Result (LambdaResult (..)) +-- | Returns the JSON value failing, according to the protocol returnAndFail :: ToJSON a => String -> a -> IO () returnAndFail uuid v = do IO.hFlush IO.stdout @@ -33,6 +49,7 @@ returnAndFail uuid v = do IO.hFlush IO.stderr Exit.exitFailure +-- | Returns the JSON value succeeding, according to the protocol returnAndSucceed :: ToJSON a => String -> a -> IO () returnAndSucceed uuid v = do IO.hFlush IO.stdout @@ -42,6 +59,7 @@ returnAndSucceed uuid v = do IO.hFlush IO.stdout Exit.exitSuccess +-- | Invokes a function defined by the user as the @haskell_lambda@ executable invoke :: Throws Error.Invocation => Throws Error.Parsing diff --git a/src/Aws/Lambda/Runtime/Publish.hs b/src/Aws/Lambda/Runtime/Publish.hs index a6381e8..f56508a 100644 --- a/src/Aws/Lambda/Runtime/Publish.hs +++ b/src/Aws/Lambda/Runtime/Publish.hs @@ -1,3 +1,5 @@ +{-| Publishing of results/errors back to the +AWS Lambda runtime API -} module Aws.Lambda.Runtime.Publish ( result , invocationError @@ -15,6 +17,7 @@ import Aws.Lambda.Runtime.Context (Context (..)) import qualified Aws.Lambda.Runtime.Error as Error import Aws.Lambda.Runtime.Result (LambdaResult (..)) +-- | Publishes the result back to AWS Lambda result :: LambdaResult -> String -> Context -> Http.Manager -> IO () result (LambdaResult res) lambdaApi context manager = do let Endpoints.Endpoint endpoint = Endpoints.response lambdaApi (awsRequestId context) @@ -25,16 +28,19 @@ result (LambdaResult res) lambdaApi context manager = do } void $ Http.httpNoBody request manager +-- | Publishes an invocation error back to AWS Lambda invocationError :: Error.Invocation -> String -> Context -> Http.Manager -> IO () invocationError err lambdaApi context = publish err (Endpoints.invocationError lambdaApi $ awsRequestId context) context +-- | Publishes a parsing error back to AWS Lambda parsingError :: Error.Parsing -> String -> Context -> Http.Manager -> IO () parsingError err lambdaApi context = publish err (Endpoints.invocationError lambdaApi $ awsRequestId context) context +-- | Publishes a runtime initialization error back to AWS Lambda runtimeInitError :: ToJSON err => err -> String -> Context -> Http.Manager -> IO () runtimeInitError err lambdaApi = publish err (Endpoints.runtimeInitError lambdaApi) diff --git a/src/Aws/Lambda/Runtime/Result.hs b/src/Aws/Lambda/Runtime/Result.hs index 0073a94..466189b 100644 --- a/src/Aws/Lambda/Runtime/Result.hs +++ b/src/Aws/Lambda/Runtime/Result.hs @@ -2,5 +2,6 @@ module Aws.Lambda.Runtime.Result ( LambdaResult(..) ) where +-- | Wrapper type to handle the result of the user newtype LambdaResult = LambdaResult String