amplify-swift/AmplifyPlugins/Auth/Sources/AWSCognitoAuthPlugin/StateMachine/hierarchical-state-machine-.../State.swift

174 lines
7.4 KiB
Swift

//
// Copyright Amazon.com Inc. or its affiliates.
// All Rights Reserved.
//
// SPDX-License-Identifier: Apache-2.0
//
/// A protocol describing the discrete attributes describing a system, and
/// providing mechanisms for changing those attributes in a predictable,
/// mechanistic way.
///
/// ### Properties of a State
///
/// States are immutable, mutually-exclusive, trees of value attributes:
/// - **Immutable**: States are not directly mutable. Instead, new states are
/// resolved by applying a State's `resolve` logic against the current value of the
/// state and a new `StateMachineEvent` to return a new `State` value.
/// - **Mutually exclusive**: A system's State is exactly equivalent to the set of
/// values that compose it. Thus, if two State values have identical properties,
/// they are themselves identical. From a practical standpoint, this means States
/// have value, not reference, semantics.
/// - **Trees**: Each State has its own set of attributes, and zero or more
/// substates. The "local" attributes of a State may be derived from the values of
/// its substates, or they may evolve independently in response to Events. A State
/// may have at most one "parent" State.
///
/// ### Resolving a State
///
/// States evolve mechanistically by applying resolution rules that evaluate
/// incoming Events against the current values of a State's attributes and
/// substates. The algorithm by which a State resolves an StateMachineEvent is:
///
/// - Traverse the State tree depth-first
/// - Resolve each leaf State (that is, each State that has no substates) by
/// invoking `resolve(oldState:event:)`. The return value of that method is a
/// `StateResolution` that contains both a new State, and a set of zero or more
/// `Effect`s. (See "Side effects" below)
/// - The parent State assigns the new substate value to the appropriate property,
/// and appends the returned Effects to the list of Effects to be returned in the
/// parent State's own `StateResolution`
/// - Each inner node resolves its own attributes by evaluating the new values of
/// its substates, the current values of its own properties, and the triggering
/// StateMachineEvent
/// - The inner node appends zero or more Effects to the list of effects to be
/// performed
/// - The inner node returns its new values (which are the new local values plus
/// the new values of all substates), and a list of Effects (which are the effects
/// requested by all substates, plus the effects requested by local state
/// resolution) in a `StateResolution`
/// - The process continues up to the "root" State
/// - The State Machine stores the new composite state as the new state of the
/// System
/// - The State Machine dispatches Effects for resolution and execution
///
/// ### Side effects
///
/// States may wish to perform "side effects" in response to an StateMachineEvent.
/// Side effects are interactions outside the assignment of a State's own property
/// values, such as:
/// - Emitting a new StateMachineEvent to indicate an important state change
/// - Interacting with an outside system such as making a network call or reading
/// from storage
/// - Starting or canceling a timer
///
/// Side effects are part of the return value of a State's
/// `resolve(oldState:event:)` method. They are resolved and executed by the State
/// Machine after the new State is fully resolved and applied. See `Effect` for
/// more details on Effect resolution
///
/// ### Example
///
/// Consider this State tree with the specified initial values:
///
/// ```
/// AuthState
/// isReady = false
/// authNState: AuthenticationState()
/// authZState: AuthorizationState()
///
/// AuthenticationState:
/// isSignedIn: false
/// lastSignedIn: Date? = nil
///
/// AuthorizationState:
/// isAuthorized = false
/// credentials: [String:String] = [:]
/// ```
///
/// ```
/// Event State Effects
/// --------------- ------------------------------- --------------------
/// Starting state { isReady: false,
/// [1] authNState: {
/// isSignedIn: false,
/// lastSignedIn: nil
/// },
/// authZState: {
/// isAuthorized: false,
/// credentials: {}
/// }
/// }
///
/// Event: { isReady: false,
/// signIn( authNState: { dispatch(didSignIn) [4]
/// user, isSignedIn: true, [3]
/// pass lastSignedIn: 11/26 9:43am
/// ) },
/// [2] authZState: {
/// isAuthorized: false,
/// credentials: {}
/// }
/// }
///
/// Event: { isReady: false,
/// didSignIn authNState: {
/// [5] isSignedIn: true,
/// lastSignedIn: 11/26 9:43am
/// },
/// authZState: { getCreds() {
/// isAuthorized: false, dispatch(didGetCreds(result))
/// credentials: {} }
/// } [6]
/// }
///
///
/// Event: { isReady: true, [9] dispatch(authIsReady)
/// didGetCreds( authNState: { [10]
/// creds isSignedIn: true,
/// ) lastSignedIn: 11/26 9:43am
/// [7] },
/// authZState: { [8]
/// isAuthorized: true,
/// credentials: {"token":"abc"}
/// }
/// }
///
/// ```
///
/// 1. The State Machine initializes. All states are initialized with their default
/// values.
/// 2. The State Machine receives a `signIn` event with the user/pass payload.
/// `AuthState` invokes `authNState.resolve(authNState, signIn)`
/// 3. For this exercise, we'll assume that the signIn event is a simple
/// validation, so `authNState` returns a new state with values of
/// `isSignedIn=true` and `lastSignedIn` set to the current timestamp.
/// 4. `authNState` returns both its new state plus an Effect to notify the the
/// state machine of the important state change
/// 5. The State Machine resolves the Effect from the last step by dispatching a
/// `didSignIn` event
/// 6. The `authNState` does not respond to this StateMachineEvent. However, the
/// `authZState` recognizes that it should take action on this, so it dispatches an
/// Effect to execute a `getCreds()` call. The completion block of that call will
/// dispatch the results in a `didGetCreds` StateMachineEvent
/// 7. The State Machine executes the Effect from the previous step, and receives a
/// `didGetCreds` when the completion block runs. The State Machine dispatches a
/// `didGetCreds` StateMachineEvent
/// 8. The `authZState` recognizes the `didGetCreds` event and updates its
/// attribute values to show `isAuthorized=true` and to store the value of the
/// retrieved credentials
/// 9. After `authZState` resolves, the parent `AuthState` inspects both the
/// `authNState.isSignedIn` and the `authZState.isAuthorized` and recognizes the
/// important state change
/// 10. The parent `AuthState` returns an Effect to dispatch an `authIsReady` event
///
/// - seealso: `Effect`
protocol State: Equatable {
var type: String { get }
}
struct StateID: Hashable {
let id: String
}