embulk-output-mailchimp 0.2.3 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -1
  3. data/.gitignore +10 -3
  4. data/.travis.yml +4 -7
  5. data/CHANGELOG.md +18 -0
  6. data/README.md +18 -17
  7. data/build.gradle +124 -0
  8. data/ci/travis_mailchimp.yml +8 -0
  9. data/circle.yml +16 -0
  10. data/classpath/embulk-base-restclient-0.5.0.jar +0 -0
  11. data/classpath/embulk-output-mailchimp-0.3.2.jar +0 -0
  12. data/classpath/embulk-util-retryhelper-jetty92-0.5.0.jar +0 -0
  13. data/classpath/jetty-client-9.2.14.v20151106.jar +0 -0
  14. data/classpath/jetty-http-9.2.14.v20151106.jar +0 -0
  15. data/classpath/jetty-io-9.2.14.v20151106.jar +0 -0
  16. data/classpath/jetty-util-9.2.14.v20151106.jar +0 -0
  17. data/config/checkstyle/checkstyle.xml +128 -0
  18. data/config/checkstyle/default.xml +108 -0
  19. data/gradle/wrapper/gradle-wrapper.jar +0 -0
  20. data/gradle/wrapper/gradle-wrapper.properties +6 -0
  21. data/gradlew +160 -0
  22. data/gradlew.bat +90 -0
  23. data/lib/embulk/output/mailchimp.rb +3 -172
  24. data/src/main/java/org/embulk/output/mailchimp/MailChimpAbstractRecordBuffer.java +320 -0
  25. data/src/main/java/org/embulk/output/mailchimp/MailChimpHttpClient.java +151 -0
  26. data/src/main/java/org/embulk/output/mailchimp/MailChimpOutputPlugin.java +18 -0
  27. data/src/main/java/org/embulk/output/mailchimp/MailChimpOutputPluginDelegate.java +164 -0
  28. data/src/main/java/org/embulk/output/mailchimp/MailChimpRecordBuffer.java +174 -0
  29. data/src/main/java/org/embulk/output/mailchimp/helper/MailChimpHelper.java +70 -0
  30. data/src/main/java/org/embulk/output/mailchimp/model/AuthMethod.java +58 -0
  31. data/src/main/java/org/embulk/output/mailchimp/model/CategoriesResponse.java +30 -0
  32. data/src/main/java/org/embulk/output/mailchimp/model/ErrorResponse.java +56 -0
  33. data/src/main/java/org/embulk/output/mailchimp/model/InterestCategoriesResponse.java +24 -0
  34. data/src/main/java/org/embulk/output/mailchimp/model/InterestResponse.java +30 -0
  35. data/src/main/java/org/embulk/output/mailchimp/model/InterestsResponse.java +24 -0
  36. data/src/main/java/org/embulk/output/mailchimp/model/MemberStatus.java +64 -0
  37. data/src/main/java/org/embulk/output/mailchimp/model/MetaDataResponse.java +36 -0
  38. data/src/main/java/org/embulk/output/mailchimp/model/ReportResponse.java +105 -0
  39. data/src/main/java/org/embulk/output/mailchimp/validation/ColumnDataValidator.java +40 -0
  40. data/src/test/java/org/embulk/output/mailchimp/CircleCICredentials.java +22 -0
  41. data/src/test/java/org/embulk/output/mailchimp/TestColumnDataValidator.java +43 -0
  42. data/src/test/java/org/embulk/output/mailchimp/TestMailChimpHelper.java +54 -0
  43. data/src/test/java/org/embulk/output/mailchimp/TestMailChimpOutputPlugin.java +158 -0
  44. data/src/test/resources/csv/email.csv +2 -0
  45. metadata +55 -122
  46. data/.ruby-version +0 -1
  47. data/Gemfile +0 -2
  48. data/Rakefile +0 -29
  49. data/embulk-output-mailchimp.gemspec +0 -27
