Deputy

Build StatusCoverage StatusHex VersionHex Docs

An Elixir client for the Deputy API, organized by resource type.

Installation

This package can be installed by adding deputy to your list of dependencies in mix.exs:

def deps do
[
{:deputy, "~> 0.5.0"}
]
end

Getting Started

First, create a new Deputy client with your API credentials:

client = Deputy.new(
base_url: "https://your-subdomain.deputy.com",
api_key: "your-api-key"
)

Then, use the client to interact with the Deputy API:

# Get locations
{:ok, locations} = Deputy.Locations.list(client)
# Get employees
{:ok, employees} = Deputy.Employees.list(client)

API Modules

The library is organized into modules by resource type:

Each module provides functions for interacting with the corresponding Deputy API endpoints.

Error Handling

All API functions return either {:ok, result} or {:error, error} tuples. The error can be one of several types:

Example of handling different error types:

case Deputy.Locations.get(client, 12345) do
{:ok, location} ->
# Process location data
IO.inspect(location)
{:error, %Deputy.Error.APIError{status: 404}} ->
# Handle not found error
IO.puts("Location not found")
{:error, %Deputy.Error.HTTPError{reason: reason}} ->
# Handle HTTP error
IO.puts("HTTP error: #{inspect(reason)}")
{:error, %Deputy.Error.RateLimitError{retry_after: seconds}} ->
# Handle rate limit
IO.puts("Rate limited. Try again in #{seconds} seconds")
end

Bang Functions

Each API function has a corresponding bang (!) version that raises an exception instead of returning an error tuple. This is useful when you want to fail fast if an error occurs.

# Using regular function with error tuple
{:ok, locations} = Deputy.Locations.list(client)
# Using bang version that raises an exception on error
locations = Deputy.Locations.list!(client)

Examples

Working with Locations

# List all locations
{:ok, locations} = Deputy.Locations.list(client)
# Or using the bang version
locations = Deputy.Locations.list!(client)
# Get a specific location
{:ok, location} = Deputy.Locations.get(client, 123)
# Or using the bang version
location = Deputy.Locations.get!(client, 123)
# Create a new location
attrs = %{
strWorkplaceName: "New Location",
strWorkplaceCode: "NLC",
strAddress: "123 Test St",
intIsWorkplace: 1,
intIsPayrollEntity: 1,
strTimezone: "America/New_York"
}
{:ok, new_location} = Deputy.Locations.create(client, attrs)
# Update a location with error handling
case Deputy.Locations.update(client, 123, %{strWorkplaceCode: "UPD"}) do
{:ok, updated} ->
IO.puts("Location updated successfully")
{:error, %Deputy.Error.APIError{status: 404}} ->
IO.puts("Location not found")
{:error, %Deputy.Error.ValidationError{message: message}} ->
IO.puts("Validation error: #{message}")
end

Working with Employees

# List all employees
{:ok, employees} = Deputy.Employees.list(client)
# Or using the bang version
employees = Deputy.Employees.list!(client)
# Get a specific employee with error handling for rate limits
case Deputy.Employees.get(client, 123) do
{:ok, employee} ->
IO.inspect(employee)
{:error, %Deputy.Error.RateLimitError{retry_after: seconds}} ->
Process.sleep(seconds * 1000)
# Retry after waiting
{:ok, employee} = Deputy.Employees.get(client, 123)
end
# Create a new employee
attrs = %{
strFirstName: "John",
strLastName: "Doe",
intCompanyId: 1,
intGender: 1,
strCountryCode: "US",
strDob: "1980-01-01",
strStartDate: "2023-01-01",
strMobilePhone: "5551234567"
}
# Using the bang version with exception rescue
try do
new_employee = Deputy.Employees.create!(client, attrs)
IO.puts("Employee created successfully")
rescue
e in Deputy.Error.ValidationError ->
IO.puts("Validation error: #{e.message}")
e in Deputy.Error.APIError ->
IO.puts("API error (#{e.status}): #{e.message}")
end

