Current section
Files
Jump to
Current section
Files
lib/codex/protocol/request_permissions.ex
defmodule Codex.Protocol.RequestPermissions do
@moduledoc """
Types for app-server request-permissions approval requests and responses.
"""
alias __MODULE__, as: RequestPermissions
defmodule AdditionalNetworkPermissions do
@moduledoc "Additional network permissions requested or granted for a turn or session."
use TypedStruct
typedstruct do
field(:enabled, boolean() | nil)
end
@spec from_map(map() | keyword() | t() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(%__MODULE__{} = permissions), do: permissions
def from_map(data) when is_list(data), do: data |> Map.new() |> from_map()
def from_map(data) when is_map(data) do
%__MODULE__{
enabled: RequestPermissions.fetch_any(data, ["enabled", :enabled])
}
end
@spec to_map(t() | nil) :: map() | nil
def to_map(nil), do: nil
def to_map(%__MODULE__{} = permissions) do
%{}
|> RequestPermissions.put_optional("enabled", permissions.enabled)
end
end
defmodule AdditionalFileSystemPermissions do
@moduledoc "Additional filesystem permissions requested or granted for a turn or session."
use TypedStruct
typedstruct do
field(:read, [String.t()] | nil)
field(:write, [String.t()] | nil)
end
@spec from_map(map() | keyword() | t() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(%__MODULE__{} = permissions), do: permissions
def from_map(data) when is_list(data), do: data |> Map.new() |> from_map()
def from_map(data) when is_map(data) do
%__MODULE__{
read: RequestPermissions.fetch_list(data, ["read", :read]),
write: RequestPermissions.fetch_list(data, ["write", :write])
}
end
@spec to_map(t() | nil) :: map() | nil
def to_map(nil), do: nil
def to_map(%__MODULE__{} = permissions) do
%{}
|> RequestPermissions.put_optional("read", permissions.read)
|> RequestPermissions.put_optional("write", permissions.write)
end
end
defmodule AdditionalMacOsPermissions do
@moduledoc "Additional macOS permissions requested for a turn or session."
use TypedStruct
typedstruct do
field(:preferences, String.t() | nil)
field(:automations, String.t() | map() | nil)
field(:launch_services, boolean() | nil)
field(:accessibility, boolean() | nil)
field(:calendar, boolean() | nil)
field(:reminders, boolean() | nil)
field(:contacts, String.t() | nil)
end
@spec from_map(map() | keyword() | t() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(%__MODULE__{} = permissions), do: permissions
def from_map(data) when is_list(data), do: data |> Map.new() |> from_map()
def from_map(data) when is_map(data) do
%__MODULE__{
preferences: RequestPermissions.fetch_any(data, ["preferences", :preferences]),
automations:
data
|> RequestPermissions.fetch_any(["automations", :automations])
|> RequestPermissions.normalize_union_value(),
launch_services:
RequestPermissions.fetch_any(data, [
"launchServices",
"launch_services",
:launchServices,
:launch_services
]),
accessibility: RequestPermissions.fetch_any(data, ["accessibility", :accessibility]),
calendar: RequestPermissions.fetch_any(data, ["calendar", :calendar]),
reminders: RequestPermissions.fetch_any(data, ["reminders", :reminders]),
contacts: RequestPermissions.fetch_any(data, ["contacts", :contacts])
}
end
@spec to_map(t() | nil) :: map() | nil
def to_map(nil), do: nil
def to_map(%__MODULE__{} = permissions) do
%{}
|> RequestPermissions.put_optional("preferences", permissions.preferences)
|> RequestPermissions.put_optional("automations", permissions.automations)
|> RequestPermissions.put_optional("launchServices", permissions.launch_services)
|> RequestPermissions.put_optional("accessibility", permissions.accessibility)
|> RequestPermissions.put_optional("calendar", permissions.calendar)
|> RequestPermissions.put_optional("reminders", permissions.reminders)
|> RequestPermissions.put_optional("contacts", permissions.contacts)
end
end
defmodule GrantedMacOsPermissions do
@moduledoc "Additional macOS permissions granted for a turn or session."
use TypedStruct
typedstruct do
field(:preferences, String.t() | nil)
field(:automations, String.t() | map() | nil)
field(:launch_services, boolean() | nil)
field(:accessibility, boolean() | nil)
field(:calendar, boolean() | nil)
field(:reminders, boolean() | nil)
field(:contacts, String.t() | nil)
end
@spec from_map(map() | keyword() | t() | nil) :: t() | nil
def from_map(nil), do: nil
def from_map(%__MODULE__{} = permissions), do: permissions
def from_map(data) when is_list(data), do: data |> Map.new() |> from_map()
def from_map(data) when is_map(data) do
%__MODULE__{
preferences: RequestPermissions.fetch_any(data, ["preferences", :preferences]),
automations:
data
|> RequestPermissions.fetch_any(["automations", :automations])
|> RequestPermissions.normalize_union_value(),
launch_services:
RequestPermissions.fetch_any(data, [
"launchServices",
"launch_services",
:launchServices,
:launch_services
]),
accessibility: RequestPermissions.fetch_any(data, ["accessibility", :accessibility]),
calendar: RequestPermissions.fetch_any(data, ["calendar", :calendar]),
reminders: RequestPermissions.fetch_any(data, ["reminders", :reminders]),
contacts: RequestPermissions.fetch_any(data, ["contacts", :contacts])
}
end
@spec to_map(t() | nil) :: map() | nil
def to_map(nil), do: nil
def to_map(%__MODULE__{} = permissions) do
%{}
|> RequestPermissions.put_optional("preferences", permissions.preferences)
|> RequestPermissions.put_optional("automations", permissions.automations)
|> RequestPermissions.put_optional("launchServices", permissions.launch_services)
|> RequestPermissions.put_optional("accessibility", permissions.accessibility)
|> RequestPermissions.put_optional("calendar", permissions.calendar)
|> RequestPermissions.put_optional("reminders", permissions.reminders)
|> RequestPermissions.put_optional("contacts", permissions.contacts)
end
end
defmodule RequestPermissionProfile do
@moduledoc "Permission profile included in request-permissions approval requests."
use TypedStruct
typedstruct do
field(:network, AdditionalNetworkPermissions.t() | nil)
field(:file_system, AdditionalFileSystemPermissions.t() | nil)
field(:macos, AdditionalMacOsPermissions.t() | nil)
end
@spec from_map(map() | keyword() | t() | nil) :: t()
def from_map(nil), do: %__MODULE__{}
def from_map(%__MODULE__{} = profile), do: profile
def from_map(data) when is_list(data), do: data |> Map.new() |> from_map()
def from_map(data) when is_map(data) do
%__MODULE__{
network:
data
|> RequestPermissions.fetch_any(["network", :network])
|> AdditionalNetworkPermissions.from_map(),
file_system:
data
|> RequestPermissions.fetch_any([
"fileSystem",
"file_system",
:fileSystem,
:file_system
])
|> AdditionalFileSystemPermissions.from_map(),
macos:
data
|> RequestPermissions.fetch_any(["macos", :macos])
|> AdditionalMacOsPermissions.from_map()
}
end
@spec to_map(t()) :: map()
def to_map(%__MODULE__{} = profile) do
%{}
|> RequestPermissions.put_optional(
"network",
AdditionalNetworkPermissions.to_map(profile.network)
)
|> RequestPermissions.put_optional(
"fileSystem",
AdditionalFileSystemPermissions.to_map(profile.file_system)
)
|> RequestPermissions.put_optional(
"macos",
AdditionalMacOsPermissions.to_map(profile.macos)
)
end
end
defmodule GrantedPermissionProfile do
@moduledoc "Permission profile included in request-permissions approval responses."
use TypedStruct
typedstruct do
field(:network, AdditionalNetworkPermissions.t() | nil)
field(:file_system, AdditionalFileSystemPermissions.t() | nil)
field(:macos, GrantedMacOsPermissions.t() | nil)
end
@spec from_map(map() | keyword() | t() | nil) :: t()
def from_map(nil), do: %__MODULE__{}
def from_map(%__MODULE__{} = profile), do: profile
def from_map(data) when is_list(data), do: data |> Map.new() |> from_map()
def from_map(data) when is_map(data) do
%__MODULE__{
network:
data
|> RequestPermissions.fetch_any(["network", :network])
|> AdditionalNetworkPermissions.from_map(),
file_system:
data
|> RequestPermissions.fetch_any([
"fileSystem",
"file_system",
:fileSystem,
:file_system
])
|> AdditionalFileSystemPermissions.from_map(),
macos:
data
|> RequestPermissions.fetch_any(["macos", :macos])
|> GrantedMacOsPermissions.from_map()
}
end
@spec to_map(t()) :: map()
def to_map(%__MODULE__{} = profile) do
%{}
|> RequestPermissions.put_optional(
"network",
AdditionalNetworkPermissions.to_map(profile.network)
)
|> RequestPermissions.put_optional(
"fileSystem",
AdditionalFileSystemPermissions.to_map(profile.file_system)
)
|> RequestPermissions.put_optional("macos", GrantedMacOsPermissions.to_map(profile.macos))
end
end
defmodule PermissionGrantScope do
@moduledoc "Scope applied to granted permissions in a permissions approval response."
@type t :: :turn | :session
@spec from_value(t() | String.t() | nil) :: t()
def from_value(nil), do: :turn
def from_value(:turn), do: :turn
def from_value(:session), do: :session
def from_value("turn"), do: :turn
def from_value("session"), do: :session
def from_value(_), do: :turn
@spec to_value(t() | String.t() | nil) :: String.t()
def to_value(value), do: value |> from_value() |> Atom.to_string()
end
defmodule Response do
@moduledoc "Structured response for an app-server permissions approval request."
use TypedStruct
typedstruct do
field(:permissions, GrantedPermissionProfile.t(), default: %GrantedPermissionProfile{})
field(:scope, PermissionGrantScope.t(), default: :turn)
end
@spec from_map(map() | keyword() | t() | nil) :: t()
def from_map(nil), do: %__MODULE__{}
def from_map(%__MODULE__{} = response), do: response
def from_map(data) when is_list(data), do: data |> Map.new() |> from_map()
def from_map(data) when is_map(data) do
%__MODULE__{
permissions:
data
|> RequestPermissions.fetch_any(["permissions", :permissions])
|> GrantedPermissionProfile.from_map(),
scope:
data
|> RequestPermissions.fetch_any(["scope", :scope])
|> PermissionGrantScope.from_value()
}
end
@spec to_map(t()) :: map()
def to_map(%__MODULE__{} = response) do
%{
"permissions" => GrantedPermissionProfile.to_map(response.permissions),
"scope" => PermissionGrantScope.to_value(response.scope)
}
end
end
@doc false
def fetch_any(map, keys) when is_map(map) do
Enum.reduce_while(keys, nil, fn key, _acc ->
if Map.has_key?(map, key) do
{:halt, Map.get(map, key)}
else
{:cont, nil}
end
end)
end
@doc false
def fetch_list(map, keys) when is_map(map) do
case fetch_any(map, keys) do
nil -> nil
values when is_list(values) -> values
value -> List.wrap(value)
end
end
@doc false
def normalize_union_value(nil), do: nil
def normalize_union_value(%{} = value), do: value
def normalize_union_value(value) when is_list(value),
do: value |> Map.new() |> normalize_union_value()
def normalize_union_value(value), do: to_string(value)
@doc false
def put_optional(map, _key, nil), do: map
def put_optional(map, _key, value) when value == %{}, do: map
def put_optional(map, key, value), do: Map.put(map, key, value)
end