Skip to content

Add Contact API functionality#38

Merged
gaalferov merged 3 commits intomainfrom
feature/contacts
May 29, 2025
Merged

Add Contact API functionality#38
gaalferov merged 3 commits intomainfrom
feature/contacts

Conversation

@gaalferov
Copy link
Copy Markdown
Collaborator

@gaalferov gaalferov commented May 25, 2025

Motivation

Support new functionality (Contacts API)
https://help.mailtrap.io/article/147-contacts

Changes

  • Added new sub layer Contact into MailtrapGeneralClient
    • getAllContactLists
    • createContact
    • updateContact
    • deleteContact
  • Added new tests
  • Added examples

How to test

composer test

OR run PHP code from the example

(new MailtrapGeneralClient($config))
    ->contacts($accountId)
    ->getAllContactLists();

Summary by CodeRabbit

  • New Features
    • Introduced Contacts API functionality for managing contacts within an account.
    • Added methods for creating, updating, retrieving contact lists, and deleting contacts.
    • Included a new example script demonstrating contact management operations.
  • Documentation
    • Updated changelog to include the new Contacts API feature.
  • Tests
    • Added extensive tests covering contact list retrieval, contact creation, updates, deletion, and error handling.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2025

Walkthrough

A new Contacts API feature was added, including a client class, DTOs for creating and updating contacts, and tests. The Mailtrap general client was updated to include the new endpoint. An example script and changelog entry were also introduced to document the functionality.

Changes

File(s) Change Summary
CHANGELOG.md Added entry for version 3.1.0 (2025-05-27) noting the addition of Contacts API functionality.
examples/general/contacts.php New example script demonstrating usage of the Contacts API for CRUD operations with Mailtrap.
src/Api/General/Contact.php New Contact API client class for managing contacts, supporting CRUD operations and list retrieval.
src/DTO/Request/Contact/ContactInterface.php New interface defining contract for contact DTOs, requiring getEmail() and getFields() methods.
src/DTO/Request/Contact/CreateContact.php New DTO class for contact creation, with properties, getters, static factory, and array serialization.
src/DTO/Request/Contact/UpdateContact.php New DTO class for contact updates, with properties, getters, static factory, and array serialization.
src/MailtrapGeneralClient.php Added contacts endpoint mapping and method annotation to the general client.
tests/Api/General/ContactTest.php New PHPUnit test class covering all main methods of the Contact API client, including error handling.
tests/MailtrapGeneralClientTest.php Updated provider to instantiate the contacts API client with required arguments for testing.

Sequence Diagram(s)

sequenceDiagram
    participant UserScript
    participant MailtrapGeneralClient
    participant ContactAPI
    participant MailtrapAPI

    UserScript->>MailtrapGeneralClient: contacts(accountId)
    MailtrapGeneralClient->>ContactAPI: new Contact(config, accountId)
    UserScript->>ContactAPI: createContact(CreateContactDTO)
    ContactAPI->>MailtrapAPI: POST /accounts/{accountId}/contacts
    MailtrapAPI-->>ContactAPI: Response (created contact)
    ContactAPI-->>UserScript: Response

    UserScript->>ContactAPI: updateContactById(contactId, UpdateContactDTO)
    ContactAPI->>MailtrapAPI: PUT /accounts/{accountId}/contacts/{contactId}
    MailtrapAPI-->>ContactAPI: Response (updated contact)
    ContactAPI-->>UserScript: Response

    UserScript->>ContactAPI: deleteContactByEmail(email)
    ContactAPI->>MailtrapAPI: DELETE /accounts/{accountId}/contacts/email/{email}
    MailtrapAPI-->>ContactAPI: Response (deletion status)
    ContactAPI-->>UserScript: Response
Loading

Suggested reviewers

  • mklocek
  • leonid-shevtsov

Poem

In burrows deep, new contacts hop,
With DTOs and tests that never stop.
A client grows, its features bloom,
In Mailtrap’s fields, there’s extra room!
From creation, update, to delete—
This bunny’s code is quite a feat!
🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 445fb4d and 747a7df.

📒 Files selected for processing (3)
  • src/Api/General/Contact.php (1 hunks)
  • src/DTO/Request/Contact/UpdateContact.php (1 hunks)
  • tests/Api/General/ContactTest.php (1 hunks)
🔇 Additional comments (6)
src/Api/General/Contact.php (2)

16-21: Well-designed API client with modern PHP practices.

The class design follows good practices:

  • Extends AbstractApi for common functionality
  • Uses PHP 8 constructor property promotion for clean code
  • Implements GeneralInterface for consistency
  • Proper dependency injection with ConfigInterface

129-132: Clean path construction with proper string formatting.

The getBasePath() method uses sprintf() for clean and readable URL construction, making it easy to understand the API endpoint pattern.

tests/Api/General/ContactTest.php (4)

26-34: Excellent test setup with proper mocking strategy.

The setUp method correctly mocks only the HTTP methods while preserving the actual business logic, allowing for thorough testing of the Contact class behavior.


135-160: Comprehensive test validates null filtering behavior.

This test specifically verifies that the UpdateContact DTO correctly excludes null values from the payload, which validates the implementation discussed in previous reviews. The use of a callback assertion is an excellent way to verify the exact payload structure.


190-213: Good error handling test coverage.

The test properly validates that validation errors from the API are correctly transformed into HttpClientException with readable error messages, ensuring proper error handling in client applications.


120-120: URL encoding properly tested in mock expectations.

The test expectations correctly verify that urlencode() is applied to contact identifiers in the URL paths, ensuring the security fix is properly validated.

Also applies to: 181-181

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@gaalferov gaalferov marked this pull request as ready for review May 26, 2025 07:16
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (4)
examples/general/contacts.php (2)

58-79: Consider demonstrating different update scenarios.

Both updateContactById and updateContactByEmail use identical contact data, which doesn't clearly demonstrate the difference between the two methods. Consider using different data or adding comments to highlight when each method would be preferred.

 // Update contact by ID
 $response = $contacts->updateContactById(
     '019706a8-0000-0000-0000-4f26816b467a',
     UpdateContact::init(
-        'john.smith@example.com',
-        ['first_name' => 'John', 'last_name' => 'Smith'], // Fields
+        'john.doe@example.com', // Update email
+        ['first_name' => 'John', 'last_name' => 'Doe'], // Update name
         [3], // List IDs to which the contact will be added
         [1, 2], // List IDs from which the contact will be removed
         true // Unsubscribe the contact
     )
 );

 // OR update contact by email
 $response = $contacts->updateContactByEmail(
     'john.smith@example.com',
     UpdateContact::init(
         'john.smith@example.com',
-        ['first_name' => 'John', 'last_name' => 'Smith'], // Fields
+        ['first_name' => 'Jane', 'last_name' => 'Smith'], // Update first name only
         [3], // List IDs to which the contact will be added
         [1, 2], // List IDs from which the contact will be removed
-        true // Unsubscribe the contact
+        false // Keep subscribed
     )
 );

95-98: Variable reuse masks the first delete operation result.

Both delete operations assign to the same $response variable, which means the result of deleteContactById is immediately overwritten. This could confuse users about which operation's result is being displayed.

 // Delete contact by ID
-$response = $contacts->deleteContactById('019706a8-0000-0000-0000-4f26816b467a');
+$responseById = $contacts->deleteContactById('019706a8-0000-0000-0000-4f26816b467a');

 // OR delete contact by email
-$response = $contacts->deleteContactByEmail('john.smith@example.com');
+$responseByEmail = $contacts->deleteContactByEmail('john.smith@example.com');

 // print the response body (array)
-var_dump(ResponseHelper::toArray($response));
+var_dump(ResponseHelper::toArray($responseByEmail));
tests/Api/General/ContactTest.php (1)

60-61: Minor inconsistency with variable usage.

The $fakeEmail variable is defined but the email value is hardcoded in the CreateContact constructor. Consider using the variable for consistency.

 $fakeEmail = 'test@example.com';
-$contactDTO = new CreateContact('test@example.com', ['first_name' => 'John'], [1, 2]);
+$contactDTO = new CreateContact($fakeEmail, ['first_name' => 'John'], [1, 2]);
src/Api/General/Contact.php (1)

55-58: Consider adding input validation for better error handling.

The public methods don't validate their string parameters. Consider adding validation to provide better error messages for empty or invalid inputs before making HTTP requests.

Example validation for contact ID/email parameters:

public function updateContactById(string $contactId, UpdateContact $contact): ResponseInterface
{
+    if (empty(trim($contactId))) {
+        throw new \InvalidArgumentException('Contact ID cannot be empty');
+    }
    return $this->updateContact($contactId, $contact);
}

public function updateContactByEmail(string $email, UpdateContact $contact): ResponseInterface
{
+    if (empty(trim($email))) {
+        throw new \InvalidArgumentException('Email cannot be empty');
+    }
+    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
+        throw new \InvalidArgumentException('Invalid email format');
+    }
    return $this->updateContact($email, $contact);
}

Also applies to: 67-70, 78-81, 89-92

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a17c7bc and 445fb4d.

