How to apply a guard in a function

The goal of this post is to get some guidance on how to apply guards to the function below. I’m struggling to make it work with the when keyword, and had to place a giant if block to make the logic work.

Giving more context around the function: Either a ‘connection’, ‘organization’, ‘domain’, or ‘provider’ args should be provided, otherwise the function shouldn’t be executed.

This is how it looks like by using a if

 @doc """
  Generates an OAuth 2.0 authorization URL.
  """
  @spec get_authorization_url(map()) :: {:ok, String.t()} | {:error, String.t()}
  def get_authorization_url(params) do
    if is_map_key(params, :connection) or is_map_key(params, :organization) or
         is_map_key(params, :redirect_uri) do
      if is_map_key(params, :domain) do
        Logger.warn(
          "The `domain` parameter for `get_authorization_url` is deprecated. Please use `organization` instead."
        )
      end

      defaults = %{
        client_id: WorkOS.config() |> Keyword.take([:client_id]),
        response_type: "code"
      }

      query =
        defaults
        |> Map.merge(params)
        |> Map.take(
          [
            :domain,
            :provider,
            :connection,
            :organization,
            :client_id,
            :redirect_uri,
            :state,
            :domain_hint,
            :login_hint
          ] ++ Map.keys(defaults)
        )
        |> URI.encode_query()

      base_url = WorkOS.config() |> Keyword.take([:base_url])

      {:ok, "#{base_url}/sso/authorize?#{query}"}
    else
      {:error, "Invalid params"}
    end
  end

I tried to do the same approach with a guard:

 @spec get_authorization_url(map()) :: {:ok, String.t()} | {:error, String.t()}
  def get_authorization_url(params) when is_map_key(params, :connection) or is_map_key(params, :organization) or
         is_map_key(params, :redirect_uri) do
      if is_map_key(params, :domain) do
        Logger.warn(
          "The `domain` parameter for `get_authorization_url` is deprecated. Please use `organization` instead."
        )
      end

      defaults = %{
        client_id: WorkOS.config() |> Keyword.take([:client_id]),
        response_type: "code"
      }

      query =
        defaults
        |> Map.merge(params)
        |> Map.take(
          [
            :domain,
            :provider,
            :connection,
            :organization,
            :client_id,
            :redirect_uri,
            :state,
            :domain_hint,
            :login_hint
          ] ++ Map.keys(defaults)
        )
        |> URI.encode_query()

      base_url = WorkOS.config() |> Keyword.take([:base_url])

      "#{base_url}/sso/authorize?#{query}"
  end

However, I’m getting the following Dialyzer error: “The @spec for the function does not match the success typing of the function.” - what is it trying to infer here regarding the “does not match the success typing”?

Should another function clause be declared to handle the case where none of those args are provided?

Hi @LauraBeatris,

What I can see from your guard example is that you definitely miss default get_authorization_url /1 function that will be called when one of the guards is false.
def get_authorization_url(_params), do: {:error, "Invalid params"}

Also specification for a function says that return types are {:ok, String.t()} | {:error, String.t()} but your function only returns string instead of tuple.

3 Likes

Hey @stefan_z, thanks for jumping in to help on such a basic thing hahah, I really appreciate that!

Yeah, somehow I was struggling to add that second clause with the default get_authorization_url /1 function. This is the updated version, now with the typespecs working:

 @doc """
  Generates an OAuth 2.0 authorization URL.
  """
  @spec get_authorization_url(map()) :: {:ok, String.t()} | {:error, String.t()}
  def get_authorization_url(params)
      when is_map_key(params, :connection) or is_map_key(params, :organization) or
             is_map_key(params, :redirect_uri) do
    if is_map_key(params, :domain) do
      Logger.warn(
        "The `domain` parameter for `get_authorization_url` is deprecated. Please use `organization` instead."
      )
    end

    defaults = %{
      client_id: WorkOS.config() |> Keyword.take([:client_id]),
      response_type: "code"
    }

    query =
      defaults
      |> Map.merge(params)
      |> Map.take(
        [
          :domain,
          :provider,
          :connection,
          :organization,
          :client_id,
          :redirect_uri,
          :state,
          :domain_hint,
          :login_hint
        ] ++ Map.keys(defaults)
      )
      |> URI.encode_query()

    base_url = WorkOS.config() |> Keyword.take([:base_url])

    {:ok, "#{base_url}/sso/authorize?#{query}"}
  end

  def get_authorization_url(_params), do: {:error, "Invalid params"}

Hi @LauraBeatris, you are welcome! :+1: