Jeder mit Zugriff auf Policy
Konstruktoren kann ein Policy
auseinander nehmen und es wieder zusammensetzen, möglicherweise in einer unsinnigen Art und Weise. Setzen Sie den Policy
Konstruktor nicht außerhalb dieses Moduls ein. Stellen Sie stattdessen eine smart constructor bereit, um Richtlinien zu erstellen, die garantiert gut formatiert sind und eine Monoid
-Schnittstelle bereitstellen, um sie zu erstellen, ohne Invarianten zu beschädigen. Wenn Sie den Typ Policy
beibehalten, wird sichergestellt, dass der gesamte Code, der zu unsinnigen Richtlinien führen könnte, in diesem Modul verbleibt.
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Policy (
Role(..),
Level(..),
Policy, -- keep Policy abstract by not exposing the constructor
can
) where
import Data.Semigroup (Semigroup, Max(..))
data Role = Public | Contributor | Owner
deriving (Eq, Ord, Bounded, Enum, Show, Read)
data Level = None | View | Edit
deriving (Eq, Ord, Bounded, Enum, Show, Read)
Im Folgenden werde ich GeneralizedNewtypeDeriving
bin mit ein Paar Monoid
Instanzen von base
zu leihen: the monoid for functions, die eine andere Monoid durch die Funktion Pfeil punktweise und the Max
newtype, die durch eine Ord
Instanz in eine Monoid
Instanz dreht hebt immer die größeren Argumente von mappend
wählen.
So Policy
‚s Monoid
Instanz wird automatisch die Reihenfolge der Level
verwalten, wenn Politik Komponieren: wenn zwei Richtlinien mit widersprüchlichen Ebenen zu einer bestimmten Rolle zu komponieren wir immer mehr permissiven wählen. Dies macht <>
eine additive Operation: Sie definieren Richtlinien durch Hinzufügen von Berechtigungen zu der "Standard" -Richtlinie, mempty
, die derjenige ist, der keine Berechtigungen an niemanden gewährt.
newtype Policy = Policy (Role -> Max Level) deriving (Semigroup, Monoid)
grant
ist ein Smart Konstruktor, die Politik, die die Ordnungseigenschaften von Role
und Level
respektieren produziert. Beachten Sie, dass ich Rollen mit >=
vergleiche, um sicherzustellen, dass das Gewähren einer Berechtigung für eine Rolle diese Berechtigung auch für mehr privilegierte Rollen gewährt.
grant :: Role -> Level -> Policy
grant r l = Policy (Max . pol)
where pol r'
| r' >= r = l
| otherwise = None
can
ist eine Beobachtung, die Ihnen sagt, ob eine Politik einen bestimmten Zugriffsebene auf eine bestimmte Rolle gewährt.Noch einmal verwende ich >=
um sicherzustellen, dass mehr-permissive Ebenen weniger permissive beinhalten.
can :: Role -> Level -> Policy -> Bool
(r `can` l) (Policy f) = getMax (f r) >= l
Ich war angenehm überrascht, wie wenig Code dieses Modul dauerte! Auf dem deriving
Mechanismus, besonders GeneralizedNewtypeDeriving
, ist eine wirklich nette Weise, die Typen verantwortlich für "langweiligen" Code zu setzen, damit Sie sich auf die wichtigen Sachen konzentrieren können.
Verwendung dieser Politik sieht wie folgt aus:
module Client where
import Data.Monoid ((<>))
import Policy
Sie die Monoid
-Klasse verwenden, können komplexe Richtlinien aus einfachen Ideen zu bauen.
ownerEdit, contributorView, myPolicy :: Policy
ownerEdit = grant Owner Edit
contributorView = grant Contributor View
myPolicy = ownerEdit <> contributorView
Und Sie können die can
Funktion zu testen Richtlinien verwenden.
canPublicView :: Policy -> Bool
canPublicView = Public `can` View
Zum Beispiel:
ghci> canPublicView myPolicy
False
Bin ich richtig, dass GHC Lage ist, eine Monoid-Instanz für 'Policy' abzuleiten, weil' Max a' ein Monoid ist und 'x -> Monoid y' ist ein Monoid. Ich kann auch meine eigene 'Instanz ableiten:' '' (Richtlinie a) 'mappend' (Richtlinie b) = Richtlinie $ \ r -> max (ar) (br)' '' – homam
Ja, obwohl GHC genau dasselbe erzeugt Code, warum also die Mühe machen, es zu schreiben? –
Dies ist eine sehr elegante Lösung. Vielen Dank! – homam