Skip to content

Conversation

@VishnuJha100
Copy link

@VishnuJha100 VishnuJha100 commented Dec 9, 2025

🔒 Improved Input Validation & Error Handling for Appointment API

Closes # 58

📝 Description

This PR enhances the Appointment API by introducing robust input validation and standardized error handling. It ensures that all incoming data is properly validated before interacting with the database, improving both reliability and security

🔧 Changes Made

✨ Added Cerberus validation for appointment fields

  • Validates date format: YYYY-MM-DD
  • Validates time format: HH:MM AM/PM
  • Validates status: allowed → ["Pending", "Completed", "Cancelled"]
  • description made optional but validated as a string

🚫 Unknown/extra fields are rejected during validation

📌 Improved structured and descriptive error responses:

  • "Missing required fields"
  • "Invalid appointment ID"
  • "Invalid time format. Expected HH:MM AM/PM"
  • "Invalid status value"

🛡 Wrapped all DB operations in try/except for safer failure handling

🧰 Updated logs for easier debugging and API traceability

🧹 General cleanup and code quality improvements

✅ Checklist

  • ✅ I have read the contributing guidelines.
  • ✅ Improved validation rules added
  • ✅ Proper error messages returned
  • ✅ Manual testing done across all CRUD endpoints

Summary by CodeRabbit

  • Bug Fixes
    • Enhanced error handling and validation for appointment operations, providing clearer error messages for invalid inputs (dates, times, status values, missing fields)
    • Improved API reliability and consistency across all appointment operations

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 9, 2025

Walkthrough

A single file (Backend/routes/appointments.py) has been refactored to introduce structured error handling via a make_error helper, Cerberus-based data validation, JSON body parsing, comprehensive error responses, and robust database interactions with logging across all CRUD routes.

Changes

Cohort / File(s) Summary
Appointments Route Validation & Error Handling
Backend/routes/appointments.py
Added structured error handling with make_error helper function; introduced Cerberus-based AppointmentValidator class with appointment_schema for date/time/status validation; added parse_json_body and validate_appointment_payload functions for request validation; refactored all CRUD route handlers to perform early input validation, wrap database operations in try/except blocks with logging, and return consistent success/error responses.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • AppointmentValidator class and validation rules: Verify custom date/time format validators and schema rules are correctly defined and cover all edge cases
  • Error handling consistency: Ensure make_error helper and try/except patterns are applied uniformly across all route handlers
  • Database error classification: Check distinction between operational errors (logged and returned) vs. unexpected errors, and verify logging statements
  • Payload mapping logic: Confirm API request fields are correctly mapped to database columns in add/update operations
  • Early validation returns: Validate that payload validation occurs before any database operations for get/update/delete routes

Poem

🐰 We've structured the chaos, made errors speak clear,
Validation stands guard at the gate without fear,
Each route now knows what to accept and what's right,
With logging and handlers that shine oh-so-bright! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title is partially related to the changeset but incomplete and truncated. It mentions 'Added Input Validation and Improved Error Handl…' which covers core changes, but is cut off mid-word, lacks specificity, and uses generic '[Feature]' labeling that doesn't add clarity. Complete the title and remove truncation. Consider: 'Add input validation and error handling to Appointment API' to be more specific and concise.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

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

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

🧹 Nitpick comments (6)
Backend/routes/appointments.py (6)

13-14: Consider deferring logging configuration to the application level.

Calling logging.basicConfig() at module import time can override or conflict with application-level logging configuration. If the Flask app or other modules configure logging differently, this module-level setup may cause unexpected behavior.

Consider removing logging.basicConfig() here and letting the main application configure logging centrally, or guard it with a check.


40-47: Time validation is case-sensitive—consider accepting lowercase am/pm.

The regex (AM|PM) only matches uppercase. Users submitting "10:30 am" will get a validation error. Consider making the pattern case-insensitive:

     def _validate_is_time(self, is_time, field, value):
         """
         The rule 'is_time': True enforces HH:MM AM/PM.
         """
         if is_time:
-            pattern = r"^(0[1-9]|1[0-2]):[0-5][0-9]\s?(AM|PM)$"
-            if not re.match(pattern, value):
+            pattern = r"^(0[1-9]|1[0-2]):[0-5][0-9]\s?(AM|PM)$"
+            if not re.match(pattern, value, re.IGNORECASE):
                 self._error(field, "Invalid time format. Expected HH:MM AM/PM")

