# `Ltix.LaunchClaims.Role`
[🔗](https://github.com/DecoyLex/ltix/blob/main/lib/ltix/launch_claims/role.ex#L1)

A parsed LTI role with type, name, and optional sub-role.

Launch claims include roles as URI strings. Use `parse/1` to convert
a single URI, or access them pre-parsed via `context.claims.roles`.

## Checking Roles

Predicate helpers like `instructor?/1` and `learner?/1` match on the
role name regardless of sub-role. This means `instructor?/1` returns
`true` for both a principal Instructor and an Instructor#TeachingAssistant.

    Role.instructor?(launch.claims.roles)

To check for a specific sub-role, use `teaching_assistant?/1` or
`has_role?/4`.

    Role.teaching_assistant?(roles)
    Role.has_role?(roles, :context, :instructor, :teaching_assistant)

To check for _only_ the principal role (excluding sub-roles), pass
`nil` as the sub-role.

    Role.has_role?(roles, :context, :instructor, nil)

## Role Types

  * `:context` - course-level roles (Instructor, Learner, etc.)
  * `:institution` - institution-level roles (Faculty, Student, etc.)
  * `:system` - system-level roles (Administrator, SysAdmin, etc.)

Use the filter helpers to narrow by type.

    Role.context_roles(roles)
    Role.institution_roles(roles)

## Examples

    iex> Ltix.LaunchClaims.Role.parse("http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor")
    {:ok, %Ltix.LaunchClaims.Role{type: :context, name: :instructor, sub_role: nil, uri: "http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor"}}

    iex> Ltix.LaunchClaims.Role.parse("Instructor")
    {:ok, %Ltix.LaunchClaims.Role{type: :context, name: :instructor, sub_role: nil, uri: "Instructor"}}

    iex> Ltix.LaunchClaims.Role.parse("http://example.com/unknown")
    :error

# `t`

```elixir
@type t() :: %Ltix.LaunchClaims.Role{
  name: atom(),
  sub_role: atom() | nil,
  type: :context | :institution | :system,
  uri: String.t()
}
```

# `t_without_uri`

```elixir
@type t_without_uri() :: %Ltix.LaunchClaims.Role{
  name: atom(),
  sub_role: atom() | nil,
  type: :context | :institution | :system,
  uri: nil
}
```

# `administrator?`

```elixir
@spec administrator?([t()]) :: boolean()
```

Check if any role is an institution Administrator, including sub-roles.

# `content_developer?`

```elixir
@spec content_developer?([t()]) :: boolean()
```

Check if any role is a context ContentDeveloper, including sub-roles.

# `context_administrator?`

```elixir
@spec context_administrator?([t()]) :: boolean()
```

Check if any role is a context Administrator, including sub-roles.

# `context_roles`

```elixir
@spec context_roles([t()]) :: [t()]
```

Filter to only context roles.

# `from_atom`

```elixir
@spec from_atom(atom()) :: t()
```

Build a role from a well-known atom.

Supports common context roles, one sub-role, and a few
institution and system roles. Raises `ArgumentError` for unknown atoms.

## Context roles

    iex> Ltix.LaunchClaims.Role.from_atom(:instructor)
    %Ltix.LaunchClaims.Role{type: :context, name: :instructor, sub_role: nil, uri: "http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor"}

    iex> Ltix.LaunchClaims.Role.from_atom(:learner)
    %Ltix.LaunchClaims.Role{type: :context, name: :learner, sub_role: nil, uri: "http://purl.imsglobal.org/vocab/lis/v2/membership#Learner"}

    iex> Ltix.LaunchClaims.Role.from_atom(:context_administrator)
    %Ltix.LaunchClaims.Role{type: :context, name: :administrator, sub_role: nil, uri: "http://purl.imsglobal.org/vocab/lis/v2/membership#Administrator"}

## Sub-roles

    iex> Ltix.LaunchClaims.Role.from_atom(:teaching_assistant)
    %Ltix.LaunchClaims.Role{type: :context, name: :instructor, sub_role: :teaching_assistant, uri: "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistant"}

## Institution roles

    iex> Ltix.LaunchClaims.Role.from_atom(:administrator)
    %Ltix.LaunchClaims.Role{type: :institution, name: :administrator, sub_role: nil, uri: "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Administrator"}

    iex> Ltix.LaunchClaims.Role.from_atom(:faculty)
    %Ltix.LaunchClaims.Role{type: :institution, name: :faculty, sub_role: nil, uri: "http://purl.imsglobal.org/vocab/lis/v2/institution/person#Faculty"}

## System roles

    iex> Ltix.LaunchClaims.Role.from_atom(:test_user)
    %Ltix.LaunchClaims.Role{type: :system, name: :test_user, sub_role: nil, uri: "http://purl.imsglobal.org/vocab/lti/system/person#TestUser"}

# `has_role?`

```elixir
@spec has_role?([t()], :context | :institution | :system, atom(), atom() | nil) ::
  boolean()
```

Check if any role matches the given type, name, and optional sub-role.

## Examples

    iex> {:ok, role} = Ltix.LaunchClaims.Role.parse("http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor")
    iex> Ltix.LaunchClaims.Role.has_role?([role], :context, :instructor)
    true

# `institution_roles`

```elixir
@spec institution_roles([t()]) :: [t()]
```

Filter to only institution roles.

# `instructor?`

```elixir
@spec instructor?([t()]) :: boolean()
```

Check if any role is a context Instructor, including sub-roles
like TeachingAssistant.

To check for _only_ the principal Instructor role (excluding sub-roles),
use `has_role?(roles, :context, :instructor, nil)`.

# `learner?`

```elixir
@spec learner?([t()]) :: boolean()
```

Check if any role is a context Learner, including sub-roles
like GuestLearner.

To check for _only_ the principal Learner role (excluding sub-roles),
use `has_role?(roles, :context, :learner, nil)`.

# `mentor?`

```elixir
@spec mentor?([t()]) :: boolean()
```

Check if any role is a context Mentor, including sub-roles.

# `parse`

```elixir
@spec parse(String.t(), keyword()) :: {:ok, t()} | :error
```

Parse a single role URI into a `%Role{}` struct.

Tries parsers whose URI prefix matches, then the built-in LIS parser, then
short context role names.

Returns `{:ok, %Role{}}` for recognized roles, `:error` for unknown URIs.

## Options

  * `:role_parsers` - map of URI prefix to parser module or function,
    merged over the application configuration. These parsers are tried
    longest prefix first, before the built-in LIS parser.

## Examples

    iex> Ltix.LaunchClaims.Role.parse("http://purl.imsglobal.org/vocab/lis/v2/membership#Learner")
    {:ok, %Ltix.LaunchClaims.Role{type: :context, name: :learner, sub_role: nil, uri: "http://purl.imsglobal.org/vocab/lis/v2/membership#Learner"}}

    iex> Ltix.LaunchClaims.Role.parse("http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistant")
    {:ok, %Ltix.LaunchClaims.Role{type: :context, name: :instructor, sub_role: :teaching_assistant, uri: "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistant"}}

# `parse_all`

```elixir
@spec parse_all([String.t()], keyword()) :: {[t()], [String.t()]}
```

Parse a list of role URIs, separating recognized from unrecognized.

Returns `{parsed_roles, unrecognized_uris}` where order is preserved.

## Options

  * `:role_parsers` - map of URI prefix to parser module or function,
    merged over the application configuration. These parsers are tried
    longest prefix first, before the built-in LIS parser.

## Examples

    iex> Ltix.LaunchClaims.Role.parse_all([])
    {[], []}

# `system_roles`

```elixir
@spec system_roles([t()]) :: [t()]
```

Filter to only system roles.

# `teaching_assistant?`

```elixir
@spec teaching_assistant?([t()]) :: boolean()
```

Check if any role is an Instructor#TeachingAssistant sub-role.

# `to_uri`

```elixir
@spec to_uri(t() | t_without_uri(), keyword()) :: {:ok, String.t()} | :error
```

Convert a `%Role{}` struct to its URI string.

Returns the role's URI, such as one parsed from a launch. For a role
without one, returns the URI from a role parser module that implements
`to_uri/1`, or `:error` when none can convert the role.

## Options

  * `:role_parsers` - map of URI prefix to parser module or function,
    merged over the application configuration, as for `parse/2`

## Examples

    iex> role = %Ltix.LaunchClaims.Role{type: :context, name: :instructor, sub_role: nil}
    iex> Ltix.LaunchClaims.Role.to_uri(role)
    {:ok, "http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor"}

    iex> role = %Ltix.LaunchClaims.Role{type: :context, name: :instructor, sub_role: :teaching_assistant}
    iex> Ltix.LaunchClaims.Role.to_uri(role)
    {:ok, "http://purl.imsglobal.org/vocab/lis/v2/membership/Instructor#TeachingAssistant"}

# `to_uri!`

```elixir
@spec to_uri!(t() | t_without_uri(), keyword()) :: String.t()
```

Same as `to_uri/2` but raises `ArgumentError` when no role parser can convert the role.

## Examples

    iex> role = %Ltix.LaunchClaims.Role{type: :context, name: :instructor, sub_role: nil}
    iex> Ltix.LaunchClaims.Role.to_uri!(role)
    "http://purl.imsglobal.org/vocab/lis/v2/membership#Instructor"

---

*Consult [api-reference.md](api-reference.md) for complete listing*
