Skip to content

fix: propagate errors on non-OK Core responses and store resolved payload in metadata#56

Open
mathildeshagl wants to merge 6 commits into
mainfrom
fix/error-propagation-and-resolution-payload
Open

fix: propagate errors on non-OK Core responses and store resolved payload in metadata#56
mathildeshagl wants to merge 6 commits into
mainfrom
fix/error-propagation-and-resolution-payload

Conversation

@mathildeshagl

@mathildeshagl mathildeshagl commented Mar 7, 2026

Copy link
Copy Markdown
Member

Summary

Three bugs are fixed in this PR:

1. coursePhaseRoleMapping.go — propagate non-OK Core responses as errors

Previously, a non-OK HTTP response from Core was logged but returned with a nil error, causing callers to silently treat outages or misconfigurations as an empty role set. The non-OK branch now returns a descriptive fmt.Errorf so callers can detect and handle upstream failures.

2. isStudent.go — propagate non-OK Core responses as errors

Same issue: any non-OK response other than 401 Unauthorized was swallowed. The 401 path ("not a student") is intentional and unchanged. All other non-OK status codes now return a non-nil error, preventing Core outages from being misinterpreted as a successful empty lookup.

3. resolution.go — store resolved payload instead of raw bytes in metadata

FetchAndMergeCourseParticipationWithResolution was storing the raw response bytes (data) into participation.PrevData[res.DtoName] instead of the processed result (resolvedData) returned by ResolveParticipation. Fixed to store resolvedData.

4. README.md — document module import path migration

Added an upgrade note calling out the breaking import-path change from github.com/ls1intum/prompt-sdk to github.com/prompt-edu/prompt-sdk, with instructions to run go get github.com/prompt-edu/prompt-sdk@latest and update all import lines.

Summary by CodeRabbit

  • Bug Fixes

    • Better HTTP error propagation with status codes and clearer error messages for request failures.
    • Corrected course participation handling so resolved/validated data is stored.
  • Refactor

    • Updated request APIs to surface HTTP status information and improved error reporting across verification flows.
  • Chores

    • Minor logging/message wording and comment cleanup.

Copilot AI review requested due to automatic review settings March 7, 2026 10:48
@coderabbitai

coderabbitai Bot commented Mar 7, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@mathildeshagl has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 44 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e6db878d-8c7b-44f2-9f31-7fb07f90f118

📥 Commits

Reviewing files that changed from the base of the PR and between 1c15f79 and fe279c8.

📒 Files selected for processing (1)
  • testutils/mockAuthMiddleware.go
📝 Walkthrough

Walkthrough

Propagate HTTP status codes and formatted errors through Keycloak request handlers (one signature extended). Fix resolution merging to store resolvedData into participation.PrevData instead of raw fetched data.

Changes

Cohort / File(s) Summary
Keycloak core requests
keycloakTokenVerifier/keycloakCoreRequests/coursePhaseRoleMapping.go, keycloakTokenVerifier/keycloakCoreRequests/isStudent.go
Propagate HTTP status codes on errors; return formatted errors for non-OK responses. SendCoursePhaseRoleMappingRequest now returns a status code alongside result and error.
Keycloak role handling
keycloakTokenVerifier/getLecturerAndEditorRole.go
Updated caller to accept and act on the propagated status code from SendCoursePhaseRoleMappingRequest instead of assuming internal server error on failures.
Resolution merge fix
resolution.go
When a per-participation resolution yields data, store the resolved value into participation.PrevData[DtoName] (was storing raw fetched outer data).
Manifest / tests
go.mod, testutils/mockAuthMiddleware.go
Minor manifest edits and removal of two non-functional comment lines in test middleware.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • niclasheun
  • rappm

Poem

🐰 I hopped through code with eager paws,

Statuses now speak without a pause,
Resolved data tucked where it should be,
Errors tell truths for all to see,
A tiny rabbit's tidy spree. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: propagate errors on non-OK Core responses and store resolved payload in metadata' accurately captures the main changes across multiple files: error propagation in coursePhaseRoleMapping.go and isStudent.go, plus storing resolved data in resolution.go.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/error-propagation-and-resolution-payload

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.

