Skip to content

Comments

4561: User functions#1

Merged
k1bs merged 9 commits intomainfrom
k1bs/4561/transfer-module-v1
Nov 19, 2025
Merged

4561: User functions#1
k1bs merged 9 commits intomainfrom
k1bs/4561/transfer-module-v1

Conversation

@k1bs
Copy link
Collaborator

@k1bs k1bs commented Nov 18, 2025

Here are the user functions that I think we should start with. I'm going to stop here, I think, although the next logical step is to add the four obvious server functions.

@k1bs k1bs requested a review from fastjames November 18, 2025 23:50
@coderabbitai
Copy link

coderabbitai bot commented Nov 18, 2025

Summary by CodeRabbit

  • New Features

    • Added comprehensive AWS Transfer Family API for user management, including create, delete, describe, and update operations with configurable options.
  • Tests

    • Expanded test suite with comprehensive validation of operations, headers, data transformations, and nested structures.
  • Chores

    • Added Dialyzer static type checking configuration.
    • Added GitHub Actions CI workflow for automated testing across multiple OTP and Elixir versions.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds AWS Transfer Family user-management API functions and helpers to ExAws.Transfer; updates mix.exs with package and Dialyzer config; replaces tests with an extensive async suite; adds Dialyzer PLT ignores and a GitHub Actions CI workflow.

Changes

Cohort / File(s) Summary
AWS Transfer API Implementation
lib/ex_aws/transfer.ex
Added @namespace "TransferService", public operations delete_user/2, describe_user/2, create_user/3-4, update_user/2-3, public types create_user_opts and update_user_opts, docs, request/2, and helper functions: pascalize_keys/1, pascalize_key/1, format_opts/1, normalize_home_directory_type/1, normalize_tags/1.
Project Configuration
mix.exs
Added package() metadata and included package: package() in project config; added :dialyxir as a :dev dependency and dialyzer settings (plt paths, plt file, halt_exit_status).
Test Suite
test/ex_aws_transfer_test.exs
Renamed test module to ExAws.TransferTest, enabled async: true, and added comprehensive tests for describe_user, delete_user, create_user (3/4-arg), and update_user (2/3-arg) validating operation structs, headers, PascalCase conversions, enum normalization, tags formatting, and nested structures (PosixProfile, HomeDirectoryMappings).
Repository Ignore
.gitignore
Added ignore patterns for Dialyzer PLT files: priv/plts/*.plt, priv/plts/*.plt.hash.
CI Workflow
.github/workflows/ci.yml
Added GitHub Actions workflow for matrix builds, formatting checks, tests, and Dialyzer on PRs to main.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Transfer as ExAws.Transfer
    participant Normalizer
    participant OpBuilder as ExAws.Operation.JSON

    Caller->>Transfer: create_user(server_id, user_name, role, opts?)
    activate Transfer

    Transfer->>Normalizer: format_opts / pascalize_keys
    activate Normalizer
    Normalizer-->>Transfer: normalized params (PascalCase keys, enums, tags, nested maps)
    deactivate Normalizer

    Transfer->>OpBuilder: request("TransferService", "CreateUser", params)
    activate OpBuilder
    OpBuilder-->>OpBuilder: build Operation.JSON (service:"transfer", data, headers x-amz-target/content-type)
    OpBuilder-->>Transfer: Operation struct
    deactivate OpBuilder

    Transfer-->>Caller: ExAws.Operation.JSON.t()
    deactivate Transfer
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Inspect PascalCase conversion and nested-key handling in pascalize_keys/1 and pascalize_key/1.
  • Verify enum normalization for home_directory_type and tags formatting in format_opts/1 / normalize_tags/1.
  • Confirm request/2 sets correct x-amz-target header, content-type, and uses the TransferService namespace.
  • Validate tests in test/ex_aws_transfer_test.exs cover nested structures (PosixProfile, HomeDirectoryMappings) and combined option cases.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'User functions' directly and clearly describes the main change—adding user functions to the ExAws.Transfer module.
Description check ✅ Passed The description is directly related to the changeset, explaining that the user functions are the proposed initial set and mentioning the next logical step.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch k1bs/4561/transfer-module-v1

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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: 1

🧹 Nitpick comments (1)
lib/ex_aws/transfer.ex (1)

16-18: Fix doctest prompt in piped examples

In the delete_user/2 and describe_user/2 examples, the pipeline is split across two iex> lines:

iex> ExAws.Transfer.delete_user("...", "myuser")
iex> |> ExAws.request()

For valid doctests this second line should use the continuation prompt (...>), e.g.:

iex> ExAws.Transfer.delete_user("s-1234567890abcdef0", "myuser")
...> |> ExAws.request()
{:ok, %{}}

Same adjustment applies to the describe_user/2 example.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ce015a and 9c99dec.

⛔ Files ignored due to path filters (1)
  • mix.lock is excluded by !**/*.lock
