module Ecluse.Core.Registry.WireSupport (
partitionLenientList,
Projection (..),
checkNameAgreement,
NameRefusal (..),
parseNameComponent,
nameComponentWith,
withinNameLimit,
) where
import Data.Aeson (Value)
import Data.Text qualified as T
import Ecluse.Core.Package (
InvalidEntry,
InvalidEntryKind,
PackageName,
isAsciiNameComponent,
mkInvalidEntry,
renderPackageName,
)
import Ecluse.Core.Registry (ParseError (ParseError))
import Ecluse.Core.Server.Path (isSafeComponent)
partitionLenientList :: InvalidEntryKind -> (Value -> Either String a) -> [(Text, Value)] -> ([(Text, a)], [InvalidEntry])
partitionLenientList :: forall a.
InvalidEntryKind
-> (Value -> Either String a)
-> [(Text, Value)]
-> ([(Text, a)], [InvalidEntry])
partitionLenientList InvalidEntryKind
kind Value -> Either String a
decode =
((Text, Value)
-> ([(Text, a)], [InvalidEntry]) -> ([(Text, a)], [InvalidEntry]))
-> ([(Text, a)], [InvalidEntry])
-> [(Text, Value)]
-> ([(Text, a)], [InvalidEntry])
forall a b. (a -> b -> b) -> b -> [a] -> b
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr (Text, Value)
-> ([(Text, a)], [InvalidEntry]) -> ([(Text, a)], [InvalidEntry])
step ([], [])
where
step :: (Text, Value)
-> ([(Text, a)], [InvalidEntry]) -> ([(Text, a)], [InvalidEntry])
step (Text
key, Value
value) ([(Text, a)]
kept, [InvalidEntry]
dropped) = case Value -> Either String a
decode Value
value of
Right a
a -> ((Text
key, a
a) (Text, a) -> [(Text, a)] -> [(Text, a)]
forall a. a -> [a] -> [a]
: [(Text, a)]
kept, [InvalidEntry]
dropped)
Left String
err -> ([(Text, a)]
kept, InvalidEntryKind -> Text -> Value -> Text -> InvalidEntry
mkInvalidEntry InvalidEntryKind
kind Text
key Value
value (String -> Text
forall a. ToText a => a -> Text
toText String
err) InvalidEntry -> [InvalidEntry] -> [InvalidEntry]
forall a. a -> [a] -> [a]
: [InvalidEntry]
dropped)
data Projection a
=
Projected a
|
NameMismatch Text
deriving stock (Projection a -> Projection a -> Bool
(Projection a -> Projection a -> Bool)
-> (Projection a -> Projection a -> Bool) -> Eq (Projection a)
forall a. Eq a => Projection a -> Projection a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: forall a. Eq a => Projection a -> Projection a -> Bool
== :: Projection a -> Projection a -> Bool
$c/= :: forall a. Eq a => Projection a -> Projection a -> Bool
/= :: Projection a -> Projection a -> Bool
Eq, Int -> Projection a -> ShowS
[Projection a] -> ShowS
Projection a -> String
(Int -> Projection a -> ShowS)
-> (Projection a -> String)
-> ([Projection a] -> ShowS)
-> Show (Projection a)
forall a. Show a => Int -> Projection a -> ShowS
forall a. Show a => [Projection a] -> ShowS
forall a. Show a => Projection a -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: forall a. Show a => Int -> Projection a -> ShowS
showsPrec :: Int -> Projection a -> ShowS
$cshow :: forall a. Show a => Projection a -> String
show :: Projection a -> String
$cshowList :: forall a. Show a => [Projection a] -> ShowS
showList :: [Projection a] -> ShowS
Show)
checkNameAgreement :: PackageName -> PackageName -> a -> Projection a
checkNameAgreement :: forall a. PackageName -> PackageName -> a -> Projection a
checkNameAgreement PackageName
requestedName PackageName
reportedName a
projected
| PackageName
reportedName PackageName -> PackageName -> Bool
forall a. Eq a => a -> a -> Bool
== PackageName
requestedName = a -> Projection a
forall a. a -> Projection a
Projected a
projected
| Bool
otherwise = Text -> Projection a
forall a. Text -> Projection a
NameMismatch (PackageName -> Text
renderPackageName PackageName
reportedName)
data NameRefusal
=
NameEmpty
|
NameNotAscii
|
NameUnsafeComponent
deriving stock (NameRefusal -> NameRefusal -> Bool
(NameRefusal -> NameRefusal -> Bool)
-> (NameRefusal -> NameRefusal -> Bool) -> Eq NameRefusal
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: NameRefusal -> NameRefusal -> Bool
== :: NameRefusal -> NameRefusal -> Bool
$c/= :: NameRefusal -> NameRefusal -> Bool
/= :: NameRefusal -> NameRefusal -> Bool
Eq, Int -> NameRefusal -> ShowS
[NameRefusal] -> ShowS
NameRefusal -> String
(Int -> NameRefusal -> ShowS)
-> (NameRefusal -> String)
-> ([NameRefusal] -> ShowS)
-> Show NameRefusal
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> NameRefusal -> ShowS
showsPrec :: Int -> NameRefusal -> ShowS
$cshow :: NameRefusal -> String
show :: NameRefusal -> String
$cshowList :: [NameRefusal] -> ShowS
showList :: [NameRefusal] -> ShowS
Show)
parseNameComponent :: Text -> Either NameRefusal Text
parseNameComponent :: Text -> Either NameRefusal Text
parseNameComponent Text
component
| Text -> Bool
T.null Text
component = NameRefusal -> Either NameRefusal Text
forall a b. a -> Either a b
Left NameRefusal
NameEmpty
| Bool -> Bool
not (Text -> Bool
isAsciiNameComponent Text
component) = NameRefusal -> Either NameRefusal Text
forall a b. a -> Either a b
Left NameRefusal
NameNotAscii
| Bool -> Bool
not (Text -> Bool
isSafeComponent Text
component) = NameRefusal -> Either NameRefusal Text
forall a b. a -> Either a b
Left NameRefusal
NameUnsafeComponent
| Bool
otherwise = Text -> Either NameRefusal Text
forall a b. b -> Either a b
Right Text
component
nameComponentWith :: Text -> (Text -> Bool) -> Text -> Either ParseError Text
nameComponentWith :: Text -> (Text -> Bool) -> Text -> Either ParseError Text
nameComponentWith Text
noun Text -> Bool
usable Text
component = do
onFloor <- (NameRefusal -> ParseError)
-> Either NameRefusal Text -> Either ParseError Text
forall a b c. (a -> b) -> Either a c -> Either b c
forall (p :: * -> * -> *) a b c.
Bifunctor p =>
(a -> b) -> p a c -> p b c
first NameRefusal -> ParseError
floorRefusal (Text -> Either NameRefusal Text
parseNameComponent Text
component)
if usable onFloor
then Right onFloor
else Left unusable
where
floorRefusal :: NameRefusal -> ParseError
floorRefusal :: NameRefusal -> ParseError
floorRefusal = \case
NameRefusal
NameEmpty -> Text -> ParseError
ParseError (Text
"empty " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
noun)
NameRefusal
NameNotAscii -> Text -> ParseError
ParseError (Text
"non-ASCII " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
noun Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
": " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
forall b a. (Show a, IsString b) => a -> b
show Text
component)
NameRefusal
NameUnsafeComponent -> ParseError
unusable
unusable :: ParseError
unusable :: ParseError
unusable = Text -> ParseError
ParseError (Text
"unusable " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
noun Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
": " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
forall b a. (Show a, IsString b) => a -> b
show Text
component)
withinNameLimit :: Text -> Int -> Text -> Either ParseError ()
withinNameLimit :: Text -> Int -> Text -> Either ParseError ()
withinNameLimit Text
noun Int
limit Text
raw
| Text -> Int -> Ordering
T.compareLength Text
raw Int
limit Ordering -> Ordering -> Bool
forall a. Eq a => a -> a -> Bool
== Ordering
GT = ParseError -> Either ParseError ()
forall a b. a -> Either a b
Left (Text -> ParseError
ParseError Text
overLong)
| Bool
otherwise = () -> Either ParseError ()
forall a b. b -> Either a b
Right ()
where
overLong :: Text
overLong :: Text
overLong = Text
noun Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" over " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Int -> Text
forall b a. (Show a, IsString b) => a -> b
show Int
limit Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" characters, starting " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
forall b a. (Show a, IsString b) => a -> b
show (Int -> Text -> Text
T.take Int
24 Text
raw)