119-140: Add specific error handling for invalid date format.

Time errors get a specific message, but date format errors fall through to the generic handler. For consistent UX, add a specific check:

         # Specific time format error
         if "time" in errors:
             # Our custom message is already "Invalid time format. Expected HH:MM AM/PM"
             return False, make_error(
                 "Invalid time format. Expected HH:MM AM/PM",
                 400
             )

+        # Specific date format error
+        if "date" in errors:
+            return False, make_error(
+                "Invalid date format. Expected YYYY-MM-DD",
+                400
+            )
+
         # Specific status value error
         if "status" in errors:

163-164: close_db(db) parameter is ignored—clarify intent.

Looking at close_db in db/db.py, the function pops from Flask's g object and ignores its parameter. Passing db here is misleading. This works because open_db() stores the connection in g.db, but the code reads as if it's closing the passed connection.

Consider either:

  • Call close_db() without arguments (clearer intent)
  • Or call db.close() directly if you want explicit control

This applies to all routes using close_db(db) in their finally blocks.


230-233: Consider returning 201 and the created resource ID.

For REST semantics, POST creating a resource typically returns 201 Created. Returning the new appointment ID helps clients reference the created resource:

+        new_id = db.execute("SELECT last_insert_rowid()").fetchone()[0]
         return jsonify({
             "status": "success",
-            "message": "Appointment added successfully"
-        }), 200
+            "message": "Appointment added successfully",
+            "id": new_id
+        }), 201

107-117: Use Cerberus error codes instead of fragile string matching for required field detection.

Checking for "required field" as a substring in error messages is brittle and couples to Cerberus's internal message format. Use the built-in error code instead:

from cerberus import errors

missing_fields = [
    field for field, errs in v.document_error_tree.items()
    if errors.REQUIRED_FIELD in errs
]

This is the documented way to access validation errors in Cerberus and avoids dependency on message text.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d702628 and 4c43398.

📒 Files selected for processing (1)
  • Backend/routes/appointments.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
Backend/routes/appointments.py (1)
Backend/db/db.py (2)
  • open_db (8-13)
  • close_db (15-18)
🔇 Additional comments (5)
Backend/routes/appointments.py (5)

17-22: LGTM!

Clean helper function providing consistent structured error responses across all routes.


50-83: LGTM!

Well-structured schema with appropriate constraints for required and optional fields.


86-94: LGTM!

Safe JSON parsing with appropriate error handling for malformed or missing request bodies.


167-192: LGTM!

Clean implementation with proper ID validation and 404 handling for non-existent appointments.


305-340: LGTM!

Clean delete implementation with proper existence check before deletion.

Comment on lines +259 to +261
appointment_status = data.get("status", "Pending")
description = data.get("description", "") or ""
description = description.strip()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Default values may unintentionally overwrite existing data.

When status is omitted, it defaults to "Pending", which could reset a "Completed" appointment. Similarly, omitting description defaults to "", erasing existing content.

Since the schema marks these as optional, users may expect omitting them preserves existing values. Consider either:

  1. Require all fields on update (change schema or use a separate update schema)
  2. Preserve existing values when fields are omitted:
-    appointment_status = data.get("status", "Pending")
-    description = data.get("description", "") or ""
+    # Preserve existing values if not provided
+    appointment_status = data.get("status") or dict(existing_appointment)["appointment_status"]
+    description = data.get("description")
+    if description is None:
+        description = dict(existing_appointment)["content"]

(This requires moving these lines after the existence check.)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In Backend/routes/appointments.py around lines 259-261, the current defaulting
of appointment_status and description will overwrite existing values when fields
are omitted; instead, move this logic after you verify the appointment exists
and then preserve existing values by using the existing appointment fields as
defaults (e.g., status = data.get("status", existing.status) and description =
data.get("description", existing.description)), and only call strip() when
description is not None; alternatively implement/validate a separate update
schema that requires fields or explicitly distinguishes omitted vs null values.

@bhavik-mangla
Copy link
Contributor

Include testing evidences

1 similar comment
@bhavik-mangla
Copy link
Contributor

Include testing evidences

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