📒 Files selected for processing (3)
  • lib/ex_aws/transfer.ex (1 hunks)
  • mix.exs (3 hunks)
  • test/ex_aws_transfer_test.exs (1 hunks)
🔇 Additional comments (5)
mix.exs (3)

11-12: Project metadata wiring looks good

Adding deps: deps() and package: package() into project/0 is idiomatic and sets you up cleanly for publishing without affecting runtime behavior.


27-29: Dev/test‑only tooling dependency is appropriately scoped

Placing your static‑analysis / tooling dependency in the deps list with only: [:dev, :test] and runtime: false keeps production releases lean while still enabling good DX in local/test environments.

If you haven’t already, it’s worth running mix deps.get and your usual CI to confirm this matches your Elixir/OTP matrix.


39-48: Hex package definition is conventional and complete

The package/0 metadata (name, licenses, links, files, maintainers) follows common Hex conventions and should work well for publishing.

test/ex_aws_transfer_test.exs (1)

1-230: Thorough test coverage for the public Transfer API

This suite does a good job pinning the contract of the user functions: it checks the ExAws operation struct shape, headers, and key/option normalization (PascalCase keys, HomeDirectoryType mapping, tag formatting) across describe_user/2, delete_user/2, create_user/3-4, and update_user/2-3, plus a basic integration‑style shape check for passing ops into ExAws.

lib/ex_aws/transfer.ex (1)

260-273: Request helper matches ExAws JSON pattern; headers and service look correct

request/2 building ExAws.Operation.JSON.new(:transfer, %{data: params, headers: [...]}) with x-amz-target of "#{@namespace}.#{pascalize_key(action)}" and the JSON content‑type cleanly centralizes the wire‑level details and is exercised by your tests for each operation.

It’s still worth double‑checking this against the ExAws.Operation.JSON.new/2 documentation for your pinned ex_aws version to ensure the options map shape (%{data: ..., headers: ...}) matches any upstream changes.

Copy link

@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: 0

🧹 Nitpick comments (2)
lib/ex_aws/transfer.ex (2)

8-24: Add a summary sentence to the documentation.

As noted in a previous review, the documentation should start with a summary sentence (e.g., "Deletes a user from a Transfer Family server.") before the Parameters section to maintain consistency with other function docs like describe_user/2 and create_user/3.


236-239: Consider rephrasing the comment for professionalism.

The comment "It grosses me out..." is informal for production code. Consider a more professional phrasing like: "Note: Despite its name, Macro.camelize returns PascalCase rather than camelCase."

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c99dec and 4830a5f.

📒 Files selected for processing (2)
  • lib/ex_aws/transfer.ex (1 hunks)
  • test/ex_aws_transfer_test.exs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/ex_aws_transfer_test.exs (1)
lib/ex_aws/transfer.ex (4)
  • describe_user (54-61)
  • delete_user (26-33)
  • create_user (128-140)
  • update_user (201-212)
🔇 Additional comments (11)
test/ex_aws_transfer_test.exs (6)

6-29: LGTM!

The test coverage for describe_user/2 is comprehensive, validating the operation structure, service identifier, data payload, and both required headers.


31-54: LGTM!

The test coverage for delete_user/2 mirrors the describe_user tests and validates all critical aspects of the operation.


56-141: LGTM!

Excellent test coverage for create_user/3 and create_user/4. The tests thoroughly validate option transformations including pascalization, home_directory_type normalization, tag formatting, and combined options.


143-194: LGTM!

These tests validate the recursive pascalization of nested structures (PosixProfile, HomeDirectoryMappings), confirming that the implementation correctly handles the nested option transformations mentioned in past review comments.


196-273: LGTM!

Comprehensive test coverage for update_user/2 and update_user/3, validating all option transformations and ensuring consistency with the create_user behavior.


275-318: LGTM!

The nested structure tests for update_user ensure consistency with create_user, and the integration test validates that the generated operations are compatible with ExAws functions.

lib/ex_aws/transfer.ex (5)

35-61: LGTM!

The describe_user/2 function is well-documented with a clear summary, parameters, examples, and implementation.


63-140: LGTM!

The create_user/3 and create_user/4 functions are well-implemented with comprehensive documentation. While a past review suggested condensing the two @spec declarations, having separate specs for different arities is a valid and common Elixir pattern, especially with default parameters.


142-212: LGTM!

The update_user/2 and update_user/3 functions follow the same pattern as create_user and are well-implemented. The dual @spec declarations are consistent with the create_user approach and represent a valid Elixir pattern.


241-268: LGTM!

The option formatting and normalization functions correctly handle the conversions needed for AWS API compatibility. The normalize_tags/1 fallback pattern allows flexibility for already-formatted or custom tag structures.


270-283: LGTM!

The request/2 function correctly constructs the ExAws.Operation.JSON struct with appropriate headers for AWS Transfer Family API calls. The use of @namespace and pascalized action names aligns with AWS Transfer Service conventions.

coderabbitai[bot]
coderabbitai bot previously approved these changes Nov 19, 2025
Copy link

@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: 0

🧹 Nitpick comments (1)
lib/ex_aws/transfer.ex (1)

109-117: Consider tightening option types for better tooling

The create_user_opts/update_user_opts types are correct but quite generic (map() for nested shapes). If you want stronger dialyzer and IDE help, you could introduce named types for nested structs, e.g.:

+  @type posix_profile :: %{
+          uid: pos_integer(),
+          gid: pos_integer(),
+          optional(:secondary_gids) => [pos_integer()]
+        }
+
+  @type tag :: %{key: binary(), value: binary()}
+
   @type create_user_opts :: [
     {:home_directory, binary()},
     {:home_directory_type, :path | :logical},
-    {:home_directory_mappings, [map()]},
+    {:home_directory_mappings, [map()]},
     {:policy, binary()},
-    {:posix_profile, map()},
-    {:ssh_public_key_body, binary()},
-    {:tags, [map()]}
+    {:posix_profile, posix_profile()},
+    {:ssh_public_key_body, binary()},
+    {:tags, [tag()]}
   ]

and reuse posix_profile() in update_user_opts.

Also applies to: 185-192

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4830a5f and 2589e94.

📒 Files selected for processing (1)
  • lib/ex_aws/transfer.ex (1 hunks)
🔇 Additional comments (7)
lib/ex_aws/transfer.ex (7)

2-7: Module docs and namespace are clear and focused

The short @moduledoc plus centralized @namespace "TransferService" keep the module purpose and AWS target prefix obvious and easy to maintain.


8-34: Delete/describe wrappers are thin and consistent

Both delete_user/2 and describe_user/2 build minimal param maps and delegate to request/2 in a consistent way; signatures, docs, and examples line up cleanly.

Also applies to: 36-62


118-141: create_user/4 normalization and merge pipeline looks solid

The opts |> format_opts() |> pascalize_keys() |> Map.merge(%{...}) pipeline gives you normalized home_directory_type/tags and PascalCase keys (including nested maps) while ensuring required params (ServerId, UserName, Role) always win; the default opts \\ [] covers the arity-3 variant cleanly.


193-213: update_user/3 mirrors create_user/4 behavior correctly

update_user/3 reuses the same normalization + PascalCase path as create_user/4 and only merges in ServerId/UserName, which matches the documented surface for updates without surprising extra fields.


217-231: Recursive PascalCase conversion correctly handles nested structures

pascalize_keys/1 plus pascalize_value/1 (list/map fallthrough) give you full recursive key conversion while leaving scalar values untouched, which is exactly what you want for deeply nested option maps.


242-269: Option normalization helpers are cohesive and side‑effect free

format_opts/1 cleanly composes normalize_home_directory_type/1 and normalize_tags/1, with normalize_tags/1 safely handling both atom‑keyed and already‑normalized tag maps and leaving unknown shapes alone.


271-283: Shared request/2 helper keeps request construction DRY

Centralizing ExAws.Operation.JSON.new/2 with consistent headers and x-amz-target construction via @namespace + pascalize_key/1 makes the public functions very small and reduces the chance of per‑call header drift.

coderabbitai[bot]
coderabbitai bot previously approved these changes Nov 19, 2025
fastjames
fastjames previously approved these changes Nov 19, 2025
Copy link

@fastjames fastjames left a comment

Choose a reason for hiding this comment

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

🍰

@k1bs k1bs dismissed stale reviews from fastjames and coderabbitai[bot] via 144ff95 November 19, 2025 19:17
coderabbitai[bot]
coderabbitai bot previously approved these changes Nov 19, 2025
coderabbitai[bot]
coderabbitai bot previously approved these changes Nov 19, 2025
Copy link

