# `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.

Roles arrive in launch claims 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 a context Administrator, including sub-roles.

# `content_developer?`

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

Check if any role is a context ContentDeveloper, 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 handful of
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"}

## 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(: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 registered parsers by URI prefix, then falls back to short
context role names. The LIS vocabulary parser is registered by default.

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

## Options

  * `:parsers` — map of URI prefix to parser module or function
    (defaults to application config, with LIS as a fallback)

## 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

  * `:parsers` — map of URI prefix to parser module or function
    (defaults to application config, with LIS as a fallback)

## 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_without_uri()) :: {:ok, String.t()} | :error
```

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

Tries each registered parser's `to_uri/1` callback until one succeeds.
The LIS parser is tried by default.

## 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"}

---

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