@mathildeshagl mathildeshagl changed the title Improve error handling in course phase role mapping and is student requests fix: propagate errors on non-OK Core responses and store resolved payload in metadata Mar 7, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR improves correctness and reliability when enriching course participation data with resolved DTOs and when calling core auth endpoints for role mapping / student checks.

Changes:

  • Fixes merging logic to store resolvedData (instead of the raw HTTP response bytes) into PrevData for a single participation resolution.
  • Returns proper errors on non-200 responses from the core for is_student and course_phase roles requests.
  • Improves diagnostics by including HTTP status information in returned errors from core request helpers.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
resolution.go Fixes resolved DTO merge to persist resolvedData into PrevData for single participation resolution.
keycloakTokenVerifier/keycloakCoreRequests/isStudent.go Returns an error for non-OK responses (previously returned nil error), improving caller handling.
keycloakTokenVerifier/keycloakCoreRequests/coursePhaseRoleMapping.go Returns an error for non-OK responses (previously returned nil error), improving caller handling.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

if resp.StatusCode != http.StatusOK {
log.Error("Received non-OK response:", resp.Status)
return keycloakTokenVerifierDTO.GetCourseRoles{}, nil
return keycloakTokenVerifierDTO.GetCourseRoles{}, fmt.Errorf("course phase role mapping request failed with status %d: %s", resp.StatusCode, resp.Status)

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

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

The error message includes both resp.StatusCode and resp.Status, but resp.Status already includes the numeric code (e.g., "404 Not Found"), leading to duplication in logs/returned errors. Consider using just resp.Status or only the code plus a more specific context (method + path) for cleaner diagnostics.

Suggested change
return keycloakTokenVerifierDTO.GetCourseRoles{}, fmt.Errorf("course phase role mapping request failed with status %d: %s", resp.StatusCode, resp.Status)
return keycloakTokenVerifierDTO.GetCourseRoles{}, fmt.Errorf("course phase role mapping request GET %s failed: %s", path, resp.Status)

Copilot uses AI. Check for mistakes.
Comment thread resolution.go
Comment on lines 181 to 185
if participation.PrevData == nil {
participation.PrevData = make(promptTypes.MetaData)
}
participation.PrevData[res.DtoName] = data
participation.PrevData[res.DtoName] = resolvedData
cppWithRes.Participation = participation

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

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

Consider adding a unit/integration test for FetchAndMergeCourseParticipationWithResolution to ensure the resolved payload (from ResolveParticipation) is what gets written into Participation.PrevData. This line previously wrote the raw participation response bytes, so a regression here would be hard to spot without a test (can be done with an httptest server backing FetchJSON).

Copilot uses AI. Check for mistakes.
Comment on lines 31 to +36
return keycloakTokenVerifierDTO.GetCoursePhaseParticipation{IsStudentOfCoursePhase: false}, errors.New("not student of course")
}

