# Define a role

A role describes a part of an app that more than one module could implement, so that other modules can count on it without naming the implementation. The built-in roles live in `smf_contracts`. A role of yours lives in a package of its own, which its providers and the modules that use it depend on.

Make a part a role when other modules count on it, even if a single module provides it today, so that another provider can join later without a change to the modules that use the part. The sockets of a module are for what only makes sense with that one module, and only the modules that depend on it directly fill them; see [Write an infrastructure module](https://doc.saymyframe.com/extending/infrastructure-module.md#building-on-another-module).

The examples come from the fixtures of `smf_pipeline`, which define two roles outside `smf_contracts`. Continuous integration generates apps with them and analyzes those apps.

## The role class

A role is a subclass of `Role<D>` with a single constant instance. `D` is the type of the data that modules give the role, and a role that takes no data uses `NoDsl`.

```dart
/// The clock role; see [ClockRole].
const clockRole = ClockRole._();

/// The clock of the app, with the time zones the modules ask for as data
/// and the ticks of the modules as a socket.
final class ClockRole extends Role<String> {
  const ClockRole._();

  /// Statements that run on every tick, in `tick()`.
  static const ticks = SocketRef<CodeSocket>.role(
    clockRole,
    'ticks',
    CodeSocket(),
  );

  /// `Clock createClock()`, which every provider generates.
  static const createClock = RequiredFunction(
    'createClock',
    path: 'lib/core/clock/clock_factory.dart',
    returnType: 'Clock',
  );

  @override
  String get id => 'clock';

  @override
  String get description => 'Clock';

  @override
  RoleCardinality get cardinality => RoleCardinality.atMostOne;

  @override
  List<SocketRef> get sockets => const [ticks];

  @override
  RoleInterface get interface => const RoleInterface(
        files: ['lib/core/clock/clock.dart'],
        symbols: [createClock],
      );

  @override
  RoleTemplate<String> get template => const _ClockTemplate();
}
```

| Member | What it declares |
| --- | --- |
| `id` | A lower snake_case id, unique among the roles and modules of a command. It names the presence flag `has_clock`, the tags of the role's sockets, and the owner `role:clock` of the template's files. |
| `description` | The name of the role in the questions of `smf create`, such as "Clock: which module provides it?". The messages of `smf create` name the role by it, followed by "role", in lower case unless it starts with an acronym, such as "the clock role". |
| `cardinality` | `exactlyOne`, `atMostOne` or `many` providers in an app. A role with exactly one provider is added to every app. |
| `requires`, `uses` | Roles that must be present with this one, and roles it works with when present. The hooks of the role and of its providers can read their data. Every provider requires and uses them too. |
| `sockets`, `socketFamilies` | The places where other modules put code (see [Sockets and contributions](https://doc.saymyframe.com/concepts/sockets.md)). A family has one socket per key, such as the annotations of each screen of the router. |
| `interface` | The files that the role's template generates, and the symbols that every provider must generate, such as `createClock()`. `providerFiles` lists files that every provider must generate and that no symbol can name, such as the images of the app icon role. |
| `options` | Options of `smf create` that the role reads, such as `--start` of the router. An option whose every value makes another app is a [mode option](#a-mode-option). |
| `template` | The code of the role that does not depend on its provider. |
| `moduleRules`, `structuralRules` | Checks of the modules and of the generated code. |

The symbols are functions, classes and extensions at the top level of the file at their `path`. A function or a class is callable the way other code calls it: with its `positionalArguments` and `namedParameters`, and no other required parameters. A `RequiredFunction` also returns its `returnType`, if it has one. The parameters of a `RequiredClass` are those of its unnamed constructor, which is `const` when `constConstructor` is set, and the class declares each of its `getters` itself, as a public instance field or getter, since other code reads them from its instances. An inherited member does not count. The layout role requires `AppShell` this way, with the getters `destinations`, `currentIndex` and `onSelect`.

A `RequiredExtension` is an extension on the type that its `on` names, which declares each of its `getters` as an instance getter. Other code never calls an extension. It names the extension only to show or to hide it in an import, and reads the getters from the objects that the extension is on. The localization role requires the extension `AppTexts` on `BuildContext` this way, with the getter `l10n`, so that every module reads a text as `context.l10n.<getter>`. The contract harness checks the symbols in the app of every provider.

`openToAllModules` makes the sockets and symbols of a role usable by every module without declaring it. Only the app entry is open. Keep a role of yours closed, so that a module that uses it has to say so.

## Data

A module that declares the role gives it data in the role's type:

```dart
clockRole.data("Europe/Kyiv's zone"),
```

Data applies only when the role is present, and only its hooks see it, so a module that only uses the role can contribute data unconditionally. The hooks get the data of every module, in the order the modules were chosen, with the module that contributed each. Choose a type that describes what modules need rather than how a provider implements it, the way the router takes the routes of a feature and not go_router code.

## The template

The template contributes the files of the role's interface and renders what depends on the data. Its hooks run at four stages of the pipeline:

| Hook | Stage | Does |
| --- | --- | --- |
| `contribute(context)` | Collection | Returns the template's own contributions, such as the brick with the interface. |
| `validate(input)` | Validation | Checks the data of all modules together and returns problems. |
| `manualSteps(input)` | Validation | Returns the [steps by hand](https://doc.saymyframe.com/extending/infrastructure-module.md#steps-that-depend-on-the-data-of-a-role) that depend on the data, such as a step in a web console that only some of the data needs. |
| `choose(context)` | Choices | Asks the user, or reads the role's options, for a decision the data leaves open. |
| `render(input)` | Rendering | Returns fragments for sockets, variables for the template's bricks, and files whose paths depend on the data. |

```dart
final class _ClockTemplate extends RoleTemplate<String> {
  const _ClockTemplate();

  @override
  List<Contribution> contribute(ModuleContext context) =>
      [BrickContribution(clockRoleBundle)];

  @override
  RoleOutput render(RoleHookInput<String> input) => RoleOutput(
        vars: {
          'zones': [
            for (final data in input.data) SmfNames.dartString(data.value),
          ].join(', '),
        },
      );
}
```

The template's brick holds the interface and the tag of the role's socket:

```dart title="bricks/clock_role/__brick__/lib/core/clock/clock.dart"
/// The clock of the app.
abstract interface class Clock {
  /// The time zones the modules of the app asked for.
  List<String> get zones;
}

/// The time zones the modules of the app asked for.
const clockZones = <String>[{{{zones}}}];

/// Runs the ticks of the modules of the app.
void tick() {
{{{smf_clock__ticks}}}
}
```

The hooks of different roles never see each other's results, because all data is collected before `validate` runs. A hook reads only the data of its own role and of the roles its role requires or uses.

A brick has the same files in every app. When the number of the files, or their paths, depend on the data of the role or on its choice, the render hook returns them in `RoleOutput.files`, each as its text by its path in the app; see [Files of render hooks](https://doc.saymyframe.com/extending/templates.md#files-of-render-hooks).

### What a template contributes

A template contributes like a module. Besides its bricks, it can put code into the sockets of the app entry and of the roles that its role requires or uses, give those roles data, and add dependencies. The template of the theme role keeps the theme mode this way, whichever module provides the theme:

```dart
  @override
  List<Contribution> contribute(ModuleContext context) => [
        BrickContribution(themeRoleBundle),
        AppEntryRole.agentSections.entry(
          themeRole.description,
          AgentNote.ofRole(_agentNote),
        ),
        // ... the scope around the root, and its theme, darkTheme and themeMode.
        const SocketContribution.item(
          PreferencesRole.restorers,
          Fragment('restoreAppThemeMode', imports: [_themeMode]),
        ),
        // The entry of the settings screen, with its texts: only an app with
        // a settings screen gets the file of the entry, and only then does
        // the app need the texts.
        BrickContribution(
          themeRoleSettingBundle,
          when: const {settingsScreenRole},
        ),
        settingsScreenRole.data(
          const SettingsEntry(
            widget: TypeRef(
              'ThemeModeSetting',
              import: ImportRef.app('core/theme/theme_mode_setting.dart'),
            ),
          ),
        ),
        localizationRole.data(
          TextsData([..._texts.values]),
          when: const {settingsScreenRole},
        ),
      ];
```

- The role lists the preferences role in `requires`, and the settings screen role and the localization role in `uses`, so its template may contribute to them.
- A file that only some apps have is a brick of its own, contributed with `when`, as the entry of the settings screen is here.
- The template reads its texts with `localizationRole.expressionOf` in its render hook; see [Texts](https://doc.saymyframe.com/extending/texts.md#texts-of-the-template-of-a-role).
- `AgentNote.ofRole` adds the note of the role to the guide for coding agents of the app, under the description of the role. It tells what holds whichever module provides the role: where the files of the role are, how code uses it, and what code must not do. It names no provider and no package, which a test checks with [`roleNoteProblems`](https://doc.saymyframe.com/extending/testing.md#the-notes-of-roles). Only the template of a role contributes such a note, and a provider adds a note of its own under the same heading; see [Notes for coding agents](https://doc.saymyframe.com/extending/infrastructure-module.md#notes-for-coding-agents).

[How settings work](https://doc.saymyframe.com/concepts/settings.md#the-theme-mode-a-setting-of-a-role) describes the theme role as an example, and [Provide the theme](https://doc.saymyframe.com/extending/provide-theme.md) what is left for its provider.

## Choices and options

When the data leaves a decision open, `choose` makes it. It reads the role's options and, in a run that can ask, asks the user. The router chooses the start screen this way: its `choose` takes `--start` when given and returns the only start candidate when there is one. Otherwise it asks, or, in a run without a terminal, throws an `SmfUsageException` that names the option.

A role whose `choose` asks must also implement `optionsOf(choice)`, which returns the option values that make the same choice without asking, such as `--start /home`. The contract harness answers the questions of a role like a user who presses Enter, then checks that these options reproduce the answer in a run without a terminal, so choices must compare by value.

What a role chooses reaches the render hooks of its template and of its providers, as `RoleHookInput.choice`. The contributions of a module are the same whatever the role chose, but for a brick or data of a role under a [condition on the choice](#a-condition-on-a-choice).

### Warnings

A choice may leave the app other than the user may expect, such as an app that no route starts. The hook then reports a warning through `RoleChoiceContext.warn`, with a code of its own role:

```dart
  /// The code of the warning that no route can start the app, which then
  /// starts on the fallback start screen of the app entry role: the app has
  /// routes, but none is a start candidate and `--start` names none.
  static const noStartWarning = 'router.no_start';
```

```dart
context.warn(
  SmfIssue.warning(
    'No route is marked as a start candidate, so the app starts on '
    'its fallback screen. Choose a start route with '
    '--${RouterRole.startOption.name}.',
    code: RouterRole.noStartWarning,
  ),
);
```

- A code is the id of the role, a dot and a name in lower snake_case, so that it says which role reported the warning and two roles never report the same code. The role publishes each code as a constant, and a test or a tool names a warning by its code rather than by its message. The codes that start with `pipeline.` are the pipeline's own.
- The pipeline stops on a warning with a code of another role or of another form. It reports the warnings of a hook as warnings of the template of the role, once the hook has chosen, and `smf create --strict` stops on them before the app is rendered; see [lenient and strict](https://doc.saymyframe.com/guides/smf-create.md#lenient-and-strict).
- A warning that depends on the data alone comes from `validate` instead, which runs before the machine is checked. Its `SmfIssue.warning` takes a code too.
- A hook does not warn through the logger of its environment, whose warnings have no code: the contract harness reports that as an error of the template. A warning about the machine, `SmfIssue.machineWarning`, is for the checks of the pipeline alone.

### A condition on a choice

Some files belong only in the apps where a role chose something, such as a screen that only an app without a start route needs. `when` tells only which roles an app has, so a role publishes a condition on its choice as a constant instead, a `ChoiceCondition`. The router publishes `RouterRole.noStartRoute`, which holds in an app that no route starts, and in an app without a router. The clock role of the fixtures could publish one for its hours:

```dart
  static const twelveHours = ChoiceCondition(
    clockRole,
    'the clock shows 12 hours',
    holdsFor: _showsTwelveHours,
    withoutRole: false,
  );

bool _showsTwelveHours(Object? choice) => choice == '12';
```

A module that uses the clock then contributes the files of a dial with 12 hours only to the apps that need them, with `BrickContribution(dialBundle, whenChosen: {ClockRole.twelveHours})`. A `BrickContribution` and data of a role take `whenChosen`, and so does a symbol of the interface of a role. `AppEntryRole.fallbackStartScreen` is required only where `RouterRole.noStartRoute` holds, and an app where it does not hold must have no file at its path. So every provider of the app entry contributes the screen under that condition, as `flutter_core` does.

- A condition names a role that the contributor provides, requires or uses, as `when` does, and for the template of a role the role itself or a role that it requires or uses.
- Data of a role takes no condition of the same role, which chooses from its data. Conditions on the data of roles must not form a cycle, since neither role could choose first.
- A brick with conditions holds no tag of a socket: an app where the conditions do not hold leaves the brick out, and what the socket got would be lost with it.
- The contract harness answers the questions of a role as a user who presses Enter. So it checks both sides of a condition only when the data of the modules or a mode option drives the choice.

### A mode option

Some options change what kind of app is generated: an app that every user signs in to is another app than one that lets a guest in. A role declares such an option as a mode option, `RoleOption.mode`, with a closed list of values. The clock role of the fixtures has one for the hours that its clock shows:

```dart
  /// `--clock-hours`, how many hours the clock shows: 24, the default, or
  /// 12.
  static const hoursOption = RoleOption.mode(
    name: 'clock-hours',
    help: 'How many hours the clock of the app shows: 24, the default, or '
        '12.',
    values: ['24', '12'],
  );
```

With a mode option, the role states four things, and the contract harness holds its template to them:

| What the role states | What it means for the template |
| --- | --- |
| The code of the app depends on the value. | The render hooks of the template and of the providers read the choice. |
| The first value is the default, the value of an app that got none. | `choose` takes the value that the option gives, and the first one when the option is not given. Without a terminal it neither fails nor asks for the lack of the option, and at its question a user who presses Enter gets the first value too. |
| The option reproduces every choice. | `optionsOf` gives the option with its value for every choice, also for one that the template made without asking. |
| A run in a terminal and a run without one make the same choice. | The choices compare by value. |

So no run needs the option, and a script written before the role had it generates the same app. This is the template of the clock role:

```dart
  @override
  Future<Object?> choose(RoleChoiceContext<String> context) async {
    const option = ClockRole.hoursOption;
    final values = option.allowed!;
    final given = context.option(option.name);
    if (given != null) return given;
    final environment = context.environment;
    if (!environment.interactive) return values.first;
    return environment.prompter.select(
      'How many hours does the clock show?',
      values,
      defaultValue: values.first,
    );
  }

  @override
  Map<String, String> optionsOf(Object? choice) =>
      {ClockRole.hoursOption.name: choice! as String};
```

The value reaches the code of the app in three ways:

- The template of the role reads the choice in its render hook and writes it into a file of the role as a constant, here `clockHours`.
- A provider reads it in its render hook, through a function that the role offers, here `clockRole.hoursIn(input)`, and renders the code of that value alone.
- A module that is no provider has no hook, so its code is the same in every app. It reads the constant when the app runs and branches on it.

Every value gets apps in the tests. The harness builds one more app of each provider of the role for each value after the first; see [Test a module](https://doc.saymyframe.com/extending/testing.md#the-modes-of-a-role). The values become part of the names of those apps. So a command refuses a registry with a mode option that has fewer than two values, a value twice, or a value that is not of lower-case letters and digits with single hyphens between them; see [a command of your own](https://doc.saymyframe.com/extending/publishing.md#a-command-of-your-own).

Among the built-in roles, the auth role has a mode option, `--auth-mode`, with the values `required`, `guest` and `anonymous`. Its template writes the choice into the app as the constant `authMode`; see [Sign-in](https://doc.saymyframe.com/guides/sign-in.md#the-mode).

## Rules

Module rules run in the validation stage, for every module that declares the role, over the role's data and the module's contributions. The router checks there that the routes of a module have valid paths and parameters, and that the template of every screen has its annotation tags.

Structural rules run in the contract harness, over the text of every generated file and an index of every Dart file with its imports, declarations, calls, property accesses and constructors. A call comes with the declaration and the member it is in, and a method with its parameters. The DI role checks there that only composition files call `resolve`, and the app entry role that the root `MaterialApp` is created in the `build` of a widget.

A structural rule sees the data of its own role. With `StructuralRuleInput.inputOf(role)` it also reads what the hooks of another role of the app get, when that role requires or uses the role of the rule. The localization role does this to tell which texts the template of such a role may read: its own, and those in the data that the modules give it. `inputOf` throws for a role that does not build on the role of the rule, and for a role that the app lacks.

A rule is a constant with an id, a sentence that says what it requires, and a top-level function that returns problems. A problem names the module at fault, so that `smf create` can leave that module out.

A role can also have rules for the modules of a kind, such as the file where they may resolve services. The role defines each as a subclass of `KindRule`, and a kind lists those that its modules follow in `ModuleKind.roleRules`. A check of the role reads the one it needs from the kind of a module with `ruleOf<T>()`, which returns `null` when the kind lists none. The pipeline applies none of these rules itself. The DI role defines `CompositionFile` this way, and `ModuleKinds.feature` lists `CompositionFile('lib/features/<id>/<id>_composition.dart')`.

## Conditions for routes

A role that knows something that only some screens of an app need can publish it as a [condition for routes](https://doc.saymyframe.com/concepts/conditions.md), a constant of its class. The badge role of the fixtures publishes two:

```dart
  /// What a user needs for the routes of the holders of a badge. A module
  /// that requires the role says whether the user holds one, with a guard
  /// of the routes that stands for the condition.
  static const holder = RouteCondition(badgeRole, 'holder');
```

A feature then asks for the condition on a route, and a module that requires or provides the role declares the guard that stands for it. The two know the role and not each other; see [Write a feature module](https://doc.saymyframe.com/extending/feature-module.md#a-route-that-asks-for-a-condition).

- The role generates nothing for a condition. In the app, the guard has the names of the routes that ask, which the router role collects.
- The function of the guard is in a file of the module that declares the guard. The template of the router role imports the files of the modules that give it data, and no file of the template of another role. So the role offers what such a function returns, as the auth role offers `appSession.hasAccount`, and the module wraps it in a function of its own.
- An app may have no guard for a condition. Its routes that ask then show like any other, and the router role reports nothing.

A role that wants to warn of such an app requires or uses the router role. Its hooks then read the routes and the guards of the app through `routerRole.facadeOf(input)`. The auth role does so in the `validate` hook of its template:

```dart
List<SmfIssue> _openAccountRoutes(RoleHookInput<RoleImplementation> input) {
  final facade = routerRole.facadeOf(input);
  final asking = facade.routesAsking(AuthRole.account);
  if (asking.isEmpty || facade.guardFor(AuthRole.account) != null) {
    return const [];
  }
  // ... a warning that names each route of `asking`.
}
```

A role that uses the router role this way makes every provider of it use the router role too. So the contract harness builds the apps of such a provider with each router of its registry, and without one.

## Providers and users of the role

A provider lists `RoleProvider.plain(clockRole)` in its providers when its bricks alone implement the role, and subclasses `RoleProvider<String>` to validate the data or render it (see [Provide a role](https://doc.saymyframe.com/extending/role-provider.md)). A module that uses the role declares it in `uses`. It names the role in the `when` of the contributions whose code refers to the role's symbols. Its templates refer to those symbols only inside `{{#has_clock}}`, or through [a variable whose code depends on the role](https://doc.saymyframe.com/extending/templates.md#variables-that-depend-on-a-role). The fixtures define a second role, `badgeRole`, and a module that uses both adds this line to start-up only in an app with a badge:

```dart
SocketContribution.code(
  AppEntryRole.bootstrapLate,
  Fragment(
    'debugPrint(createBadge().labels.join());',
    imports: [
      const ImportRef('package:flutter/foundation.dart'),
      BadgeRole.createBadge.importRef,
    ],
  ),
  when: const {badgeRole},
),
```