Working with Timesheets

# Start a timesheet using the bang version
timesheet = Deputy.Timesheets.start!(client, %{
intEmployeeId: 123,
intCompanyId: 456
})
# Stop a timesheet with error handling
case Deputy.Timesheets.stop(client, %{intTimesheetId: 789}) do
{:ok, result} ->
IO.puts("Timesheet stopped successfully")
{:error, %Deputy.Error.APIError{status: 404}} ->
IO.puts("Timesheet not found")
{:error, %Deputy.Error.APIError{status: 400, message: message}} ->
IO.puts("Bad request: #{message}")
{:error, %Deputy.Error.HTTPError{reason: reason}} ->
IO.puts("HTTP error: #{inspect(reason)}")
end
# Get timesheet details
{:ok, details} = Deputy.Timesheets.get_details(client, 789)
# Or using the bang version
details = Deputy.Timesheets.get_details!(client, 789)

Working with Authenticated User Data

# Get information about the authenticated user
{:ok, user} = Deputy.My.me(client)
# Or using the bang version
user = Deputy.My.me!(client)
# Get locations where the authenticated user can work
{:ok, locations} = Deputy.My.locations(client)
# Get the authenticated user's rosters with error handling
case Deputy.My.rosters(client) do
{:ok, rosters} ->
IO.inspect(rosters)
{:error, %Deputy.Error.APIError{status: status, message: message}} ->
IO.puts("API error (#{status}): #{message}")
{:error, %Deputy.Error.HTTPError{reason: :timeout}} ->
IO.puts("Request timed out, try again later")
{:error, error} ->
IO.puts("Unexpected error: #{inspect(error)}")
end
# Get the authenticated user's timesheets
timesheets = Deputy.My.timesheets!(client)

Testing

You can test your code that uses this library by leveraging the Deputy.HTTPClient.Mock provided for testing. This allows you to mock the API responses in your tests.

# In your test setup
Mox.defmock(Deputy.HTTPClient.Mock, for: Deputy.HTTPClient)
test "list employees success" do
client = Deputy.new(
base_url: "https://test.deputy.com",
api_key: "test-key",
http_client: Deputy.HTTPClient.Mock
)
# Set up expectations for success
Deputy.HTTPClient.Mock
|> expect(:request, fn %Deputy.HTTPClient.Request{} = req ->
assert req.method == :get
assert req.url == "https://test.deputy.com/api/v1/supervise/employee"
{:ok, [%{"Id" => 1, "FirstName" => "John", "LastName" => "Doe"}]}
end)
# Call the function
{:ok, employees} = Deputy.Employees.list(client)
# Assertions
assert length(employees) == 1
assert hd(employees)["FirstName"] == "John"
end
test "list employees error handling" do
client = Deputy.new(
base_url: "https://test.deputy.com",
api_key: "test-key",
http_client: Deputy.HTTPClient.Mock
)
# Set up expectations for error
Deputy.HTTPClient.Mock
|> expect(:request, fn _opts ->
error = Deputy.Error.from_response(%{
status: 403,
body: %{"error" => %{"code" => "permission_denied", "message" => "Permission denied"}}
})
{:error, error}
end)
# Call the function and test error handling
assert {:error, %Deputy.Error.APIError{status: 403, code: "permission_denied"}} =
Deputy.Employees.list(client)
# Test bang version raises exception
assert_raise Deputy.Error.APIError, fn ->
Deputy.Employees.list!(client)
end
end

Development

Requirements

Setup

bin/setup # installs tooling (actionlint, lefthook, markdownlint) and git hooks
mix setup # installs Elixir dependencies

Commands

TaskCommand
Run all testsmix test
Run a single testmix test path/to/test_file.exs:line_number
Run a test filemix test path/to/test_file.exs
Format codemix format
Lintmix credo
Generate docsmix docs

Documentation

Detailed documentation can be found at https://hexdocs.pm/deputy.

License

Deputy is released under the MIT license.