if resp.StatusCode != http.StatusOK {
log.Error("Received non-OK response:", resp.Status)
return keycloakTokenVerifierDTO.GetCoursePhaseParticipation{}, nil
return keycloakTokenVerifierDTO.GetCoursePhaseParticipation{}, fmt.Errorf("is student request failed with status %d: %s", resp.StatusCode, resp.Status)

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

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

The "not student of course" path relies on a stringly-typed error; callers currently compare err.Error() to decide behavior. Since this function is already being updated to return richer errors, it would be more robust to return a sentinel error (e.g., ErrNotStudent) and have callers use errors.Is instead of string comparison.

Copilot uses AI. Check for mistakes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
keycloakTokenVerifier/keycloakCoreRequests/isStudent.go (1)

29-36: ⚠️ Potential issue | 🟠 Major

Use sentinel and typed errors to make the error contract inspectable.

The caller in getIsStudent.go:33 currently relies on err.Error() == "not student of course" and treats all other errors as 500. Returning ad-hoc errors.New(...) / fmt.Errorf(...) keeps that brittle contract in place and loses the upstream HTTP status from non-401 failures. Define a sentinel error for the unauthorized case and a typed status error for other non-200 responses so callers can use errors.Is / errors.As instead of string matching. The codebase already follows this pattern elsewhere (e.g., ErrUserNotInContext).

Possible direction
+var ErrNotStudentOfCourse = errors.New("not student of course")
+
 if resp.StatusCode == http.StatusUnauthorized {
 	log.Info("Not student of course")
-	return keycloakTokenVerifierDTO.GetCoursePhaseParticipation{IsStudentOfCoursePhase: false}, errors.New("not student of course")
+	return keycloakTokenVerifierDTO.GetCoursePhaseParticipation{IsStudentOfCoursePhase: false}, ErrNotStudentOfCourse
 }
 
 if resp.StatusCode != http.StatusOK {
 	log.Error("Received non-OK response:", resp.Status)
-	return keycloakTokenVerifierDTO.GetCoursePhaseParticipation{}, fmt.Errorf("is student request failed with status %d: %s", resp.StatusCode, resp.Status)
+	return keycloakTokenVerifierDTO.GetCoursePhaseParticipation{}, CoreStatusError{StatusCode: resp.StatusCode, Status: resp.Status}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@keycloakTokenVerifier/keycloakCoreRequests/isStudent.go` around lines 29 -
36, Replace the ad-hoc errors in the Keycloak student-check path with an
inspectable contract: introduce a package-level sentinel error (e.g.,
ErrNotStudent) and a typed error (e.g., HTTPStatusError with fields StatusCode
and Status string implementing error) and return ErrNotStudent from the
unauthorized branch in isStudent.go instead of errors.New("not student of
course"), and return an instance of HTTPStatusError (populated with
resp.StatusCode and resp.Status) for any non-200 responses instead of
fmt.Errorf; then update the caller in getIsStudent.go to use errors.Is(err,
ErrNotStudent) and errors.As(err, &HTTPStatusError{}) rather than string
comparisons so the upstream status and identity are preserved.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@keycloakTokenVerifier/keycloakCoreRequests/coursePhaseRoleMapping.go`:
- Around line 28-30: Replace the plain fmt.Errorf return in
SendCoursePhaseRoleMappingRequest with a typed error that carries the HTTP
status so callers can inspect it; define a CoreStatusError struct (fields
StatusCode int, Status string) that implements error (Error() string) and return
&CoreStatusError{StatusCode: resp.StatusCode, Status: resp.Status} instead of
fmt.Errorf in the non-OK branch, and update callers like
GetLecturerAndEditorRole (which currently calls
SendCoursePhaseRoleMappingRequest) to do a type assertion on CoreStatusError to
map/forward the actual HTTP status code.

---

Outside diff comments:
In `@keycloakTokenVerifier/keycloakCoreRequests/isStudent.go`:
- Around line 29-36: Replace the ad-hoc errors in the Keycloak student-check
path with an inspectable contract: introduce a package-level sentinel error
(e.g., ErrNotStudent) and a typed error (e.g., HTTPStatusError with fields
StatusCode and Status string implementing error) and return ErrNotStudent from
the unauthorized branch in isStudent.go instead of errors.New("not student of
course"), and return an instance of HTTPStatusError (populated with
resp.StatusCode and resp.Status) for any non-200 responses instead of
fmt.Errorf; then update the caller in getIsStudent.go to use errors.Is(err,
ErrNotStudent) and errors.As(err, &HTTPStatusError{}) rather than string
comparisons so the upstream status and identity are preserved.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 478574be-3bba-4c65-8ff7-831e2ba71571

📥 Commits

Reviewing files that changed from the base of the PR and between 0802c56 and 605347a.

📒 Files selected for processing (3)
  • keycloakTokenVerifier/keycloakCoreRequests/coursePhaseRoleMapping.go
  • keycloakTokenVerifier/keycloakCoreRequests/isStudent.go
  • resolution.go

Comment on lines +28 to +30
if resp.StatusCode != http.StatusOK {
log.Error("Received non-OK response:", resp.Status)
return keycloakTokenVerifierDTO.GetCourseRoles{}, nil
return keycloakTokenVerifierDTO.GetCourseRoles{}, fmt.Errorf("course phase role mapping request failed with status %d: %s", resp.StatusCode, resp.Status)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "coursePhaseRoleMapping.go" | head -20

Repository: prompt-edu/prompt-sdk

Length of output: 135


🏁 Script executed:

find . -type f -name "getLecturerAndEditorRole.go" | head -20

Repository: prompt-edu/prompt-sdk

Length of output: 116


🏁 Script executed:

cat -n ./keycloakTokenVerifier/keycloakCoreRequests/coursePhaseRoleMapping.go | sed -n '20,40p'

Repository: prompt-edu/prompt-sdk

Length of output: 929


🏁 Script executed:

cat -n ./keycloakTokenVerifier/getLecturerAndEditorRole.go | sed -n '25,45p'

Repository: prompt-edu/prompt-sdk

Length of output: 955


🏁 Script executed:

rg "SendCoursePhaseRoleMappingRequest" --type go -B 2 -A 5

Repository: prompt-edu/prompt-sdk

Length of output: 1790


🏁 Script executed:

find ./keycloakTokenVerifier -type f -name "*.go" | head -20

Repository: prompt-edu/prompt-sdk

Length of output: 813


🏁 Script executed:

rg "type.*Error.*struct" ./keycloakTokenVerifier --type go

Repository: prompt-edu/prompt-sdk

Length of output: 47


🏁 Script executed:

cat -n ./keycloakTokenVerifier/keycloakCoreRequests/sendCoreRequest.go

Repository: prompt-edu/prompt-sdk

Length of output: 1026


🏁 Script executed:

cat -n ./keycloakTokenVerifier/keycloakCoreRequests/isStudent.go | sed -n '1,50p'

Repository: prompt-edu/prompt-sdk

Length of output: 1943


🏁 Script executed:

rg "SendIsStudentRequest" --type go -B 2 -A 5

Repository: prompt-edu/prompt-sdk

Length of output: 1641


🏁 Script executed:

cat -n ./keycloakTokenVerifier/getIsStudent.go | sed -n '25,50p'

Repository: prompt-edu/prompt-sdk

Length of output: 1333


Return a status-carrying error, not just a formatted message.

keycloakTokenVerifier/getLecturerAndEditorRole.go:34 unconditionally returns http.StatusInternalServerError for any error from SendCoursePhaseRoleMappingRequest. Since this function returns a plain fmt.Errorf with the status code only in the message string, upstream callers cannot distinguish 401/404/503 without string parsing and cannot forward the actual HTTP status. Use a typed error that carries resp.StatusCode and let handlers map that to the appropriate response status.

Consider defining a CoreStatusError struct with StatusCode and Status fields, implementing the error interface, and returning that instead of fmt.Errorf.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@keycloakTokenVerifier/keycloakCoreRequests/coursePhaseRoleMapping.go` around
lines 28 - 30, Replace the plain fmt.Errorf return in
SendCoursePhaseRoleMappingRequest with a typed error that carries the HTTP
status so callers can inspect it; define a CoreStatusError struct (fields
StatusCode int, Status string) that implements error (Error() string) and return
&CoreStatusError{StatusCode: resp.StatusCode, Status: resp.Status} instead of
fmt.Errorf in the non-OK branch, and update callers like
GetLecturerAndEditorRole (which currently calls
SendCoursePhaseRoleMappingRequest) to do a type assertion on CoreStatusError to
map/forward the actual HTTP status code.

Copilot AI review requested due to automatic review settings March 7, 2026 15:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +15 to +20
func SendCoursePhaseRoleMappingRequest(coreURL url.URL, authHeader string, coursePhaseID uuid.UUID) (keycloakTokenVerifierDTO.GetCourseRoles, int, error) {
path := path.Join("/api/auth/course_phase", coursePhaseID.String(), "roles")

resp, err := sendRequest(coreURL, "GET", path, authHeader, nil)
if err != nil {
return keycloakTokenVerifierDTO.GetCourseRoles{}, err
return keycloakTokenVerifierDTO.GetCourseRoles{}, http.StatusInternalServerError, err

Copilot AI Mar 7, 2026

Copy link

Choose a reason for hiding this comment

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

SendCoursePhaseRoleMappingRequest now has a different exported signature (adds an int status code return). This is a breaking API change for any external consumers of keycloakCoreRequests. Consider keeping the original signature and encoding the status code into the returned error (e.g., a typed error), or introduce a new function instead; if the signature change is intentional, it should be explicitly called out in the upgrade documentation/changelog.

Copilot uses AI. Check for mistakes.
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