feat: user service

This commit is contained in:
Ilia Denisov
2026-04-10 19:05:02 +02:00
committed by GitHub
parent 710bad712e
commit 23ffcb7535
140 changed files with 33418 additions and 952 deletions
+175
View File
@@ -0,0 +1,175 @@
// Package shared provides shared request parsing and error normalization used
// by the user-service application and transport layers.
package shared
import (
"errors"
"net/http"
"strings"
)
const (
// ErrorCodeInvalidRequest reports malformed or semantically invalid caller
// input.
ErrorCodeInvalidRequest = "invalid_request"
// ErrorCodeConflict reports that the requested mutation conflicts with the
// current source-of-truth state.
ErrorCodeConflict = "conflict"
// ErrorCodeSubjectNotFound reports that the requested user subject does not
// exist.
ErrorCodeSubjectNotFound = "subject_not_found"
// ErrorCodeServiceUnavailable reports that a required dependency is
// temporarily unavailable.
ErrorCodeServiceUnavailable = "service_unavailable"
// ErrorCodeInternalError reports that a local invariant failed unexpectedly.
ErrorCodeInternalError = "internal_error"
)
var internalErrorStatusCodes = map[string]int{
ErrorCodeInvalidRequest: http.StatusBadRequest,
ErrorCodeConflict: http.StatusConflict,
ErrorCodeSubjectNotFound: http.StatusNotFound,
ErrorCodeServiceUnavailable: http.StatusServiceUnavailable,
ErrorCodeInternalError: http.StatusInternalServerError,
}
var internalStableMessages = map[string]string{
ErrorCodeConflict: "request conflicts with current state",
ErrorCodeSubjectNotFound: "subject not found",
ErrorCodeServiceUnavailable: "service is unavailable",
ErrorCodeInternalError: "internal server error",
}
// InternalErrorProjection stores the transport-ready representation of one
// normalized trusted-internal error.
type InternalErrorProjection struct {
// StatusCode stores the HTTP status returned to the trusted caller.
StatusCode int
// Code stores the stable machine-readable error code written into the JSON
// envelope.
Code string
// Message stores the stable or caller-safe message written into the JSON
// envelope.
Message string
}
// ServiceError stores one normalized application-layer failure.
type ServiceError struct {
// Code stores the stable machine-readable error code.
Code string
// Message stores the caller-safe error message.
Message string
// Err stores the wrapped underlying cause when one exists.
Err error
}
// Error returns the caller-safe message of ServiceError.
func (err *ServiceError) Error() string {
if err == nil {
return ""
}
if strings.TrimSpace(err.Message) != "" {
return err.Message
}
if strings.TrimSpace(err.Code) != "" {
return err.Code
}
if err.Err != nil {
return err.Err.Error()
}
return ErrorCodeInternalError
}
// Unwrap returns the wrapped underlying cause.
func (err *ServiceError) Unwrap() error {
if err == nil {
return nil
}
return err.Err
}
// NewServiceError returns one new normalized application-layer error.
func NewServiceError(code string, message string, err error) *ServiceError {
return &ServiceError{
Code: strings.TrimSpace(code),
Message: strings.TrimSpace(message),
Err: err,
}
}
// InvalidRequest returns one normalized invalid-request error.
func InvalidRequest(message string) *ServiceError {
return NewServiceError(ErrorCodeInvalidRequest, strings.TrimSpace(message), nil)
}
// Conflict returns one normalized conflict error.
func Conflict() *ServiceError {
return NewServiceError(ErrorCodeConflict, "", nil)
}
// SubjectNotFound returns one normalized subject-not-found error.
func SubjectNotFound() *ServiceError {
return NewServiceError(ErrorCodeSubjectNotFound, "", nil)
}
// ServiceUnavailable returns one normalized dependency-unavailable error.
func ServiceUnavailable(err error) *ServiceError {
return NewServiceError(ErrorCodeServiceUnavailable, "", err)
}
// InternalError returns one normalized invariant-failure error.
func InternalError(err error) *ServiceError {
return NewServiceError(ErrorCodeInternalError, "", err)
}
// CodeOf returns the normalized service error code carried by err when one is
// available.
func CodeOf(err error) string {
serviceErr, ok := errors.AsType[*ServiceError](err)
if !ok || serviceErr == nil {
return ""
}
return serviceErr.Code
}
// ProjectInternalError normalizes err to the frozen trusted-internal HTTP
// error surface.
func ProjectInternalError(err error) InternalErrorProjection {
serviceErr, ok := errors.AsType[*ServiceError](err)
code := CodeOf(err)
if _, exists := internalErrorStatusCodes[code]; !exists {
return InternalErrorProjection{
StatusCode: http.StatusInternalServerError,
Code: ErrorCodeInternalError,
Message: internalStableMessages[ErrorCodeInternalError],
}
}
message := ""
if ok && serviceErr != nil {
message = serviceErr.Message
}
if stable, exists := internalStableMessages[code]; exists {
message = stable
}
if strings.TrimSpace(message) == "" {
message = internalStableMessages[ErrorCodeInternalError]
}
return InternalErrorProjection{
StatusCode: internalErrorStatusCodes[code],
Code: code,
Message: message,
}
}