Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/recruiting/errors.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 2178e7298d0e5d9af83afa12402fd587f0ba6ed8 |
| Branch | feature/aioe-upgrade |
| Path or archive member | internal/modules/recruiting/errors.go |
| Original SHA-256 | 507fa5e9b29b156ecc6235053e07f132b984d1f926da5d7112198e4ad565b99b |
| Git blob SHA | 2675c303f20b71f708f40577ba9045dda35b4a49 |
| Reading-copy SHA-256 | 507fa5e9b29b156ecc6235053e07f132b984d1f926da5d7112198e4ad565b99b |
Recovered source transcript
Download complete source text ↓112 lines · 5.8 KB
package recruiting
import (
"errors"
"fmt"
"strings"
"github.com/jackc/pgx/v5/pgconn"
)
var (
ErrNoDatabase = errors.New("recruiting: no database configured")
ErrNotFound = errors.New("recruiting: not found")
// ErrConflict is an optimistic concurrency failure: the row moved after the caller read it.
ErrConflict = errors.New("recruiting: the record was changed by someone else")
ErrInvalid = errors.New("recruiting: invalid input")
)
// DuplicateError names the field that collided so the caller can point at the right input rather than
// showing a generic failure.
type DuplicateError struct{ Field string }
func (e *DuplicateError) Error() string { return "recruiting: duplicate " + e.Field }
// ValidationError carries a per field message. Returning the field lets the UI mark the offending input
// instead of dropping one sentence at the top of a form.
type ValidationError struct {
Field string
Message string
}
func (e *ValidationError) Error() string { return e.Field + ": " + e.Message }
func (e *ValidationError) Unwrap() error { return ErrInvalid }
// translatePG converts a Postgres integrity failure into a typed domain error.
//
// The database is the only place uniqueness can be checked without a race, so the service inserts and
// interprets the result rather than pre checking with a SELECT and hoping nothing changes in between.
// Doing it the other way round produces a 500 under concurrency, which is exactly the case that matters.
//
// The constraint NAMES are matched deliberately. They are declared in migrations 0047 and 0048 and
// changing one without changing this mapping would silently turn a clean 409 back into a 500, so the
// names are load bearing and the tests pin them.
func translatePG(err error) error {
var pg *pgconn.PgError
if !errors.As(err, &pg) {
return err
}
switch pg.Code {
case "23505": // unique_violation
switch {
case strings.Contains(pg.ConstraintName, "uq_clients_org_name"):
return &DuplicateError{Field: "name"}
case strings.Contains(pg.ConstraintName, "uq_clients_org_uei"):
return &DuplicateError{Field: "uei"}
// The same candidate cannot be on one job order twice. Postgres names this constraint after its
// columns, and without this case the raw name reached the user as "that
// applications_organization_id_requisition_id_candidate_id_key is already in use".
case strings.Contains(pg.ConstraintName, "applications_organization_id_requisition_id_candidate"):
return &DuplicateError{Field: "candidate"}
case strings.Contains(pg.ConstraintName, "uq_client_contacts_primary"):
// Reached only if the demote and promote were not done in one transaction. The service does
// them together, so this is a guard against a future caller taking a shortcut.
return &DuplicateError{Field: "is_primary"}
default:
return &DuplicateError{Field: pg.ConstraintName}
}
case "23514": // check_violation
switch {
case strings.Contains(pg.ConstraintName, "clients_name_check"):
return &ValidationError{Field: "name", Message: "must be between 1 and 200 characters"}
// Rate ranges. Only one end of a range is often being changed, and the other end is whatever is
// already stored, so the database is the only place the pair can be compared. Naming the field
// turns an unactionable failure into one the form can mark.
case strings.Contains(pg.ConstraintName, "requisitions_bill_range_chk"):
return &ValidationError{Field: "bill_rate_min", Message: "cannot be greater than the maximum bill rate"}
case strings.Contains(pg.ConstraintName, "requisitions_pay_range_chk"):
return &ValidationError{Field: "pay_rate_min", Message: "cannot be greater than the maximum pay rate"}
case strings.Contains(pg.ConstraintName, "requisitions_salary_range_chk"):
return &ValidationError{Field: "salary_min", Message: "cannot be greater than the maximum salary"}
case strings.Contains(pg.ConstraintName, "requisitions_rates_nonneg_chk"):
return &ValidationError{Field: "bill_rate_min", Message: "rates cannot be negative"}
case strings.Contains(pg.ConstraintName, "requisitions_target_after_open_chk"):
return &ValidationError{Field: "target_fill_date", Message: "cannot be before the date the order was opened"}
case strings.Contains(pg.ConstraintName, "requisitions_title_check"):
return &ValidationError{Field: "title", Message: "must be between 1 and 300 characters"}
case strings.Contains(pg.ConstraintName, "requisitions_openings_check"):
return &ValidationError{Field: "openings", Message: "must be at least 1"}
// The two clock invariants from 0047 and 0052. Reaching either means the transition logic moved a
// status without moving the clock that status implies, which is a defect in this service rather
// than bad input, so it is reported as an internal failure and not blamed on the caller.
case strings.Contains(pg.ConstraintName, "requisitions_closed_at_chk"),
strings.Contains(pg.ConstraintName, "requisitions_held_since_chk"):
return fmt.Errorf("recruiting: requisition clock invariant %s violated: %w", pg.ConstraintName, err)
case strings.Contains(pg.ConstraintName, "status"):
return &ValidationError{Field: "status", Message: "is not a permitted value"}
default:
return &ValidationError{Field: pg.ConstraintName, Message: "failed a database constraint"}
}
case "23503": // foreign_key_violation
// The composite key added in 0052 carries client_id, so this fires when a contact exists but works
// at a different client. "Not found" would be misleading, because the contact is right there.
if strings.Contains(pg.ConstraintName, "requisitions_hiring_manager_fkey") {
return &ValidationError{Field: "hiring_manager_id", Message: "is not a contact at this client"}
}
return ErrNotFound
case "23502": // not_null_violation
return &ValidationError{Field: pg.ColumnName, Message: "is required"}
}
return fmt.Errorf("recruiting: database error: %w", err)
}