Skip to content
This repository was archived by the owner on Sep 30, 2025. It is now read-only.

Comments

feat/chapter 6#9

Merged
TN19N merged 2 commits intomainfrom
8-feat/chapter-6
Aug 28, 2025
Merged

feat/chapter 6#9
TN19N merged 2 commits intomainfrom
8-feat/chapter-6

Conversation

@TN19N
Copy link
Owner

@TN19N TN19N commented Aug 28, 2025

Summary by CodeRabbit

  • New Features
    • Added robust server-side validation for subscription name and email (Unicode-aware length, forbidden characters, and email format).
  • Bug Fixes
    • Invalid subscription requests now return HTTP 400 with clearer error messages, preventing malformed inputs from being accepted.
  • Tests
    • Expanded coverage with edge cases and property-based tests for email validity and name constraints.
  • Chores
    • Introduced new libraries for validation, Unicode handling, and test data generation.

TN19N added 2 commits August 28, 2025 14:04
This commit refactors the dependencies and adds domain logic for the
subscriber module. This includes the creation of a `Subscriber` struct
with `email` and `name` fields. It also introduces email and name
validation using the `validator` crate.

Additionally, the commit updates the `Cargo.toml` file to include the
necessary dependencies, such as `validator`, `claims`,
`unicode-segmentation`, and `fake`. The `src/domain` and
`src/domain/subscriber` modules have been created to provide a
structured organization for the domain logic.
This commit refactors the email validation to provide more information
and adds a check to the insert query for subscriptions.
@TN19N TN19N self-assigned this Aug 28, 2025
@TN19N TN19N added the enhancement New feature or request label Aug 28, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 28, 2025

Walkthrough

Adds a new domain layer with validated Subscriber, SubscriberEmail, and SubscriberName types; wires domain into lib. Updates handler to convert FormData into Subscriber with validation. Replaces model’s create_subscription with create_subscriber. Adds centralized validation error handling (HTTP 400). Expands tests. Introduces new deps for validation and testing.

Changes