@@ -0,0 +1,174 @@
1
+ package org.embulk.output.mailchimp;
2
+
3
+ import com.fasterxml.jackson.core.JsonProcessingException;
4
+ import com.fasterxml.jackson.databind.JsonNode;
5
+ import com.fasterxml.jackson.databind.node.ObjectNode;
6
+ import com.google.common.base.Function;
7
+ import com.google.common.collect.FluentIterable;
8
+ import com.google.common.collect.ImmutableList;
9
+ import com.google.common.collect.Maps;
10
+ import org.eclipse.jetty.http.HttpMethod;
11
+ import org.embulk.config.ConfigException;
12
+ import org.embulk.output.mailchimp.helper.MailChimpHelper;
13
+ import org.embulk.output.mailchimp.model.CategoriesResponse;
14
+ import org.embulk.output.mailchimp.model.ErrorResponse;
15
+ import org.embulk.output.mailchimp.model.InterestCategoriesResponse;
16
+ import org.embulk.output.mailchimp.model.InterestResponse;
17
+ import org.embulk.output.mailchimp.model.InterestsResponse;
18
+ import org.embulk.output.mailchimp.model.MetaDataResponse;
19
+ import org.embulk.output.mailchimp.model.ReportResponse;
20
+ import org.embulk.spi.Exec;
21
+ import org.embulk.spi.Schema;
22
+ import org.slf4j.Logger;
23
+
24
+ import javax.annotation.Nullable;
25
+
26
+ import java.text.MessageFormat;
27
+ import java.util.HashMap;
28
+ import java.util.List;
29
+ import java.util.Map;
30
+
31
+ import static org.embulk.output.mailchimp.model.AuthMethod.API_KEY;
32
+ import static org.embulk.output.mailchimp.model.AuthMethod.OAUTH;
33
+
34
+ /**
35
+ * Created by thangnc on 4/25/17.
36
+ */
37
+ public class MailChimpRecordBuffer extends MailChimpAbstractRecordBuffer
38
+ {
39
+ private static final Logger LOG = Exec.getLogger(MailChimpRecordBuffer.class);
40
+ private MailChimpHttpClient client;
41
+
42
+ public MailChimpRecordBuffer(final Schema schema, final MailChimpOutputPluginDelegate.PluginTask task)
43
+ {
44
+ super(schema, task);
45
+ client = new MailChimpHttpClient(task);
46
+ }
47
+
48
+ @Override
49
+ void cleanUp()
50
+ {
51
+ client.close();
52
+ }
53
+
54
+ /**
55
+ * Build an array of email subscribers and batch insert via bulk MailChimp API
56
+ * Reference: https://developer.mailchimp.com/documentation/mailchimp/reference/lists/#create-post_lists_list_id
57
+ *
58
+ * @param node the data
59
+ * @param task the task
60
+ * @throws JsonProcessingException the json processing exception
61
+ */
62
+ @Override
63
+ public ReportResponse push(final ObjectNode node, MailChimpOutputPluginDelegate.PluginTask task)
64
+ throws JsonProcessingException
65
+ {
66
+ String endpoint = MessageFormat.format(mailchimpEndpoint + "/lists/{0}",
67
+ task.getListId());
68
+
69
+ JsonNode response = client.sendRequest(endpoint, HttpMethod.POST, node.toString(), task);
70
+ return getMapper().treeToValue(response, ReportResponse.class);
71
+ }
72
+
73
+ @Override
74
+ void handleErrors(List<ErrorResponse> errorResponses)
75
+ {
76
+ if (!errorResponses.isEmpty()) {
77
+ StringBuilder errorMessage = new StringBuilder();
78
+
79
+ for (ErrorResponse errorResponse : errorResponses) {
80
+ errorMessage.append(MessageFormat.format("\nEmail `{0}` failed cause `{1}`",
81
+ MailChimpHelper.maskEmail(errorResponse.getEmailAddress()),
82
+ MailChimpHelper.maskEmail(errorResponse.getError())));
83
+ }
84
+
85
+ LOG.error(errorMessage.toString());
86
+ }
87
+ }
88
+
89
+ Map<String, Map<String, InterestResponse>> extractInterestCategoriesByGroupNames(final MailChimpOutputPluginDelegate.PluginTask task)
90
+ throws JsonProcessingException
91
+ {
92
+ Map<String, Map<String, InterestResponse>> categories = new HashMap<>();
93
+ if (task.getGroupingColumns().isPresent() && !task.getGroupingColumns().get().isEmpty()) {
94
+ List<String> interestCategoryNames = task.getGroupingColumns().get();
95
+
96
+ String endpoint = MessageFormat.format(mailchimpEndpoint + "/lists/{0}/interest-categories",
97
+ task.getListId());
98
+
99
+ JsonNode response = client.sendRequest(endpoint, HttpMethod.GET, task);
100
+ InterestCategoriesResponse interestCategoriesResponse = getMapper().treeToValue(response,
101
+ InterestCategoriesResponse.class);
102
+
103
+ Function<CategoriesResponse, String> function = new Function<CategoriesResponse, String>()
104
+ {
105
+ @Override
106
+ public String apply(CategoriesResponse input)
107
+ {
108
+ return input.getTitle().toLowerCase();
109
+ }
110
+ };
111
+
112
+ // Transform to a list of available category names and validate with data that user input
113
+ ImmutableList<String> availableCategories = FluentIterable
114
+ .from(interestCategoriesResponse.getCategories())
115
+ .transform(function)
116
+ .toList();
117
+
118
+ for (String category : interestCategoryNames) {
119
+ if (!availableCategories.contains(category)) {
120
+ throw new ConfigException("Invalid interest category name: '" + category + "'");
121
+ }
122
+ }
123
+
124
+ for (CategoriesResponse categoriesResponse : interestCategoriesResponse.getCategories()) {
125
+ String detailEndpoint = MessageFormat.format(mailchimpEndpoint + "/lists/{0}/interest-categories/{1}/interests",
126
+ task.getListId(),
127
+ categoriesResponse.getId());
128
+ response = client.sendRequest(detailEndpoint, HttpMethod.GET, task);
129
+ InterestsResponse interestsResponse = getMapper().treeToValue(response, InterestsResponse.class);
130
+ categories.put(categoriesResponse.getTitle().toLowerCase(),
131
+ convertInterestCategoryToMap(interestsResponse.getInterests()));
132
+ }
133
+ }
134
+
135
+ return categories;
136
+ }
137
+
138
+ @Override
139
+ String extractDataCenter(MailChimpOutputPluginDelegate.PluginTask task) throws JsonProcessingException
140
+ {
141
+ if (task.getAuthMethod() == OAUTH) {
142
+ // Extract data center from meta data URL
143
+ JsonNode response = client.sendRequest("https://login.mailchimp.com/oauth2/metadata", HttpMethod.GET, task);
144
+ MetaDataResponse metaDataResponse = getMapper().treeToValue(response, MetaDataResponse.class);
145
+ return metaDataResponse.getDc();
146
+ }
147
+ else if (task.getAuthMethod() == API_KEY && task.getApikey().isPresent()) {
148
+ // Authenticate and return data center
149
+ String domain = task.getApikey().get().split("-")[1];
150
+ String endpoint = MessageFormat.format(mailchimpEndpoint + "/", domain);
151
+ client.sendRequest(endpoint, HttpMethod.GET, task);
152
+ return domain;
153
+ }
154
+ else {
155
+ throw new ConfigException("Could not get data center");
156
+ }
157
+ }
158
+
159
+ private Map<String, InterestResponse> convertInterestCategoryToMap(final List<InterestResponse> interestResponseList)
160
+ {
161
+ Function<InterestResponse, String> function = new Function<InterestResponse, String>()
162
+ {
163
+ @Override
164
+ public String apply(@Nullable InterestResponse input)
165
+ {
166
+ return input.getName();
167
+ }
168
+ };
169
+
170
+ return Maps.uniqueIndex(FluentIterable.from(interestResponseList)
171
+ .toList(),
172
+ function);
173
+ }
174
+ }
@@ -0,0 +1,70 @@
1
+ package org.embulk.output.mailchimp.helper;
2
+
3
+ import com.fasterxml.jackson.databind.JsonNode;
4
+ import com.google.common.base.Function;
5
+ import com.google.common.collect.Multimap;
6
+ import com.google.common.collect.Multimaps;
7
+
8
+ import javax.annotation.Nullable;
9
+
10
+ import java.util.List;
11
+
12
+ /**
13
+ * Created by thangnc on 4/26/17.
14
+ */
15
+ public final class MailChimpHelper
16
+ {
17
+ private MailChimpHelper()
18
+ {
19
+ }
20
+
21
+ /**
22
+ * Mask email string.
23
+ *
24
+ * @param email the email
25
+ * @return the string
26
+ */
27
+ public static String maskEmail(final String email)
28
+ {
29
+ return email.replaceAll("(?<=.).(?=[^@]*?..@)", "*");
30
+ }
31
+
32
+ /**
33
+ * This method help to get explicit merge fields with column schema without case-sensitive
34
+ *
35
+ * @param s the s
36
+ * @param list the list
37
+ * @return the boolean
38
+ */
39
+ public static String containsCaseInsensitive(final String s, final List<String> list)
40
+ {
41
+ for (String string : list) {
42
+ if (string.equalsIgnoreCase(s)) {
43
+ return string;
44
+ }
45
+ }
46
+
47
+ return "";
48
+ }
49
+
50
+ /**
51
+ * Extract member status to validate.
52
+ *
53
+ * @param data the data
54
+ * @return the multimap
55
+ */
56
+ public static Multimap<String, JsonNode> extractMemberStatus(final List<JsonNode> data)
57
+ {
58
+ Function<JsonNode, String> function = new Function<JsonNode, String>()
59
+ {
60
+ @Nullable
61
+ @Override
62
+ public String apply(@Nullable JsonNode input)
63
+ {
64
+ return input != null ? input.findPath("status").asText() : "";
65
+ }
66
+ };
67
+
68
+ return Multimaps.index(data, function);
69
+ }
70
+ }
@@ -0,0 +1,58 @@
1
+ package org.embulk.output.mailchimp.model;
2
+
3
+ import com.fasterxml.jackson.annotation.JsonCreator;
4
+ import org.embulk.config.ConfigException;
5
+
6
+ /**
7
+ * Created by thangnc on 4/17/17.
8
+ * <p>
9
+ * MailChimp v3 supports 2 types of auth: OAuth and API key.
10
+ */
11
+ public enum AuthMethod
12
+ {
13
+ /**
14
+ * OAuth2 type
15
+ */
16
+ OAUTH("oauth"),
17
+ /**
18
+ * API key type
19
+ */
20
+ API_KEY("api_key");
21
+
22
+ private String type;
23
+
24
+ AuthMethod(final String type)
25
+ {
26
+ this.type = type;
27
+ }
28
+
29
+ /**
30
+ * Gets type.
31
+ *
32
+ * @return the type
33
+ */
34
+ public String getType()
35
+ {
36
+ return type;
37
+ }
38
+
39
+ /**
40
+ * Find by type auth method.
41
+ *
42
+ * @param type the type
43
+ * @return the auth method
44
+ */
45
+ @JsonCreator
46
+ public static AuthMethod findByType(final String type)
47
+ {
48
+ for (AuthMethod method : values()) {
49
+ if (method.getType().equals(type.toLowerCase())) {
50
+ return method;
51
+ }
52
+ }
53
+
54
+ throw new ConfigException(
55
+ String.format("Unknown auth_method '%s'. Supported targets are [api_key, oauth]",
56
+ type));
57
+ }
58
+ }
@@ -0,0 +1,30 @@
1
+ package org.embulk.output.mailchimp.model;
2
+
3
+ /**
4
+ * Created by thangnc on 5/5/17.
5
+ */
6
+ public class CategoriesResponse
7
+ {
8
+ private String id;
9
+ private String title;
10
+
11
+ public String getId()
12
+ {
13
+ return id;
14
+ }
15
+
16
+ public void setId(String id)
17
+ {
18
+ this.id = id;
19
+ }
20
+
21
+ public String getTitle()
22
+ {
23
+ return title;
24
+ }
25
+
26
+ public void setTitle(String title)
27
+ {
28
+ this.title = title;
29
+ }
30
+ }
@@ -0,0 +1,56 @@
1
+ package org.embulk.output.mailchimp.model;
2
+
3
+ import com.fasterxml.jackson.annotation.JsonProperty;
4
+
5
+ /**
6
+ * The POJO class of MailChimp v3 error. Each representing an email address that could not be
7
+ * added to the list or updated and an error message providing more details.
8
+ * <p>
9
+ * Created by thangnc on 4/26/17.
10
+ */
11
+ public class ErrorResponse
12
+ {
13
+ @JsonProperty("email_address")
14
+ private String emailAddress;
15
+ private String error;
16
+
17
+ /**
18
+ * Gets email address.
19
+ *
20
+ * @return the email address
21
+ */
22
+ public String getEmailAddress()
23
+ {
24
+ return emailAddress;
25
+ }
26
+
27
+ /**
28
+ * Sets email address.
29
+ *
30
+ * @param emailAddress the email address
31
+ */
32
+ public void setEmailAddress(String emailAddress)
33
+ {
34
+ this.emailAddress = emailAddress;
35
+ }
36
+
37
+ /**
38
+ * Gets error.
39
+ *
40
+ * @return the error
41
+ */
42
+ public String getError()
43
+ {
44
+ return error;
45
+ }
46
+
47
+ /**
48
+ * Sets error.
49
+ *
50
+ * @param error the error
51
+ */
52
+ public void setError(String error)
53
+ {
54
+ this.error = error;
55
+ }
56
+ }
@@ -0,0 +1,24 @@
1
+ package org.embulk.output.mailchimp.model;
2
+
3
+ import com.fasterxml.jackson.annotation.JsonProperty;
4
+
5
+ import java.util.List;
6
+
7
+ /**
8
+ * Created by thangnc on 5/8/17.
9
+ */
10
+ public class InterestCategoriesResponse
11
+ {
12
+ @JsonProperty("categories")
13
+ private List<CategoriesResponse> categories;
14
+
15
+ public List<CategoriesResponse> getCategories()
16
+ {
17
+ return categories;
18
+ }
19
+
20
+ public void setCategories(List<CategoriesResponse> categories)
21
+ {
22
+ this.categories = categories;
23
+ }
24
+ }
@@ -0,0 +1,30 @@
1
+ package org.embulk.output.mailchimp.model;
2
+
3
+ /**
4
+ * Created by thangnc on 5/8/17.
5
+ */
6
+ public class InterestResponse
7
+ {
8
+ private String id;
9
+ private String name;
10
+
11
+ public String getId()
12
+ {
13
+ return id;
14
+ }
15
+
16
+ public void setId(String id)
17
+ {
18
+ this.id = id;
19
+ }
20
+
21
+ public String getName()
22
+ {
23
+ return name;
24
+ }
25
+
26
+ public void setName(String name)
27
+ {
28
+ this.name = name;
29
+ }
30
+ }
@@ -0,0 +1,24 @@
1
+ package org.embulk.output.mailchimp.model;
2
+
3
+ import com.fasterxml.jackson.annotation.JsonProperty;
4
+
5
+ import java.util.List;
6
+
7
+ /**
8
+ * Created by thangnc on 5/8/17.
9
+ */
10
+ public class InterestsResponse
11
+ {
12
+ @JsonProperty("interests")
13
+ private List<InterestResponse> interests;
14
+
15
+ public List<InterestResponse> getInterests()
16
+ {
17
+ return interests;
18
+ }
19
+
20
+ public void setInterests(List<InterestResponse> interests)
21
+ {
22
+ this.interests = interests;
23
+ }
24
+ }
@@ -0,0 +1,64 @@
1
+ package org.embulk.output.mailchimp.model;
2
+
3
+ import com.fasterxml.jackson.annotation.JsonCreator;
4
+ import org.embulk.config.ConfigException;
5
+
6
+ /**
7
+ * Created by thangnc on 4/26/17.
8
+ */
9
+ public enum MemberStatus
10
+ {
11
+ /**
12
+ * Subscribed member status.
13
+ */
14
+ SUBSCRIBED("subscribed"),
15
+ /**
16
+ * Pending member status. This status will enable to send confirmation email to user
17
+ */
18
+ PENDING("pending"),
19
+ /**
20
+ * Unsubscribed member status.
21
+ */
22
+ UNSUBSCRIBED("unsubscribed"),
23
+ /**
24
+ * Cleaned member status. Remove out of list of members but keep log
25
+ */
26
+ CLEANED("cleaned");
27
+
28
+ private String type;
29
+
30
+ MemberStatus(final String type)
31
+ {
32
+ this.type = type;
33
+ }
34
+
35
+ /**
36
+ * Gets type.
37
+ *
38
+ * @return the type
39
+ */
40
+ public String getType()
41
+ {
42
+ return type;
43
+ }
44
+
45
+ /**
46
+ * Find by type auth method.
47
+ *
48
+ * @param type the type
49
+ * @return the auth method
50
+ */
51
+ @JsonCreator
52
+ public static MemberStatus findByType(final String type)
53
+ {
54
+ for (MemberStatus method : values()) {
55
+ if (method.getType().equals(type.toLowerCase())) {
56
+ return method;
57
+ }
58
+ }
59
+
60
+ throw new ConfigException(
61
+ String.format("Unknown status '%s'. Supported statuses are [subscribed, pending, unsubscribed, cleaned]",
62
+ type));
63
+ }
64
+ }
@@ -0,0 +1,36 @@
1
+ package org.embulk.output.mailchimp.model;
2
+
3
+ import com.fasterxml.jackson.annotation.JsonProperty;
4
+
5
+ /**
6
+ * The POJO class of MailChimp v3 metadata
7
+ * <p>
8
+ * Created by thangnc on 5/16/17.
9
+ */
10
+ public class MetaDataResponse
11
+ {
12
+ private String dc;
13
+
14
+ @JsonProperty("api_endpoint")
15
+ private String apiEndpoint;
16
+
17
+ public String getDc()
18
+ {
19
+ return dc;
20
+ }
21
+
22
+ public void setDc(String dc)
23
+ {
24
+ this.dc = dc;
25
+ }
26
+
27
+ public String getApiEndpoint()
28
+ {
29
+ return apiEndpoint;
30
+ }
31
+
32
+ public void setApiEndpoint(String apiEndpoint)
33
+ {
34
+ this.apiEndpoint = apiEndpoint;
35
+ }
36
+ }
@@ -0,0 +1,105 @@
1
+ package org.embulk.output.mailchimp.model;
2
+
3
+ import com.fasterxml.jackson.annotation.JsonProperty;
4
+
5
+ import java.util.List;
6
+
7
+ /**
8
+ * The POJO class of MailChimp v3 response
9
+ * <p>
10
+ * Created by thangnc on 4/26/17.
11
+ */
12
+ public class ReportResponse
13
+ {
14
+ @JsonProperty("total_created")
15
+ private int totalCreated;
16
+
17
+ @JsonProperty("total_updated")
18
+ private int totalUpdated;
19
+
20
+ @JsonProperty("error_count")
21
+ private int errorCount;
22
+
23
+ @JsonProperty("errors")
24
+ private List<ErrorResponse> errors;
25
+
26
+ /**
27
+ * The total number of created records matching the query, irrespective of pagination.
28
+ *
29
+ * @return the total created
30
+ */
31
+ public int getTotalCreated()
32
+ {
33
+ return totalCreated;
34
+ }
35
+
36
+ /**
37
+ * Sets total created.
38
+ *
39
+ * @param totalCreated the total created
40
+ */
41
+ public void setTotalCreated(int totalCreated)
42
+ {
43
+ this.totalCreated = totalCreated;
44
+ }
45
+
46
+ /**
47
+ * The total number of updated records matching the query, irrespective of pagination.
48
+ *
49
+ * @return the total updated
50
+ */
51
+ public int getTotalUpdated()
52
+ {
53
+ return totalUpdated;
54
+ }
55
+
56
+ /**
57
+ * Sets total updated.
58
+ *
59
+ * @param totalUpdated the total updated
60
+ */
61
+ public void setTotalUpdated(int totalUpdated)
62
+ {
63
+ this.totalUpdated = totalUpdated;
64
+ }
65
+
66
+ /**
67
+ * The total number of error records matching the query, irrespective of pagination.
68
+ *
69
+ * @return the error count
70
+ */
71
+ public int getErrorCount()
72
+ {
73
+ return errorCount;
74
+ }
75
+
76
+ /**
77
+ * Sets error count.
78
+ *
79
+ * @param errorCount the error count
80
+ */
81
+ public void setErrorCount(int errorCount)
82
+ {
83
+ this.errorCount = errorCount;
84
+ }
85
+
86
+ /**
87
+ * Gets errors.
88
+ *
89
+ * @return the errors
90
+ */
91
+ public List<ErrorResponse> getErrors()
92
+ {
93
+ return errors;
94
+ }
95
+
96
+ /**
97
+ * Sets errors.
98
+ *
99
+ * @param errors the errors
100
+ */
101
+ public void setErrors(List<ErrorResponse> errors)
102
+ {
103
+ this.errors = errors;
104
+ }
105
+ }