/forcex

Elixir library for the Force.com / Salesforce / SFDC REST API

Primary LanguageElixirMIT LicenseMIT

Forcex

Build Status Hex.pm Version Coverage Status

Elixir library for interacting with the Force.com REST API.

Usage

Add Forcex to your dependency list

  defp deps do
    [ {:forcex, "~> 0.4"}
    ]
  end

At compile time Forcex will query the Force.com REST API and generate modules for all the SObjects you have configured and permission to see.

If you see a warning like

23:37:02.057 [warn]  Cannot log into SFDC API. Please ensure you have Forcex properly configured.
Got error code 400 and message %{"error" => "invalid_client_id", "error_description" => "client identifier invalid"}

You will need to configure Forcex, as noted below, and then explicitly recompile Forcex

$ mix deps.clean forcex
$ mix deps.compile forcex

You can have Forcex generate modules at compile time using the accompanying Mix task.

$ mix compile.forcex

This can also be invoked automatically by adding Forcex to your project's compilers in mix.exs

compilers: [:forcex] ++ Mix.compilers,

Bulk API Usage

Forcex has an example Bulk API query job controller. Here's roughly how that can work.

client = Forcex.Bulk.Client.login
[
  "Account",
  "Campaign",
  "Contact",
  "Lead",
  "Opportunity",
  "OpportunityLineItem",
]
|> Enum.map(fn sobject -> {sobject, ["select Id, Name from #{sobject}"]} end)
|> Enum.map(fn {sobject, queries} ->
Forcex.Bulk.JobController.start_link({:query, sobject, queries, client}) end)

Configuration

The Forcex.Client is configured to read login information either from application configuration:

config :forcex, Forcex.Client,
  username: "user@example.com",
  password: "my_super_secret_password",
  security_token: "EMAILED_FROM_SALESFORCE",
  client_id: "CONNECTED_APP_OAUTH_CLIENT_ID",
  client_secret: "CONNECTED_APP_OAUTH_CLIENT_SECRET"

or these environment variables:

  • SALESFORCE_USERNAME
  • SALESFORCE_PASSWORD
  • SALESFORCE_SECURITY_TOKEN
  • SALESFORCE_CLIENT_ID
  • SALESFORCE_CLIENT_SECRET

HTTPoison request-specific options may also be configured:

config :forcex, :request_options,
  timeout: 20000,
  recv_timeout: :infinity

For steps on how to create a Connected App with OAuth keys and secrets, please see the Force.com REST API section on Connected Apps.

Currently, to be fully functional, the Forcex.Client must both login and locate_services.

Pagination of results is entirely manual at the moment.

client = Forcex.Client.login |> Forcex.Client.locate_services

first_page = Forcex.query("select Id, Name from Account order by CreatedDate desc", client)

second_page = first_page |> Map.get("nextRecordsUrl") |> Forcex.get(client)

Further Configuration

Forcex allows additional configuration of API endpoint and API version via the %Forcex.Client{} struct. You may also use this mechanism if you have a grant_type other than password.

This example shows how to use both an older API version and the SalesForce sandbox API.

Forcex.Client.default_config
|> Forcex.Client.login(%Forcex.Client{endpoint: "https://test.salesforce.com", api_version: "34.0"})

Testing

Make sure dependencies are installed

mix deps.get

Tests can be run with

mix test

Tests can be run automatically when files change with

mix test.watch --stale

Tests mock the api calls to the Salesforce API using Mox to set expectations on Forcex.Api.MockHttp.raw_request. To know what to put in a mock response just run the client in iex and look for the debug logging response from http.ex. Make sure to scrub any responses for sensitive data before including them in a commit.

Example assuming environment variables are in place with login info

% iex -S mix
iex(1)> client = Forcex.Client.login |> Forcex.Client.locate_services
14:40:27.858 file=forcex/lib/forcex/api/http.ex line=19 [debug] Elixir.Forcex.Api.Http.raw_request response=%{access_token: "redacted",...
14:40:28.222 file=forcex/lib/forcex/api/http.ex line=19 [debug] Elixir.Forcex.Api.Http.raw_request response=%{process: "/services/data/v43.0/process", search...
iex(2)> Forcex.query("select Id, Name from Account order by CreatedDate desc", client)
14:43:05.896 file=forcex/lib/forcex/api/http.ex line=19 [debug] Elixir.Forcex.Api.Http.raw_request response=%{done: false, nextRecordsUrl: "/services/data/v4

Just take the data after response= and throw it in a Mox expectation. See existing tests for full examples

response = %{access_token: "redacted"}
Forcex.Api.MockHttp
|> expect(:raw_request, fn(:get, ^expected_url, _, ^auth_header, _) -> response end)

Current State

See https://www.salesforce.com/us/developer/docs/api_rest/

  • List API versions available
  • Login (Username/Password/Client Key/Client Secret)
  • Login (Web Server OAuth)
  • Login (User-Agent OAuth)
  • OAuth Refresh Token
  • Resources by Version
  • Limits
  • Describe Global
  • SObject Basic Information
  • SObject Describe
  • SObject Get Deleted
  • SObject Get Updated
  • SObject Named Layouts
  • SObject Rows
  • SObject Rows by External ID
  • SObject ApprovalLayouts
  • SObject CompactLayouts
  • SObject Layouts
  • SObject Blob Retrieve
  • SObject Quick Actions
  • SObject Suggested Articles for Case
  • SObject User Password
  • AppMenu
  • Compact Layouts
  • FlexiPage
  • Process Approvals
  • Process Rules
  • Query
  • QueryAll
  • Quick Actions
  • Search
  • Search Scope and Order
  • Search Result Layouts
  • Recently Viewed Items
  • Search Suggested Article Title Matches
  • Tabs
  • Themes

License

MIT License, see LICENSE