Cohort / File(s) Change summary
Dependencies
Cargo.toml
Added deps: claims, unicode-segmentation, validator. Added dev-deps: fake, proptest.
Domain wiring
src/lib.rs, src/domain/mod.rs
Added private mod domain;. Declared mod subscriber; and re-exported pub use subscriber::Subscriber;.
Domain: subscriber types
src/domain/subscriber/*
New module with Subscriber aggregate and validated value objects: SubscriberEmail (email validation via validator) and SubscriberName (non-empty, ≤256 graphemes, forbidden chars). Implements TryFrom<String> and AsRef<str>. Includes unit and property tests.
Handler updates
src/handlers.rs
Made FormData fields pub. subscribe now converts FormData via try_into() and calls mm.create_subscriber(...).await?. Imports adjusted.
Model changes
src/model.rs
Removed Subscription type and create_subscription. Added create_subscriber(domain::Subscriber). Updated bindings and added .check()?. Imports updated to include domain.
Error handling
src/errors.rs
Added Error::ValidationError(validator::ValidationErrors) and mapped to HTTP 400 in IntoResponse; other errors remain HTTP 500.
Tests
tests/tests.rs
Updated failure cases to assert varied expected statuses (400/422). Added scenarios for empty/invalid fields and invalid email.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant H as HTTP Handler (subscribe)
  participant D as Domain (TryFrom<FormData>)
  participant M as ModelManager
  participant DB as Database

  C->>H: POST /subscriptions (FormData)
  H->>D: TryFrom<FormData> -> Subscriber
  alt Validation OK
    D-->>H: Ok(Subscriber)
    H->>M: create_subscriber(Subscriber)
    M->>DB: INSERT subscriber(name, email)
    DB-->>M: Result
    M-->>H: Ok / Error
    alt DB Ok
      H-->>C: 200 OK
    else DB Error
      H-->>C: 500 Internal Server Error
    end
  else Validation Error
    D-->>H: Err(ValidationErrors)
    H-->>C: 400 Bad Request
  end

  note over D,H: Domain enforces email/name rules before DB write
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Poem

I twitch my whiskers at new domain light,
Names and emails vetted, neat and tight.
Hop to handler, try-into we glide,
Model burrows write on the data side.
If rules are broken—400 we squeak—
Then thump with joy: green tests this week! 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 8-feat/chapter-6

🪧 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.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit 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:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

Copy link
Contributor

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

🧹 Nitpick comments (10)
Cargo.toml (1)

11-11: Move claims to dev-dependencies to avoid pulling it in for non-test builds.

claims is only used in tests; keep prod dependency graph lean.

Apply this diff:

 [dependencies]
 axum = { version = "0.8.4", features = ["tracing"] }
-claims = "0.8.0"
 config = "0.15.14"
 dotenvy = "0.15.7"
@@
 [dev-dependencies]
 http-body-util = "0.1.3"
 mime = "0.3.17"
+claims = "0.8.0"
 fake = "4.4.0"
 proptest = "1.7.0"

Also applies to: 28-33

src/domain/subscriber/name.rs (2)

4-5: Derive common traits for ergonomics.

Add Clone, PartialEq, and Eq to ease comparisons, cloning in tests, and struct reuse.

Apply this diff:

-#[derive(Debug)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct SubscriberName(String);

29-35: Also reject ASCII control characters (e.g., newlines, tabs).

Currently only a small forbidden set is blocked; control chars can slip through.

Apply this diff:

         let forbidden_characters = ['/', '(', ')', '"', '<', '>', '\\', '{', '}'];
         let contains_forbidden_characters =
             value.chars().any(|g| forbidden_characters.contains(&g));
         if contains_forbidden_characters {
             return Err(ValidationError::new("INVALID_SUBSCRIBER_NAME")
                 .with_message("subscriber name contains forbidden characters".into()));
         }
+
+        let contains_control = value.chars().any(|c| c.is_control());
+        if contains_control {
+            return Err(ValidationError::new("INVALID_SUBSCRIBER_NAME")
+                .with_message("subscriber name contains control characters".into()));
+        }
src/domain/subscriber/email.rs (2)

3-5: Derive common traits for convenience.

Consistent with SubscriberName, derive Clone, PartialEq, and Eq.

Apply this diff:

-#[derive(Debug)]
+#[derive(Debug, Clone, PartialEq, Eq)]
 pub struct SubscriberEmail(String);

60-61: Remove noisy dbg! in property test.

Avoid cluttering test output for passing cases.

Apply this diff:

-            dbg!(&email);
-
             assert_ok!(SubscriberEmail::try_from(email));
src/domain/mod.rs (1)

3-3: Optionally re-export the validated wrappers for a flatter API surface.

Exposing SubscriberEmail and SubscriberName at domain::* can simplify downstream imports.

Apply these diffs:

- pub use subscriber::Subscriber;
+ pub use subscriber::{Subscriber, SubscriberEmail, SubscriberName};

And in src/domain/subscriber/mod.rs add:

+pub use email::SubscriberEmail;
+pub use name::SubscriberName;
src/domain/subscriber/mod.rs (1)

37-44: Nit: redundant errors.is_empty() in the if let chain.

If both email and name are Some, there can’t be collected errors. Simplify for clarity.

Apply this diff if you keep the current impl:

-        if let Some(email) = email
-            && let Some(name) = name
-            && errors.is_empty()
-        {
+        if let (Some(email), Some(name)) = (email, name) {
             Ok(Self { email, name })
         } else {
             Err(errors)
         }
tests/tests.rs (1)

140-163: Consider a couple more negative cases to harden validation.

  • Name >256 graphemes.
  • Name with forbidden chars (e.g., <script>).
  • Email with leading/trailing spaces (should be invalid unless trimmed).

I can add parameterized cases for these.

src/model.rs (2)

29-31: Minor: avoid intermediate String allocations if Surreal accepts &str.

If the client’s .bind supports &str, pass as_ref() directly.

Apply if supported:

-            .bind(("name", subscriber.name.as_ref().to_string()))
-            .bind(("email", subscriber.email.as_ref().to_string()))
+            .bind(("name", subscriber.name.as_ref()))
+            .bind(("email", subscriber.email.as_ref()))

25-35: Enforce uniqueness on email at the DB level.

Prevent duplicate subscriptions with a UNIQUE index.

Add a migration:

DEFINE INDEX uniq_subscriptions_email ON TABLE subscriptions FIELDS email UNIQUE;
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8055763 and 1e10ca6.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • Cargo.toml (2 hunks)
  • src/domain/mod.rs (1 hunks)
  • src/domain/subscriber/email.rs (1 hunks)
  • src/domain/subscriber/mod.rs (1 hunks)
  • src/domain/subscriber/name.rs (1 hunks)
  • src/errors.rs (1 hunks)
  • src/handlers.rs (1 hunks)
  • src/lib.rs (1 hunks)
  • src/model.rs (2 hunks)
  • tests/tests.rs (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
src/domain/subscriber/mod.rs (2)
src/domain/subscriber/email.rs (1)
  • try_from (15-21)
src/domain/subscriber/name.rs (1)
  • try_from (16-38)
src/domain/subscriber/name.rs (2)
src/domain/subscriber/email.rs (3)
  • as_ref (7-9)
  • try_from (15-21)
  • empty_string_is_rejected (34-37)
src/domain/subscriber/mod.rs (1)
  • try_from (18-45)
src/domain/subscriber/email.rs (2)
src/domain/subscriber/name.rs (3)
  • as_ref (8-10)
  • try_from (16-38)
  • empty_string_is_rejected (65-68)
src/domain/subscriber/mod.rs (1)
  • try_from (18-45)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: check
🔇 Additional comments (7)
src/lib.rs (1)

2-2: Make domain module pub(crate)
Private domain blocks sibling modules (e.g. domain::Subscriber in src/model.rs).

-mod domain;
+pub(crate) mod domain;

Likely an incorrect or invalid review comment.

Cargo.toml (1)

24-27: Deps look appropriate for the new domain validation.

unicode-segmentation and validator align with the added name/email validators.

src/errors.rs (1)

17-19: Good: add ValidationError variant to map validator errors.

The conversion via #[from] is correct and keeps call sites clean.

src/domain/subscriber/mod.rs (1)

37-39: Verify MSRV for let-chains
No explicit rust-version in Cargo.toml or rust-toolchain file; ensure your MSRV is ≥ 1.55 to support if let … && let …, or refactor to a tuple match.

tests/tests.rs (1)

112-138: Good matrix: 422 for missing fields, 400 for invalid/empty.

Covers key cases and matches extractor vs domain validation boundaries.

src/handlers.rs (1)

18-18: No action needed – validation errors already map to HTTP 400. The ValidationError(#[from] validator::ValidationErrors) variant in src/errors.rs is handled in the IntoResponse impl to return StatusCode::BAD_REQUEST.

src/model.rs (1)

25-35: LGTM: switched to domain type, added .check().

Clean hand-off from handler to model using validated domain::Subscriber.

@TN19N TN19N merged commit 533e8dd into main Aug 28, 2025
2 checks passed
@TN19N TN19N deleted the 8-feat/chapter-6 branch August 28, 2025 13:54
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant