Skip to content

Commit e20f6da

Browse files
authored
Merge pull request #37 from asgoel/suppression_list_search
Suppression list search
2 parents 8a6d29a + 17f5d3f commit e20f6da

File tree

8 files changed

+191
-5
lines changed

8 files changed

+191
-5
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
/doc
55
erl_crash.dump
66
*.ez
7+
*.swp

lib/endpoint.ex

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ defmodule SparkPost.Endpoint do
3232
"id" => "102258558346809186", "name" => "102258558346809186",
3333
"state" => "Success"}, ...], status_code: 200}
3434
"""
35-
def request(method, endpoint, body \\ %{}, headers \\ %{}, options \\ []) do
35+
def request(method, endpoint, body \\ %{}, headers \\ %{}, options \\ [], decode_results \\ true) do
3636
url = Application.get_env(:sparkpost, :api_endpoint, @default_endpoint) <> endpoint
3737

3838
{:ok, request_body} = encode_request_body(body)
@@ -49,7 +49,7 @@ defmodule SparkPost.Endpoint do
4949
|> Keyword.put(:recv_timeout, Application.get_env(:sparkpost, :http_recv_timeout, 8000))
5050

5151
HTTPoison.request(method, url, request_body, request_headers, request_options)
52-
|> handle_response
52+
|> handle_response(decode_results)
5353
end
5454

5555
def marshal_response(response, struct_type, subkey\\nil)
@@ -70,12 +70,16 @@ defmodule SparkPost.Endpoint do
7070
response
7171
end
7272

73-
defp handle_response({:ok, %HTTPoison.Response{status_code: code, body: body}}) when code >= 200 and code < 300 do
73+
defp handle_response({:ok, %HTTPoison.Response{status_code: code, body: body}}, decode_results) when code >= 200 and code < 300 do
7474
decoded_body = decode_response_body(body)
75-
%SparkPost.Endpoint.Response{status_code: 200, results: decoded_body.results}
75+
if decode_results do
76+
%SparkPost.Endpoint.Response{status_code: 200, results: decoded_body.results}
77+
else
78+
%SparkPost.Endpoint.Response{status_code: 200, results: decoded_body}
79+
end
7680
end
7781

78-
defp handle_response({:ok, %HTTPoison.Response{status_code: code, body: body}}) when code >= 400 do
82+
defp handle_response({:ok, %HTTPoison.Response{status_code: code, body: body}}, _decode_results) when code >= 400 do
7983
decoded_body = decode_response_body(body)
8084
if Map.has_key?(decoded_body, :errors) do
8185
%SparkPost.Endpoint.Error{status_code: code, errors: decoded_body.errors}

lib/suppression_list.ex

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
defmodule SparkPost.SuppressionList do
2+
@moduledoc """
3+
The SparkPost Suppression List API for working with suppression lists.
4+
Use `SparkPost.SuppressionList.search/1` to search through your account's suppression list.
5+
6+
Check out the documentation for each function
7+
or use the [SparkPost API reference](https://developers.sparkpost.com/api/suppression_list.html) for details.
8+
9+
Returned by `SparkPost.SuppressionList.search/1`.
10+
- %SparkPost.SuppressionList.SearchResult{}
11+
"""
12+
13+
alias SparkPost.Endpoint
14+
15+
@doc """
16+
Execute a search of the suppression list based on the provided
17+
parameters.
18+
19+
### Possible Parameters
20+
- to: Datetime the entries were last updated, in the format YYYY-MM-DDTHH:mm:ssZ (defaults to now)
21+
- from: Datetime the entries were last updated, in the format YYYY-MM-DDTHH:mm:ssZ
22+
- domain: Domain of entries to include in search
23+
- cursor: Results cursor (first query should use the value "initial")
24+
- per_page: Max number of results to return per page (between 1 and 10,000)
25+
- page: Results page number to return. Use if looking for less than 10,000 results. Otherwise
26+
use the cursor param.
27+
- sources: Sources of entries to include in the search.
28+
- types: Types of entries to include in the search (transactional and/or non_transactional)
29+
- description: Description of entries to include in the search.
30+
"""
31+
def search(params \\ []) do
32+
response = Endpoint.request(:get, "suppression-list", %{}, %{}, [params: params], false)
33+
case response do
34+
%SparkPost.Endpoint.Response{results: body} ->
35+
mapped_results = Enum.map(body.results, fn res -> struct(SparkPost.SuppressionList.ListEntry, res) end)
36+
%SparkPost.SuppressionList.SearchResult{
37+
results: mapped_results,
38+
links: body.links,
39+
total_count: body.total_count
40+
}
41+
_ -> response
42+
end
43+
end
44+
end

lib/suppression_list/list_entry.ex

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
defmodule SparkPost.SuppressionList.ListEntry do
2+
@moduledoc """
3+
SparkPost representation of a suppression list entry.
4+
5+
## Fields
6+
- recipient: Email address that was suppressed
7+
- type: Type of suppression record (transactional or non_transactional)
8+
- source: Source responsible for inserting the list entry. Can be one of
9+
"Spam Complaint", "List Unsubscribe", "Bounce Rule", "Unsubscribe Link",
10+
"Manually Added", or "Compliance"
11+
- description: explanation of suppression
12+
"""
13+
14+
defstruct recipient: :required,
15+
type: :required,
16+
source: nil,
17+
description: nil,
18+
transactional: nil,
19+
non_transactional: nil
20+
end
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
defmodule SparkPost.SuppressionList.SearchResult do
2+
@moduledoc """
3+
SparkPost representation of a suppression list search result.
4+
5+
## Fields
6+
- results: List of %SparkPost.SuppressionList.ListEntry{} objects.
7+
- links: Links to the first query in the chain and next query to be made for cursor
8+
based pagination.
9+
- total_count: Total number of results across all pages based on the query params.
10+
"""
11+
12+
defstruct results: :required,
13+
links: :required,
14+
total_count: :required
15+
end

test/data/suppressionsearch.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"results": [
3+
{
4+
"recipient": "test@marketing.com",
5+
"type": "non_transactional"
6+
}
7+
],
8+
"links": [],
9+
"total_count": 1
10+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"results": [
3+
{
4+
"recipient": "test@marketing.com",
5+
"type": "non_transactional"
6+
}
7+
],
8+
"links": [
9+
{
10+
"href": "/currentlink",
11+
"rel": "first"
12+
},
13+
{
14+
"href": "/linkwithcursor",
15+
"rel": "next"
16+
}
17+
],
18+
"total_count": 1
19+
}

test/suppression_list_test.exs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
defmodule SparkPost.SuppressionListTest do
2+
@moduledoc false
3+
4+
use ExUnit.Case, async: false
5+
6+
alias SparkPost.{MockServer, SuppressionList}
7+
alias SparkPost.SuppressionList.{SearchResult, ListEntry}
8+
9+
import Mock
10+
11+
test_with_mock "SuppressionList.search succeeds with SuppressionList.SearchResult",
12+
HTTPoison, [request: fn (method, url, body, headers, opts) ->
13+
assert method == :get
14+
fun = MockServer.mk_http_resp(200, MockServer.get_json("suppressionsearch"))
15+
fun.(method, url, body, headers, opts)
16+
end] do
17+
resp = SuppressionList.search()
18+
assert %SearchResult{} = resp
19+
end
20+
21+
test_with_mock "SuppressionList.search fails with Endpoint.Error", HTTPoison,
22+
[request: MockServer.mk_fail] do
23+
resp = SuppressionList.search()
24+
assert %SparkPost.Endpoint.Error{} = resp
25+
end
26+
27+
test_with_mock "SuppressionList.search creates ListEntry structs", HTTPoison,
28+
[request: fn (method, url, body, headers, opts) ->
29+
assert method == :get
30+
fun = MockServer.mk_http_resp(200, MockServer.get_json("suppressionsearch"))
31+
fun.(method, url, body, headers, opts)
32+
end] do
33+
resp = SuppressionList.search()
34+
assert %SearchResult{
35+
results: [
36+
%ListEntry{
37+
recipient: "test@marketing.com",
38+
type: "non_transactional",
39+
source: nil,
40+
description: nil,
41+
non_transactional: nil
42+
}
43+
],
44+
links: [],
45+
total_count: 1
46+
} == resp
47+
end
48+
49+
test_with_mock "SuppressionList.search parses out cursor info", HTTPoison,
50+
[request: fn (method, url, body, headers, opts) ->
51+
assert method == :get
52+
fun = MockServer.mk_http_resp(200, MockServer.get_json("suppressionsearch_links"))
53+
fun.(method, url, body, headers, opts)
54+
end] do
55+
resp = SuppressionList.search()
56+
assert %SearchResult{
57+
results: [
58+
%ListEntry{
59+
recipient: "test@marketing.com",
60+
type: "non_transactional",
61+
source: nil,
62+
description: nil,
63+
non_transactional: nil
64+
}
65+
],
66+
links: [
67+
%{href: "/currentlink", rel: "first"},
68+
%{href: "/linkwithcursor", rel: "next"}
69+
],
70+
total_count: 1
71+
} == resp
72+
end
73+
end

0 commit comments

Comments
 (0)