@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: 0

🧹 Nitpick comments (2)
lib/ex_aws/transfer.ex (2)

109-117: Consider tightening option type specifications.

The option types use generic map() for :posix_profile and :home_directory_mappings. While this is functional, more specific types would improve documentation and Dialyzer coverage:

@type posix_profile :: %{
  required(:uid) => non_neg_integer(),
  required(:gid) => non_neg_integer(),
  optional(:secondary_gids) => [non_neg_integer()]
}

@type home_directory_mapping :: %{
  required(:entry) => binary(),
  required(:target) => binary(),
  optional(:type) => binary()
}

@type create_user_opts :: [
  {:home_directory, binary()},
  {:home_directory_type, :path | :logical},
  {:home_directory_mappings, [home_directory_mapping()]},
  {:policy, binary()},
  {:posix_profile, posix_profile()},
  {:ssh_public_key_body, binary()},
  {:tags, [%{required(:key) => binary(), required(:value) => binary()}]}
]

259-270: Consider validating tag format.

The pass-through clause (tag -> tag) allows malformed tags to reach AWS, which will fail with a less clear error. For better developer experience, consider validating or warning on unexpected tag formats:

defp normalize_tags(%{tags: tags} = opts) when is_list(tags) do
  normalized_tags =
    Enum.map(tags, fn
      %{key: k, value: v} -> %{"Key" => to_string(k), "Value" => to_string(v)}
      %{"Key" => _k, "Value" => _v} = tag -> tag
      invalid -> 
        raise ArgumentError, "Invalid tag format: #{inspect(invalid)}. Expected %{key: ..., value: ...}"
    end)

  Map.put(opts, :tags, normalized_tags)
end

That said, deferring validation to AWS is a valid library design choice—this is just a DX refinement.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01aea03 and e04b571.

📒 Files selected for processing (3)
  • lib/ex_aws/transfer.ex (1 hunks)
  • mix.exs (3 hunks)
  • test/ex_aws_transfer_test.exs (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
test/ex_aws_transfer_test.exs (1)
lib/ex_aws/transfer.ex (4)
  • describe_user (55-62)
  • delete_user (27-34)
  • create_user (129-141)
  • update_user (202-213)
🔇 Additional comments (11)
mix.exs (3)

12-16: LGTM! Clean Dialyzer configuration.

The Dialyzer setup is solid—PLT paths are properly configured and halt_exit_status: true ensures type errors fail the build.


29-36: LGTM! Dependency configuration is appropriate.

The permissive Jason requirement (~> 1.2) gives downstream consumers flexibility while the resolved version (1.4.4) is current. Dialyxir is correctly scoped to dev/test only.


44-54: LGTM! Package metadata is complete.

All required fields for Hex publishing are present: license, links, files, and maintainers.

test/ex_aws_transfer_test.exs (1)

1-337: LGTM! Comprehensive and well-structured test suite.

The test coverage is excellent: validates operation construction, header formatting, PascalCase transformations, nested structure handling, and ExAws integration. The use of async: true is appropriate for these isolated unit tests.

lib/ex_aws/transfer.ex (7)

1-6: LGTM! Clean module setup.

Module documentation is clear and the namespace correctly identifies the AWS Transfer Family service.


8-34: LGTM! Well-documented and correctly implemented.

The function properly constructs the DeleteUser request parameters and includes comprehensive documentation with examples and AWS API references.


36-62: LGTM! Consistent with delete_user pattern.

The implementation correctly follows the same structure as delete_user/2 with proper parameter construction and documentation.


143-213: LGTM! Correctly implements UpdateUser operation.

The implementation follows the same solid pattern as create_user and correctly omits :tags and :ssh_public_key_body (those can't be updated via UpdateUser per AWS API).


249-257: LGTM! Home directory type normalization is correct.

The function properly converts the friendly atoms (:path, :logical) to AWS-expected uppercase strings and passes through string values unchanged.


217-240: LGTM! Recursive pascalization correctly handles nested structures.

The recursive implementation properly transforms keys at all nesting levels, addressing the concern raised in previous reviews. The comment about Macro.camelize returning PascalCase is a nice touch.


272-285: LGTM! Request builder correctly formats AWS Transfer Family operations.

The function properly constructs ExAws.Operation.JSON with the correct service identifier, data payload, and protocol headers (x-amz-target and content-type).

@k1bs k1bs merged commit 87f4446 into main Nov 19, 2025
8 checks passed
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.

2 participants