# AUTOGENERATED: This file was generated using the mix task `lexgen`. defmodule Atproto do @default_pds_hostname Application.compile_env( :atproto, :default_pds_hostname, "https://bsky.social" ) @typedoc """ A type representing the names of the options that can be passed to `query/3` and `procedure/3`. """ @type xrpc_opt :: :pds_hostname | :authorization @typedoc """ A keyword list of options that can be passed to `query/3` and `procedure/3`. """ @type xrpc_opts :: [{xrpc_opt(), any()}] @doc """ Converts a JSON string, or decoded JSON map, into a struct based on the given module. This function uses `String.to_existing_atom/1` to convert the keys of the map to atoms, meaning this will throw an error if the input JSON contains keys which are not already defined as atoms in the existing structs or codebase. """ @spec decode_to_struct(module(), binary() | map()) :: map() def decode_to_struct(module, json) when is_binary(json) do decode_to_struct(module, Jason.decode!(json, keys: :atoms!)) end def decode_to_struct(module, map) when is_map(map) do Map.merge(module.new(), map) end @doc """ Raises an error if any required parameters are missing from the given map. """ @spec ensure_required(map(), [String.t()]) :: map() def ensure_required(params, required) do if Enum.all?(required, fn key -> Map.has_key?(params, key) end) do params else raise ArgumentError, "Missing one or more required parameters: #{Enum.join(required, ", ")}" end end @doc """ Executes a "GET" HTTP request and returns the response body as a map. If the `:pds_hostname` option is not provided, the default PDS hostname as provided in the compile-time configuration will be used. """ @spec query(map(), String.t(), xrpc_opts()) :: Req.Request.t() def query(params, target, opts \\ []) do target |> endpoint(opts) |> URI.new!() |> URI.append_query(URI.encode_query(params)) |> Req.get(build_req_auth(opts)) |> handle_response(opts) end @doc """ Executes a "POST" HTTP request and returns the response body as a map. If the `:pds_hostname` option is not provided, the default PDS hostname as provided in the compile-time configuration will be used. """ @spec procedure(map(), String.t(), xrpc_opts()) :: {:ok | :refresh | :error, map()} def procedure(params, target, opts \\ []) do req_opts = opts |> build_req_auth() |> build_req_headers(opts, target) |> build_req_body(params, target) target |> endpoint(opts) |> URI.new!() |> Req.post(req_opts) |> handle_response(opts) end defp build_req_auth(opts) do case Keyword.get(opts, :access_token) do nil -> case Keyword.get(opts, :admin_token) do nil -> [] token -> [auth: {:basic, "admin:#{token}"}] end token -> [auth: {:bearer, token}] end end defp build_req_headers(req_opts, opts, "com.atproto.repo.uploadBlob") do [ {:headers, [ {"Content-Type", Keyword.fetch!(opts, :content_type)}, {"Content-Length", Keyword.fetch!(opts, :content_length)} ]} | req_opts ] end defp build_req_headers(req_opts, _opts, _target), do: req_opts defp build_req_body(opts, blob, "com.atproto.repo.uploadBlob") do [{:body, blob} | opts] end defp build_req_body(opts, %{} = params, _target) when map_size(params) > 0 do [{:json, params} | opts] end defp build_req_body(opts, _params, _target), do: opts defp endpoint(target, opts) do (Keyword.get(opts, :pds_hostname) || @default_pds_hostname) <> "/xrpc/" <> target end defp handle_response({:ok, %Req.Response{} = response}, opts) do case response.status do x when x in 200..299 -> {:ok, response.body} _ -> if response.body["error"] == "ExpiredToken" do {:ok, user} = Com.Atproto.Server.RefreshSession.main(%{}, access_token: Keyword.get(opts, :refresh_token) ) {:refresh, user} else {:error, response.body} end end end defp handle_response(error, _opts), do: error @doc """ Converts a "map-like" entity into a standard map. This will also omit any entries that have a `nil` value. This is useful for converting structs or schemas into regular maps before sending them over XRPC requests. You may optionally pass in an keyword list of options: - `:stringify` - `boolean` - If `true`, converts the keys to strings. Otherwise, converts keys to atoms. Default is `false`. - *Note*: When `false`, this feature uses the `to_existing_atom/1` function to avoid reckless conversion of string keys. """ @spec to_map(map() | struct()) :: map() def to_map(%{__struct__: _} = m, opts \\ []) do string_keys = Keyword.get(opts, :stringify, false) m |> Map.drop([:__struct__, :__meta__]) |> Enum.map(fn {_, nil} -> nil {k, v} when is_atom(k) -> if string_keys, do: {to_string(k), v}, else: {k, v} {k, v} when is_binary(k) -> if string_keys, do: {k, v}, else: {String.to_existing_atom(k), v} end) |> Enum.reject(&is_nil/1) |> Enum.into(%{}) end end