📒 Files selected for processing (9)
  • CHANGELOG.md (1 hunks)
  • examples/general/contacts.php (1 hunks)
  • src/Api/General/Contact.php (1 hunks)
  • src/DTO/Request/Contact/ContactInterface.php (1 hunks)
  • src/DTO/Request/Contact/CreateContact.php (1 hunks)
  • src/DTO/Request/Contact/UpdateContact.php (1 hunks)
  • src/MailtrapGeneralClient.php (2 hunks)
  • tests/Api/General/ContactTest.php (1 hunks)
  • tests/MailtrapGeneralClientTest.php (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/DTO/Request/Contact/ContactInterface.php (3)
src/Api/General/Contact.php (1)
  • Contact (16-133)
src/DTO/Request/Contact/CreateContact.php (2)
  • getEmail (24-27)
  • getFields (29-32)
src/DTO/Request/Contact/UpdateContact.php (2)
  • getEmail (31-34)
  • getFields (36-39)
src/MailtrapGeneralClient.php (2)
src/Api/General/Permission.php (1)
  • Permission (16-72)
src/Api/General/Contact.php (1)
  • Contact (16-133)
src/DTO/Request/Contact/UpdateContact.php (3)
src/Api/General/Contact.php (2)
  • Contact (16-133)
  • __construct (18-21)
src/DTO/Request/Contact/CreateContact.php (5)
  • __construct (12-17)
  • init (19-22)
  • getEmail (24-27)
  • getFields (29-32)
  • toArray (39-46)
src/DTO/Request/Contact/ContactInterface.php (2)
  • getEmail (14-14)
  • getFields (21-21)
tests/MailtrapGeneralClientTest.php (1)
tests/MailtrapTestCase.php (1)
  • getConfigMock (38-57)
🔇 Additional comments (14)
CHANGELOG.md (1)

1-2: LGTM! Well-formatted changelog entry.

The changelog entry properly documents the new Contacts API functionality and follows the established format and versioning pattern.

src/DTO/Request/Contact/ContactInterface.php (1)

7-22: LGTM! Well-designed interface following best practices.

The ContactInterface provides a clean contract for contact DTOs with:

  • Appropriate extension of RequestInterface
  • Clear method signatures with specific return types
  • Comprehensive PHPDoc documentation
  • Follows SOLID interface segregation principle
src/MailtrapGeneralClient.php (2)

11-11: LGTM! Method annotation follows established pattern.

The contacts(int $accountId) method annotation correctly specifies the parameter type and return type, consistent with other General API endpoints like permissions and users.


21-21: LGTM! API mapping correctly integrates the Contact class.

The contacts mapping entry follows the established pattern and properly maps to the Api\General\Contact class, maintaining consistency with the existing API structure.

tests/MailtrapGeneralClientTest.php (1)

31-31: LGTM! Test correctly handles Contact class instantiation.

Adding 'contacts' to the match expression ensures the Contact class is instantiated with both the config mock and account ID, which aligns with its constructor signature requirement (similar to permissions and users classes).

src/DTO/Request/Contact/CreateContact.php (1)

10-47: LGTM! Well-structured DTO with clean implementation.

The class follows excellent practices:

  • Immutable design with private promoted properties
  • Static factory method for convenient instantiation
  • Consistent getter methods
  • Proper array serialization with snake_case keys matching API expectations

The implementation aligns well with the ContactInterface and integrates cleanly with the Contact API client.

tests/Api/General/ContactTest.php (1)

19-232: Excellent test coverage with comprehensive scenarios.

The test class provides thorough coverage of the Contact API functionality:

  • All CRUD operations are tested
  • Both ID and email-based operations are covered
  • Error scenarios are properly tested with exception expectations
  • Mock setup correctly isolates the HTTP layer
  • Response assertions validate both structure and content

The test structure follows PHPUnit best practices and will provide good confidence in the Contact API implementation.

src/Api/General/Contact.php (7)

1-11: LGTM! Clean imports and namespace declaration.

The file header follows PHP best practices with strict types declaration and appropriate namespace organization. All imports are necessary and well-organized.


16-21: LGTM! Modern constructor with proper dependency injection.

The class properly extends the abstract base class and uses PHP 8 constructor property promotion for the account ID parameter. The dependency injection pattern is correctly implemented.


28-33: LGTM! Simple and clean implementation.

The method correctly implements the API call pattern using the inherited HTTP methods and response handling.


41-46: LGTM! Proper DTO usage and consistent API pattern.

The method correctly uses the CreateContact DTO and follows the established API request pattern with proper body structure.


55-58: LGTM! Good use of delegation pattern.

Both methods properly delegate to the private helper method, following the DRY principle while providing clear public APIs for different identifier types.

Also applies to: 67-70


78-81: LGTM! Consistent delegation pattern.

Both delete methods follow the same clean delegation pattern as the update methods.

Also applies to: 89-92


129-132: LGTM! Clean path construction.

The base path construction properly uses sprintf for string formatting and correctly incorporates the host and account ID.

Comment thread examples/general/contacts.php
Comment thread src/DTO/Request/Contact/UpdateContact.php
Comment thread src/Api/General/Contact.php
Comment thread CHANGELOG.md
Comment thread src/Api/General/Contact.php
@gaalferov gaalferov merged commit b48e1d1 into main May 29, 2025
20 checks passed
@gaalferov gaalferov deleted the feature/contacts branch May 29, 2025 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants