aws-arn (empty) → 0.1.0.0
raw patch · 10 files changed
+551/−0 lines, 10 filesdep +aws-arndep +basedep +deriving-compatsetup-changed
Dependencies added: aws-arn, base, deriving-compat, hashable, lens, tasty, tasty-discover, tasty-hunit, text
Files
- CHANGELOG.md +5/−0
- LICENSE +29/−0
- README.md +106/−0
- Setup.hs +2/−0
- aws-arn.cabal +85/−0
- src/Network/AWS/ARN.hs +171/−0
- src/Network/AWS/ARN/Lambda.hs +71/−0
- test/Main.hs +1/−0
- test/Network/AWS/ARN/Lambda/Test.hs +33/−0
- test/Network/AWS/ARN/Test.hs +48/−0
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Revision history for aws-arn++## 0.1.0.0 -- 2021-04-07++* First version. Released on an unsuspecting world.
+ LICENSE view
@@ -0,0 +1,29 @@+Copyright (C) 2020-2021 Bellroy Pty Ltd++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the+ distribution.++3. Neither the name of the copyright holder nor the names of its+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,106 @@+# aws-arn++++This library provides a type representing [Amazon Resource Names+(ARNs)](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html),+and parsing/unparsing functions for them. The provided optics make it+very convenient to rewrite parts of ARNs.++Start reading at the `Network.AWS.ARN` module, which defines the core+data type and includes some examples.++The `ARN` type is not designed to be a 100% correct-by-construction+representation of only valid ARNs; it is designed to be a lightweight+way to destructure and reassemble ARNs to be used in place of string+munging.++The library aims to provide additional parsers for destructuring the+"resource" part of an ARN, but many are missing right now. PRs to add+this support for more AWS resource types are **especially** welcome.++## Guide: adding a resource++Cribbing from an existing module (e.g., `Network.AWS.ARN.Lambda`) is+probably the easiest way to start, but here is an explicit process to+add a new resource:++1. Create a module for the AWS service, if it doesn't already+ exist. Example: `src/Network/AWS/ARN/Lambda.hs`.++2. Define a record `Foo` to represent the parsed resource part of an+ ARN, and derive (at least) `Eq`, `Ord`, `Hashable`, `Show` and+ `Generic`. Also generate lenses for its fields:++ ```haskell+ data Function = Function+ { _fName :: Text,+ _fQualifier :: Maybe Text+ }+ deriving (Eq, Ord, Hashable, Show, Generic)++ $(makeLenses ''Function)+ ```++3. Define `toFoo` and `fromFoo` functions that attempt to parse and+ unparse the resource part of the ARN:++ ```haskell+ toFunction :: Text -> Maybe Function+ fromFunction :: Function -> Text+ ```++ **Remark:** While these names sound backwards compared to+ `fromText` and `toText`, it means we can have multiple parsing+ functions in a single service's module.++ **Remark:** If you need to write tests for these functions, the+ corresponding module should live at+ `test/Network/AWS/ARN/SomeAWSService/Test.hs`++4. Define a `_Foo` `Prism'` that combines the parsing/unparsing+ functions above:++ ```haskell+ _Function :: Prism' Text Function+ _Function = prism' fromFunction toFunction+ ```++5. Add the records, its fields, its parsing/unparsing functions, and+ its optics to the service module's export list:++ ```haskell+ module Network.AWS.ARN.Lambda+ ( -- * Functions+ Function (..),+ toFunction,+ fromFunction,++ -- ** Function Optics+ _Function,+ fName,+ fQualifier,+ )+ ```++6. Test your work and make a PR.++## Formatters++The formatters used in this repo are provided by `shell.nix`:++* `*.hs`: [`ormolu`](https://github.com/tweag/ormolu)+* `*.cabal`:+ [`cabal-fmt`](https://hackage.haskell.org/package/cabal-fmt)+ (`cabal-fmt --inplace wai-handler-hal.cabal`)+* `*.nix`:+ [`nixpkgs-fmt`](https://github.com/nix-community/nixpkgs-fmt)+ (`nixpkgs-fmt *.nix`)++## Regenerate CI++This repo uses `haskell-ci`, which is provided by `shell.nix`:++```shell+haskell-ci regenerate+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ aws-arn.cabal view
@@ -0,0 +1,85 @@+cabal-version: 2.2+name: aws-arn+version: 0.1.0.0+synopsis:+ Types and optics for manipulating Amazon Resource Names (ARNs)++description:+ This library provides a type representing [Amazon Resource Names+ (ARNs)](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html),+ and parsing/unparsing functions for them. The provided optics make+ it very convenient to rewrite parts of ARNs.+ .+ Start reading at the "Network.AWS.ARN" module, which defines the+ core data type and includes some examples.+ .+ The @ARN@ type is not designed to be a 100% correct-by-construction+ representation of only valid ARNs; it is designed to be a+ lightweight way to destructure and reassemble ARNs to be used in+ place of string munging.+ .+ The library aims to provide additional parsers for destructuring the+ "resource" part of an ARN, but many are missing right now. PRs to+ add this support for more AWS resource types are __especially__+ welcome.++bug-reports: http://github.com/bellroy/aws-arn/issues+license: BSD-3-Clause+license-file: LICENSE+author: Bellroy Tech Team <haskell@bellroy.com>+maintainer: Bellroy Tech Team <haskell@bellroy.com>+copyright: Copyright (C) 2020-2021 Bellroy Pty Ltd+category: AWS, Cloud+build-type: Simple+extra-source-files:+ CHANGELOG.md+ README.md++tested-with: GHC ==8.6.5 || ==8.8.4 || ==8.10.4++common opts+ default-language: Haskell2010+ ghc-options:+ -Wall -Wcompat -Widentities -Wincomplete-record-updates+ -Wincomplete-uni-patterns -Werror=incomplete-patterns+ -Wredundant-constraints -Wpartial-fields -Wtabs+ -Wmissing-local-signatures -fhelpful-errors+ -fprint-expanded-synonyms -fwarn-unused-do-bind++common deps+ build-depends:+ , base >=4.12 && <4.15+ , deriving-compat ^>=0.5.10+ , lens >=4.18.1 && <5.1+ , text ^>=1.2.3++library+ import: opts, deps+ exposed-modules:+ Network.AWS.ARN+ Network.AWS.ARN.Lambda++ build-depends: hashable ^>=1.3.0.0+ hs-source-dirs: src++test-suite spec+ import: opts, deps+ type: exitcode-stdio-1.0+ main-is: Main.hs+ other-modules:+ Network.AWS.ARN.Lambda.Test+ Network.AWS.ARN.Test++ hs-source-dirs: test+ ghc-options: -threaded+ build-depends:+ , aws-arn+ , tasty ^>=1.4.0.2+ , tasty-discover ^>=4.2.2+ , tasty-hunit ^>=0.10.0.3++ build-tool-depends: tasty-discover:tasty-discover -any++source-repository head+ type: git+ location: https://github.com/bellroy/aws-arn.git
+ src/Network/AWS/ARN.hs view
@@ -0,0 +1,171 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveTraversable #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_HADDOCK show-extensions #-}++-- |+--+-- Module : Network.AWS.ARN+-- Copyright : (C) 2020-2021 Bellroy Pty Ltd+-- License : BSD-3-Clause+-- Maintainer : Bellroy Tech Team <haskell@bellroy.com>+-- Stability : experimental+--+-- Provides a type representing [Amazon Resource Names+-- (ARNs)](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html),+-- and parsing/unparsing functions for them. The provided optics make it+-- very convenient to rewrite parts of ARNs.+--+-- == Example+--+-- [API Gateway Lambda+-- Authorizers](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html)+-- are given the ARN of the requested endpoint and method, and are+-- expected to respond with an IAM Policy Document. It is sometimes+-- useful to manipulate the given ARN when describing which resources to+-- authorize.+--+-- Here, we generalize @authorizerSampleARN@ to cover every method of+-- every endpoint in the stage:+--+-- @+-- -- Returns "arn:aws:execute-api:us-east-1:123456789012:my-spiffy-api\/stage\/*"+-- let+-- authorizerSampleARN = "arn:aws:execute-api:us-east-1:123456789012:my-spiffy-api\/stage\/GET\/some\/deep\/path"+-- in+-- over ('_ARN' . 'arnResource' . 'slashes') (\parts -> take 2 parts ++ ["*"]) authorizerSampleARN+-- @+module Network.AWS.ARN+ ( ARN (..),+ toARN,+ fromARN,++ -- * ARN Optics+ _ARN,+ arnPartition,+ arnService,+ arnRegion,+ arnAccount,+ arnResource,++ -- * Utility Optics+ colons,+ slashes,+ )+where++import Control.Lens (Iso', Prism', iso, makeLenses, prism')+import Data.Eq.Deriving (deriveEq1)+import Data.Hashable (Hashable)+import Data.Hashable.Lifted (Hashable1)+import Data.Ord.Deriving (deriveOrd1)+import Data.Text (Text)+import qualified Data.Text as T+import GHC.Generics (Generic, Generic1)+import Text.Show.Deriving (deriveShow1)++-- $setup+-- >>> :set -XOverloadedStrings+-- >>> import Control.Lens++-- | A parsed ARN. Either use the '_ARN' 'Prism'', or the 'toARN' and+-- 'fromARN' functions to convert @'Text' \<-\> 'ARN'@. The+-- '_arnResource' part of an ARN will often contain colon- or+-- slash-separated parts which precisely identify some resource. If+-- there is no service-specific module (see below), the 'colons' and+-- 'slashes' @'Control.Lens.Iso''@s in this module can pick apart the+-- `_arnResource` field.+--+-- == Service-Specific Modules+--+-- Modules like "Network.AWS.ARN.Lambda" provide types to parse the+-- resource part of an ARN into something more specific:+--+-- @+-- -- Remark: Lambda._Function :: 'Prism'' 'Text' Lambda.Function+-- -- Returns: Just "the-coolest-function-ever"+-- let+-- functionARN = "arn:aws:lambda:us-east-1:123456789012:function:the-coolest-function-ever:Alias"+-- in+-- functionARN ^? _ARN . arnResource . Lambda._Function . Lambda.fName+-- @+--+-- You can also use 'ARN'\'s 'Traversable' instance and+-- 'Control.Lens.Prism.below' to create 'Prism''s that indicate their+-- resource type in 'ARN'\'s type variable:+--+-- @+-- '_ARN' . 'Control.Lens.Prism.below' Lambda._Function :: 'Prism'' 'Text' ('ARN' Lambda.Function)+-- @+data ARN r = ARN+ { _arnPartition :: Text,+ _arnService :: Text,+ _arnRegion :: Text,+ _arnAccount :: Text,+ _arnResource :: r+ }+ deriving+ ( Eq,+ Ord,+ Show,+ Generic,+ Generic1,+ Hashable,+ Hashable1,+ Functor,+ Foldable,+ Traversable+ )++$(makeLenses ''ARN)+$(deriveEq1 ''ARN)+$(deriveOrd1 ''ARN)+$(deriveShow1 ''ARN)++toARN :: Text -> Maybe (ARN Text)+toARN t = case T.splitOn ":" t of+ ("arn" : part : srv : reg : acc : res) ->+ Just $+ ARN+ { _arnPartition = part,+ _arnService = srv,+ _arnRegion = reg,+ _arnAccount = acc,+ _arnResource = T.intercalate ":" res+ }+ _ -> Nothing++fromARN :: ARN Text -> Text+fromARN arn =+ T.intercalate+ ":"+ [ "arn",+ _arnPartition arn,+ _arnService arn,+ _arnRegion arn,+ _arnAccount arn,+ _arnResource arn+ ]++_ARN :: Prism' Text (ARN Text)+_ARN = prism' fromARN toARN+{-# INLINE _ARN #-}++-- | Split a 'Text' into colon-separated parts.+--+-- >>> "foo:bar:baz" & colons . ix 1 .~ "quux"+-- "foo:quux:baz"+colons :: Iso' Text [Text]+colons = iso (T.splitOn ":") (T.intercalate ":")+{-# INLINE colons #-}++-- | Split a 'Text' into slash-separated parts.+--+-- >>> "foo/bar/baz" ^. slashes+-- ["foo","bar","baz"]+slashes :: Iso' Text [Text]+slashes = iso (T.splitOn "/") (T.intercalate "/")+{-# INLINE slashes #-}
+ src/Network/AWS/ARN/Lambda.hs view
@@ -0,0 +1,71 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_HADDOCK show-extensions #-}++-- |+--+-- Module : Network.AWS.ARN.Lambda+-- Copyright : (C) 2020-2021 Bellroy Pty Ltd+-- License : BSD-3-Clause+-- Maintainer : Bellroy Tech Team <haskell@bellroy.com>+-- Stability : experimental+module Network.AWS.ARN.Lambda+ ( -- * Functions+ Function (..),+ toFunction,+ fromFunction,++ -- ** Function Optics+ _Function,+ fName,+ fQualifier,+ )+where++import Control.Lens+import Data.Hashable (Hashable)+import Data.Maybe (maybeToList)+import Data.Text (Text)+import qualified Data.Text as T+import GHC.Generics (Generic)++-- $setup+-- >>> :set -XOverloadedStrings+-- >>> import Control.Lens++-- | An AWS Lambda function name, and optional alias/version qualifier.+--+-- >>> "function:helloworld" ^? _Function+-- Just (Function {_fName = "helloworld", _fQualifier = Nothing})+--+-- >>> "function:helloworld:$LATEST" ^? _Function+-- Just (Function {_fName = "helloworld", _fQualifier = Just "$LATEST"})+--+-- >>> "function:helloworld:42" ^? _Function+-- Just (Function {_fName = "helloworld", _fQualifier = Just "42"})+data Function = Function+ { _fName :: Text,+ _fQualifier :: Maybe Text+ }+ deriving (Eq, Ord, Hashable, Show, Generic)++$(makeLenses ''Function)++toFunction :: Text -> Maybe Function+toFunction t = case T.splitOn ":" t of+ ("function" : name : qual) ->+ Just (Function name) <*> case qual of+ [q] -> Just $ Just q+ [] -> Just Nothing+ _ -> Nothing+ _ -> Nothing++fromFunction :: Function -> Text+fromFunction f =+ T.intercalate ":" $+ ["function", _fName f] ++ maybeToList (_fQualifier f)++_Function :: Prism' Text Function+_Function = prism' fromFunction toFunction
+ test/Main.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF tasty-discover #-}
+ test/Network/AWS/ARN/Lambda/Test.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE OverloadedStrings #-}++module Network.AWS.ARN.Lambda.Test where++import Control.Lens (set, (^?))+import Network.AWS.ARN.Lambda+import Test.Tasty+import Test.Tasty.HUnit++test_all :: TestTree+test_all =+ testGroup+ "Network.AWS.ARN.Lambda"+ [ testGroup+ "Function"+ [ testGroup+ "parsing"+ [ testCase "unqualified function name" $+ "function:foo" ^? _Function @?= Just (Function "foo" Nothing),+ testCase "qualified function name" $+ "function:bar:3" ^? _Function @?= Just (Function "bar" $ Just "3")+ ],+ testGroup+ "updating"+ [ testCase "setting qualifier" $+ set (_Function . fQualifier) (Just "baz") "function:foo"+ @?= "function:foo:baz",+ testCase "unsetting qualifier" $+ set (_Function . fQualifier) Nothing "function:quux:42"+ @?= "function:quux"+ ]+ ]+ ]
+ test/Network/AWS/ARN/Test.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE OverloadedStrings #-}++module Network.AWS.ARN.Test where++import Control.Lens (over, preview, review)+import Data.Text (Text)+import Network.AWS.ARN+import Test.Tasty+import Test.Tasty.HUnit++test_all :: TestTree+test_all =+ testGroup+ "Network.AWS.ARN"+ [ testGroup+ "basic tests"+ [ testCase "inspect function name and alias of Lambda ARN" $+ (_arnResource <$> toARN aliasedLambdaSampleARN)+ @?= Just "function:the-coolest-function-ever:Alias",+ testCase "parses empty region OK" $+ (_arnRegion <$> toARN s3FileSampleARN) @?= Just "",+ testCase "parses empty account OK" $+ (_arnAccount <$> toARN s3FileSampleARN) @?= Just "",+ testCase "rejects non-ARNs" $+ toARN sampleNotAnARN @?= Nothing+ ],+ testGroup+ "optic tests"+ [ testCase "prism roundtrip" $+ (review _ARN <$> preview _ARN authorizerSampleARN)+ @?= Just authorizerSampleARN,+ testCase "edit path of Lambda Authorizer ARN" $+ over (_ARN . arnResource . slashes) (\parts -> take 2 parts ++ ["*"]) authorizerSampleARN+ @?= "arn:aws:execute-api:us-east-1:123456789012:my-spiffy-api/stage/*"+ ]+ ]++authorizerSampleARN :: Text+authorizerSampleARN = "arn:aws:execute-api:us-east-1:123456789012:my-spiffy-api/stage/GET/some/deep/path"++aliasedLambdaSampleARN :: Text+aliasedLambdaSampleARN = "arn:aws:lambda:us-east-1:123456789012:function:the-coolest-function-ever:Alias"++s3FileSampleARN :: Text+s3FileSampleARN = "arn:aws:s3:::an-okay-bucket/sample.txt"++sampleNotAnARN :: Text+sampleNotAnARN = "//library.googleapis.com/shelves/shelf1/books/book2"