Created
October 11, 2023 17:18
-
-
Save arosien/5b912b6b36415d6a146f839baf154af9 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//> using scala 2 | |
//> using toolkit typelevel:latest | |
// https://practicalocaml.com/how-to-build-type-safe-state-machines-using-type-state/ | |
// type 'state fsm = { state: 'state } | |
case class Fsm[State](state: State) | |
/* | |
type id | |
type scope | |
type 'resource granted = { resource : 'resource } | |
type denied = { reason : string } | |
type requested = { scopes : scope list } | |
*/ | |
case class Id(value: String) | |
case class Scope(value: String) | |
case class Granted[Resource](resource: Resource) | |
case class Denied(reason: String) | |
case class Requested(scopes: List[Scope]) | |
/* | |
type ('state, 'resource) t = { | |
(* the state of the permission request *) | |
state : 'state; | |
(* other shared metadata *) | |
resource_id : id; | |
user_id : id; | |
} | |
*/ | |
case class T[State, Resource](state: State, resourceId: Id, userId: Id) { | |
/* | |
let make ~resource_id ~user_id ~scopes = | |
{ state = { scopes }; resource_id; user_id } | |
(* TODO(@you): you can implement here the logic for checking | |
* if you actually have permissions for this request :) | |
*) | |
let run_request : (requested, 'resource') t -> ('resource, string) result = | |
fun _t -> Error "unimplemented!" | |
let request_access t = | |
match run_request t with | |
| Ok resource -> Ok { t with state = { resource } } | |
| Error reason -> Error { t with state = { reason } } | |
let reason { state = { reason }; _ } = reason | |
let with_resource { state = { resource }; _ } fn = fn resource | |
let get { state = {resource}; _ } = resource | |
*/ | |
private def run(implicit ev: State =:= Requested): Either[String, Resource] = | |
Left("Unimplemented") | |
def requestAccess(implicit | |
ev: State =:= Requested | |
): Either[T[Denied, Resource], T[Granted[Resource], Resource]] = | |
run match { | |
case Left(reason) => Left(copy(state = Denied(reason))) | |
case Right(resource) => Right(copy(state = Granted(resource))) | |
} | |
def reason(implicit ev: State =:= Denied): String = | |
ev(this.state).reason | |
def resource(implicit ev: State =:= Granted[Resource]): Resource = | |
withResource(identity) | |
def withResource[B](f: Resource => B)(implicit | |
ev: State =:= Granted[Resource] | |
): B = | |
f(ev(this.state).resource) | |
} | |
object T { | |
def apply[Resource]( | |
resourceId: Id, | |
userId: Id, | |
scopes: List[Scope] | |
): T[Requested, Resource] = | |
T(Requested(scopes), resourceId, userId) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment