ryo33 / narrative

An immensely simple library for story-driven development
12 stars 0 forks source link

Reuse other stories as nested #11

Open ryo33 opened 3 weeks ago

ryo33 commented 3 weeks ago

Should we treat nestable stories as special to prevent misuse by users? (e.g. making it impossible to implement the story as a regular story)

ryo33 commented 3 weeks ago

My current idea

How it looks like

#[narrative::story("a sample story")]
trait SampleStory {
    #[step("aaaa")]
    fn aaaa();

    #[sub_story("do the following steps")]
    fn run_substory<T: MySubStory>(a: MySubStory);
}

impl SampleStory for MyEnv {
    type Error = ();

    fn aaaa(&mut self) -> Result<(), Self::Error> {
        todo!()
    }

    type MySubStory = MySubStoryEnv; // where MySubStory: MySubStory<Error = Into<Self::Error>>

    // default implementation is provided automatically but overridable.
    // fn run_substory(a: Self::MySubStory) -> Result<(), Self::Error> {
    //     Ok(a.run_all()?)
    // }
}

Story Context API

Steps in the sub-story are flattened with other normal steps in the story context API. The caller can refer to the parent step by calling a Step::parent_step(&self) -> Option<Step> in the Step trait.

Compared to the Step::sub_steps(&self) -> &[Step] approach, it prevents users from forgetting to run steps in the substory. Users can still know the hierarchy by calling step.parent_step(). Or it may be an option to have a dedicated new API for users who want to know the hierarchy.

Story Implementation

To preserve the run_all functionality, we need to identify a concrete implementation for each sub story in a implementation of the super story.