resource

package module
v1.0.10 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 3, 2025 License: MIT Imports: 18 Imported by: 0

README

resource

Go Reference Go Report Card

resource is a Go package that simplifies the creation of REST APIs for your database models. It automatically generates RESTful endpoints with CRUD operations for your GORM models, with support for multiple router frameworks, access control, and frontend integration. It also automatically generates your docs via OpenAPI

Features

  • Automatic CRUD endpoints for your GORM models
  • Multiple router integrations:
  • Flexible access control:
    • Role-Based Access Control (RBAC)
    • Access Control Lists (ACL)
  • Field-level control for showing/hiding fields based on user roles
  • Request validation with customizable validation rules
  • Custom query operations for filtering, sorting, and pagination
  • Lifecycle hooks for customizing behavior at different stages
  • Frontend integration with JavaScript/React hooks
  • OpenAPI integration API documentation is auto generated from your model definitions

Installation

go get github.com/restk/resource

Quick Start

If you don't need Role-Based Access Control, you can omit the RBAC struct.

package main

import (
	"context"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/restk/openapi"
	"github.com/restk/resource"
	"github.com/restk/resource/access"
	ginRouter "github.com/restk/resource/router/gin"
	"gorm.io/driver/postgres"
	"gorm.io/gorm"
)

// RBAC should implement the access.RBAC interface.
type RBAC struct{}

func (r *RBAC) HasPermission(ctx context.Context, resource string, permission access.Permission) bool {
	panic("not implemented")
}

func (r *RBAC) HasRole(ctx context.Context, role string) bool {
	panic("not implemented")
}

type User struct {
	gorm.Model
	Name     string `json:"name"`
	Email    string `json:"email"`
	Password string `json:"password,omitempty"`
}

func main() {
	// Setup your database connection.
	db, err := gorm.Open(postgres.Open("host=127.0.0.1 user=db password=db dbname=db port=5433 sslmode=disable"))
	if err != nil {
		log.Fatalf("Failed to connect to database: %v", err)
	}

	// Create a Gin router.
	mux := gin.Default()
	group := mux.Group("/")
	router := ginRouter.NewRouter(group)

	// Initialize OpenAPI for docs.
	oapi := openapi.New("Your Project", "1.0.0")

	// Create a resource definition for the User model.
	users := resource.NewResource[User]("user", "id")

	// Specify RBAC to control access to the resource.
	rbac := &RBAC{}
	users.EnableRBAC(rbac)

	// Generate REST API handlers and documentation for this resource.
	if err = users.GenerateRestAPI(router, db, oapi); err != nil {
		log.Fatalf("Failed to generate rest API for user resource: %v", err)
	}

	// Start the server.
	http.ListenAndServe(":8080", mux)
}

This example sets up a REST API for a User model with the following endpoints:

  • GET /users - List all users
  • GET /users/:id - Get a specific user
  • POST /users - Create a new user
  • PUT /users/:id - Update a user
  • PATCH /users/:id - Patch a user
  • DELETE /users/:id - Delete a user

Core Concepts

Resources

A resource represents a database model with RESTful operations. Create a resource using resource.New[T]():

users := resource.NewResource[User]("user", "id")

This will use the users table, with id as the primary key (utilizing the struct's JSON tags).

Routing

The package supports multiple router frameworks through adapters.

Gin Router
package main

import (
  "github.com/gin-gonic/gin"
  ginRouter "github.com/restk/resource/router/gin"
)

func main() {
  mux := gin.Default()
  group := mux.Group("/")
  ginRouter.NewRouter(group)
}
Chi Router
package main

import (
  "github.com/go-chi/chi/v5"
  chiRouter "github.com/restk/resource/router/chi"
)

func main() {
  mux := chi.NewRouter()
  chiRouter.NewRouter(mux, "")
}
Lifecycle Hooks

Customize behavior at different lifecycle stages:

package main

import (
  "github.com/restk/resource"
  "github.com/restk/resource/access"
  resourcerouter "github.com/restk/resource/router"
  "golang.org/x/crypto/bcrypt"
  "gorm.io/gorm"
)

type User struct {
  gorm.Model
  Name     string `json:"name"`
  Email    string `json:"email"`
  Password string `json:"password,omitempty"`
}

func main() {
  // Create a resource definition for the User model.
  users := resource.NewResource[User]("user", "id")

  users.BeforeSave(access.PermissionCreate, func(ctx resourcerouter.Context, user *User) error {
    // Hash password before creating user.
    hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
    if err != nil {
      return err
    }

    user.Password = string(hashedPassword)

    return nil
  })
}
Relationships

Define relationships between resources:

package main

import (
  "github.com/globalcyberalliance/aide/pkg/model"
  "github.com/restk/resource"
  "gorm.io/gorm"
)

type User struct {
  gorm.Model
  Name     string `json:"name"`
  Email    string `json:"email"`
  Password string `json:"password,omitempty"`
}

type UserAPIKey struct {
  gorm.Model
  Key    string `json:"key"`
  UserID uint   `json:"userID"`
}

func main() {
  // Create a resource definition for the User model.
  users := resource.NewResource[User]("user", "id")

  userAPIKeys := resource.NewResource[model.UserAPIKey]("key", "id")
  userAPIKeys.BelongsTo(users, "UserID")
}

When generating the REST endpoints for userAPIKeys, they'll be routed under /users/:userID/keys/:id.

Pagination

Clients can request pages using ?page=2 or ?limit=20&offset=40.

Query params

The list endpoint such as GET /users has query params for filtering, you can use the JSON tag name or the field name such as GET /users?name=tom&age=21.

Operation suffix

For any query param, you can add an operation suffix to change the operator, for example, you could do GET /users?nameLike=%tom%&ageGte=21 you can also do ranges such as /users?createdAtGte=?&createdAtLte=?

Suffix SQL Operator Description
Gt > Greater than
Gte >= Greater than or equal to
Lt < Less than
Lte <= Less than or equal to
Ne != Not equal to
Like LIKE Pattern match

Frontend Integration

The package includes a React hook for easy frontend integration:

// From the javascript/useResource.js file
import {useResource} from '@restk/resource';

function UserList() {
    let {
        list,
        get,
        create,
        update,
        patch,
        remove,
    } = useResource("users", { pageSize: 10 });

    let users = list.data || [];

    useEffect(() => {
      list.fetch({
          ageGte: 21,
          nameLike: "%a%"          
      })
    }, [list.page]);

    if (list.loading) {
        return (<div>Loading...</div>)
    }
    if (list.error) {
        return (<div>Error: {list.error.message}</div>)
    }

    return (
        <ul>
            {users.map(user => (
                <li key={user.id}>{user.name}</li>
            ))}
        </ul>
    );
}

License

MIT

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	BadRequest = func(ctx router.Context) {
		ctx.WriteJSON(http.StatusBadRequest, S{"code": 400, "message": "Invalid request"})
	}
	CustomUserError = func(ctx router.Context, userError *UserError) {
		ctx.WriteJSON(http.StatusInternalServerError, S{"code": userError.Code, "message": userError.Message})
	}
	ForbiddenAccess = func(ctx router.Context) {
		ctx.WriteJSON(http.StatusForbidden, S{"code": 403, "message": "Forbidden access to resource"})
	}
	InternalServerError = func(ctx router.Context, err error) {

		fmt.Println("internal server error", err)
		ctx.WriteJSON(http.StatusInternalServerError, S{"code": 500, "message": "Internal Server Error"})
	}
	InvalidInput = func(ctx router.Context, msg string) {
		ctx.WriteJSON(http.StatusBadRequest, S{"code": 400, "message": msg})
	}
	NoResults = func(ctx router.Context) {
		ctx.Writer().WriteHeader(http.StatusNoContent)
	}
	ResourceNotFound = func(ctx router.Context) {
		ctx.WriteJSON(http.StatusNotFound, S{"code": 404, "message": "Resource not found"})
	}
)

Functions

func DefaultHasOwnership

func DefaultHasOwnership[T any](c router.Context, resource string, obj *T) bool

DefaultHasOwnership returns true by default and does not handle ownership. Call SetHasOwnership() to add ownership.

func NewUserError

func NewUserError(code int, message string) error

NewUserError is a custom error type that means this message will be sent to the user instead of an InternalServerError (default behaviour.) You should return a UserError in hooks such as BeforeSave / AfterSave / etc when you want the user to receive the error.

Types

type Field

type Field struct {
	Name        string
	StructField reflect.StructField
}

type FieldIgnoreRule

type FieldIgnoreRule struct {
	UnlessRoles []string
}

type FieldQueryOperation

type FieldQueryOperation string
var (
	SchemaRegistry = openapi.NewMapRegistry("#/components/schemas/", openapi.DefaultSchemaNamer)

	FieldQueryOperationEquals            FieldQueryOperation = "="
	FieldQueryOperationLike              FieldQueryOperation = "LIKE"
	FieldQueryOperationGreaterThan       FieldQueryOperation = ">"
	FieldQueryOperationGreaterThanEquals FieldQueryOperation = ">="
	FieldQueryOperationLessThan          FieldQueryOperation = "<"
	FieldQueryOperationLessThanEquals    FieldQueryOperation = "<="
	FieldQueryOperationNotEqual          FieldQueryOperation = "!="

	ErrRecordNotFound = errors.New("record not found")
)

type PathOption added in v1.0.7

type PathOption struct {
	Path          string
	Disable       bool
	DisableDocs   bool
	DeprecateDocs bool
}

PathOption holds options for a specific path

type Resource

type Resource[T any] struct {
	// contains filtered or unexported fields
}

Resource represents a single REST resource, such as /users. It seeks to auto generate all REST endpoints, API documentation, database fetching, database updating, validation (of input), access control (RBAC/ACL) and ownership from a single struct T.

We currently support gorm as a data layer and gin as a REST endpoint. It is easy to extend and change the data layer and REST endpoint by implementing the corresponding interfaces. (see pkg/gorm and pkg/gin for an implementation) TODO: make this a fact.

The purpose of Resource is the ability to handle the below problems:

Data Access

Query:

1) Query a resource by number, string, fuzzy matching, and date ranges. We support a basic query interface using query params such as /users/?id=3&name="%thoma%"&start_time=""&end_time=""

Fetching:

1) Fetch a resource (by number, string, fuzzy matching (strings) and date ranges) 2) Fetch an array of resources (by number, string, and date ranges and paginate the results)

Creating and Updating: 1) Create a resource 2) Update a resource or update a list of resources

Deleting: 1) Delete a resource 2) Delete a list of resources

Access Control: 1) Support RBAC # Error Codes

400 Bad Request - This means that client-side input fails validation. 401 Unauthorized - This means the user isn't not authorized to access a resource. It usually returns when the user isn't authenticated. 403 Forbidden - This means the user is authenticated, but it's not allowed to access a resource. 404 Not Found - This indicates that a resource is not found. 500 Internal server error - This is a generic server error. It probably shouldn't be thrown explicitly. 502 Bad Gateway - This indicates an invalid response from an upstream server. 503 Service Unavailable - This indicates that something unexpected happened on server side (It can be anything like server overload, some parts of the system failed, etc.).

func NewResource

func NewResource[T any](name string, primaryField string) *Resource[T]

NewResource creates a new resource. Name is expected to be singular and we attempt to make it plural for doc purposes. To override the plural name, call .Plural("").

func (*Resource[T]) AddTag

func (r *Resource[T]) AddTag(tag string)

AddTag adds a tag.

func (*Resource[T]) AfterDelete

func (r *Resource[T]) AfterDelete(f func(ctx context.Context, obj *T) error)

AfterDelete is called after a resource is deleted successfully.

func (*Resource[T]) AfterSave

func (r *Resource[T]) AfterSave(permission access.Permission, f func(ctx context.Context, obj *T) error)

AfterSave is called after the resource is saved to the database successfully. You can add multiple functions.

func (*Resource[T]) AllowField

func (r *Resource[T]) AllowField(field string, permissions []access.Permission) *Resource[T]

AllowField will allow a field for a specific permission. By default Fields are allowed, call IgnoreAllFields() first.

func (*Resource[T]) AllowFields

func (r *Resource[T]) AllowFields(fields []string, permissions []access.Permission) *Resource[T]

AllowFields will allow the given fields for a specific permission. By default all fields are allowed, call IgnoreAllFields() first.

func (*Resource[T]) BeforeDelete

func (r *Resource[T]) BeforeDelete(f func(ctx context.Context, obj *T) error)

BeforeDelete is called right before a resource is deleted.

func (*Resource[T]) BeforeListResponse added in v1.0.6

func (r *Resource[T]) BeforeListResponse(responseType any, f func(ctx context.Context, obj []*T) (any, error))

BeforeListResponse is called right before we respond to the client and allows you to return a custom response instead of the default response. You must specify the responseType as the first argument which is is used to auto generate docs

func (*Resource[T]) BeforeRequest added in v1.0.10

func (r *Resource[T]) BeforeRequest(requestType any, permission access.Permission, f func(ctx context.Context, requestType any) (*T, error))

BeforeRequest allows you to take in a custom request type for Creates and Updates. The request type must be transformed back to type T

func (*Resource[T]) BeforeResponse

func (r *Resource[T]) BeforeResponse(responseType any, permission access.Permission, f func(ctx context.Context, obj *T) (any, error))

BeforeResponse is called right before we respond to the client and allows you to return a custom response instead of the default response. You must specify the responseType as the first argument which is is used to auto generate docs.

func (*Resource[T]) BeforeSave

func (r *Resource[T]) BeforeSave(permission access.Permission, f func(ctx context.Context, obj *T) error)

BeforeSave adds a function that will be called before a save of a Resource. You can add multiple functions.

func (*Resource[T]) BelongsTo

func (r *Resource[T]) BelongsTo(resource ResourceInterface, field string)

BelongsTo defines that this Resource belongs to T. This will make it so the primary field of the resource we belong to is used in all fetch queries.

Example:

users := Resource[model.User]("users", "id") posts := Resource[model.UserPost]("posts", "id")

posts.BelongsTo(users, "UserID")

/users/:userID/posts /users/:userID/posts/:postID

All fetches to posts will now include the primary field (userID) in the query.

func (*Resource[T]) Create added in v1.0.3

func (r *Resource[T]) Create(ctx context.Context, resource *T) error

Create creates a resource.

func (*Resource[T]) DELETE

func (r *Resource[T]) DELETE(f func(c router.Context))

DELETE overrides the DELETE method with f.

func (*Resource[T]) Delete added in v1.0.3

func (r *Resource[T]) Delete(ctx context.Context, primaryId any) error

Delete deletes a resource by id

func (*Resource[T]) Deprecate added in v1.0.7

func (r *Resource[T]) Deprecate(permissions []access.Permission)

Deprecate deprecates a method.

func (*Resource[T]) Disable

func (r *Resource[T]) Disable(permissions []access.Permission)

Disable disables a list of access methods.

func (*Resource[T]) DisableAllDocs added in v1.0.5

func (r *Resource[T]) DisableAllDocs()

DisableAllDocs disables all docs, see DisableDocs() and individual doc disabling such as DisableCreateDocs()

func (*Resource[T]) DisableCreate

func (r *Resource[T]) DisableCreate()

DisableCreate disables creation on this resource.

func (*Resource[T]) DisableCreateDocs added in v1.0.5

func (r *Resource[T]) DisableCreateDocs()

DisableCreateDocs disables create doc generation

func (*Resource[T]) DisableDelete

func (r *Resource[T]) DisableDelete()

DisableDelete disables deletes on this resource.

func (*Resource[T]) DisableDeleteDocs added in v1.0.5

func (r *Resource[T]) DisableDeleteDocs()

DisableDeleteDocs disables deletes docs on this resource.

func (*Resource[T]) DisableDocs

func (r *Resource[T]) DisableDocs(permissions []access.Permission)

DisableDocs disables API doc generation for a list of access methods.

func (*Resource[T]) DisableList

func (r *Resource[T]) DisableList()

DisableList disables listing on this resource.

func (*Resource[T]) DisableListDocs added in v1.0.5

func (r *Resource[T]) DisableListDocs()

DisableListDocs disables listing docs on this resource.

func (*Resource[T]) DisableRead

func (r *Resource[T]) DisableRead()

DisableRead disables reads on this resource.

func (*Resource[T]) DisableReadDocs added in v1.0.5

func (r *Resource[T]) DisableReadDocs()

disableReadDocs disables read docs on this resource.

func (*Resource[T]) DisableUpdate

func (r *Resource[T]) DisableUpdate()

DisableUpdate disables updates on this resource.

func (*Resource[T]) DisableUpdateDocs added in v1.0.5

func (r *Resource[T]) DisableUpdateDocs()

DisableUpdate disables updates on this resource.

func (*Resource[T]) EnableACL

func (r *Resource[T]) EnableACL(acl access.ACL, grantPermissionsOnCreate []access.Permission, f func(obj *T) (selfID any))

EnableACL enables fine-grained access control over a resource. You are expected to implement the access.ACL interface (see pkg/access/examples) and pass it as the first argument. grantPermissionsOnCreate is the permissions that will be granted to a resource for the authenticated user on a CREATE. f is expected to return the ID of the resource which is what is passed to the acl.ACL interface to verify the authenticated user has access to the resource with that id

Example 1 (where a user (id=1) has sole access to resource user_settings (id=2))

resource_ownership_table (example, actual implementation is up to access.ACL implementation)

owner_id resource resource_id permissions -------------------------------------------------------------------------- 1 user_settings 2 create/read/write/delete/list

userSettings.EnableACL(acl, access.PermissionAll, func(userSettings *UserSettings) (selfID any, ownerID any) {
    return userSettings.ID, userSettings.UserID
})

Example 2 (where two users (id=1) and (id=2) have access to the same playlist (id=1) with different permissions)

resource_ownership_table (example, actual implementation is up to access.ACL implementation)

owner_id resource resource_id permissions -------------------------------------------------------------------------- 1 playlist 1 create/read/write/delete/list 2 playlist 1 read/list

playlist.EnableACL(acl, access.PermissionAll, func(playlist *Playlist) (selfID any, ownerID any) {
    return playlist.ID, userSettings.UserID
})

Note: You can EnableRBAC() and EnableACL() at the same time.

func (*Resource[T]) EnableRBAC

func (r *Resource[T]) EnableRBAC(rbac access.RBAC)

EnableRBAC enables Role Based Access Control for a resource. You are expected to implement the access.RBAC interface (see pkg/access/examples). This provides broad access control over a resource. If you also want fine-grained control, see EnableACL()

Note: You can EnableRBAC() and EnableACL() at the same time.

func (*Resource[T]) GET

func (r *Resource[T]) GET(f func(c router.Context))

GET overrides the GET method with f.

func (*Resource[T]) GenerateRestAPI

func (r *Resource[T]) GenerateRestAPI(routes router.Router, db *gorm.DB, openAPI *openapi.Builder) error

GenerateRestAPI generates REST API endpoints for a resource. This also handles RBAC and makes sure the calling user has permission for an action on a resource.

GET /resources -> returns a paginated list of resources (with a max amount per page) and filters GET /resources/:primaryField -> returns a single resource by the primary field POST /resources -> creates a single resource PUT /resources/:primaryField -> updates a single resource by its primary field PATCH /resources/:primaryField -> patches a single resource by its primary field DELETE /resources/:primaryField -> deletes a single resource by its primary field

func (*Resource[T]) Get added in v1.0.3

func (r *Resource[T]) Get(ctx context.Context, primaryId any) (*T, error)

Get returns the resource by the primary id.

func (*Resource[T]) IgnoreAllFields

func (r *Resource[T]) IgnoreAllFields() *Resource[T]

IgnoreAllFields ignores all fields.

func (*Resource[T]) IgnoreField

func (r *Resource[T]) IgnoreField(field string, accessMethod []access.Permission) *Resource[T]

IgnoreField will ignore a field for a specific permission. You can ignore a field for: access.PermissionRead, access.PermissionWrite, access.PermissionList, access.PermissionCreate.

func (*Resource[T]) IgnoreFieldUnlessRole

func (r *Resource[T]) IgnoreFieldUnlessRole(field string, accessMethod []access.Permission, roles []string) *Resource[T]

IgnoreFieldUnlessRole will ignore the field for all operations unless the requester has the roles provided. This can allow specific fields, such as join fields, to be ignored but they can still be updated by admins in tools.

This requires rbac to be enabled, else this will ignore fields for all roles.

func (*Resource[T]) IgnoreFields

func (r *Resource[T]) IgnoreFields(fields []string, accessMethod []access.Permission) *Resource[T]

IgnoreFields will ignore a list of fields for a specific permission. You can ignore a field for: access.PermissionRead, access.PermissionWrite, access.PermissionList, access.PermissionCreate.

func (*Resource[T]) IgnoreFieldsUnlessRole

func (r *Resource[T]) IgnoreFieldsUnlessRole(fields []string, accessMethod []access.Permission, roles []string) *Resource[T]

IgnoreFieldsUnlessRole calls IgnoreFieldUnlessRole for each given field.

func (*Resource[T]) IsValid

func (r *Resource[T]) IsValid(v any, schema *openapi.Schema) []error

IsValid validates that the value v is a valid resource.

func (*Resource[T]) MaxInputBytes

func (r *Resource[T]) MaxInputBytes(maxInputBytes int64)

MaxInputBytes sets the maximum bytes when reading a resource from a client. 10MB by default.

func (*Resource[T]) Name

func (r *Resource[T]) Name() string

Name returns the resource name.

func (*Resource[T]) PATCH

func (r *Resource[T]) PATCH(f func(c router.Context))

PATCH overrides the PATCH method with f.

func (*Resource[T]) POST

func (r *Resource[T]) POST(f func(c router.Context))

POST overrides the POST method with f.

func (*Resource[T]) PUT

func (r *Resource[T]) PUT(f func(c router.Context))

PUT overrides the PUT method with f.

func (*Resource[T]) Patch added in v1.0.3

func (r *Resource[T]) Patch(ctx context.Context, primaryId any, resource *T) error

func (*Resource[T]) Path

func (r *Resource[T]) Path(permission access.Permission, path string)

Path sets a path for a specific permission

func (*Resource[T]) Plural

func (r *Resource[T]) Plural(pluralName string)

Plural sets the plural name for this resource.

func (*Resource[T]) PluralName

func (r *Resource[T]) PluralName() string

PluralName returns the plural name.

func (*Resource[T]) Preload

func (r *Resource[T]) Preload(association ...string)

Preload loads a resources associations. For example:

Preload("Organization.Roles", "Keys") would load User.Organization, User.Organization.Roles and User.Keys.

func (*Resource[T]) PrimaryField

func (r *Resource[T]) PrimaryField() string

PrimaryField returns the name of the primary field. The primary field is what is used for REST endpoints such as /users/:id (in this case, id, is the primary field).

func (*Resource[T]) PrimaryFieldURLParam

func (r *Resource[T]) PrimaryFieldURLParam() string

PrimaryFieldURLParam returns the URL param for the primary field. This must be unique across resources.

func (*Resource[T]) SetFieldQueryOperation

func (r *Resource[T]) SetFieldQueryOperation(field string, op FieldQueryOperation)

SetFieldQueryOperation sets the query operation for a field

Resource[schema.User].SetFieldQueryOperation("EndTime", FieldOperationLessThanEqual).

func (*Resource[T]) SetHasOwnership

func (r *Resource[T]) SetHasOwnership(f func(c router.Context, resource string, obj *T) bool)

SetHasOwnership sets the function which checks if the resource is owned by the caller making the request.

func (*Resource[T]) SetQueryParamAlias added in v1.0.3

func (r *Resource[T]) SetQueryParamAlias(queryParam string, alias string)

SetQueryParamAlias sets a specific query param as an alias

Example:

queryParam: dateGte
alias: from

Passing from="2021-12-24 15:04:05" will do a dateGte="2021-12-24 15:04:05"

func (*Resource[T]) TXContextKey

func (r *Resource[T]) TXContextKey(txKey string)

TXContextKey overrides the transaction key used for queries, by default this is "gorm_tx".

func (*Resource[T]) Tags

func (r *Resource[T]) Tags(tags []string)

Tags replaces all tags, see AddTag to add a single tag.

func (*Resource[T]) Update added in v1.0.3

func (r *Resource[T]) Update(ctx context.Context, primaryId any, resource *T) error

type ResourceInterface

type ResourceInterface interface {
	Name() string
	PluralName() string
	PrimaryField() string
	PrimaryFieldURLParam() string
}

type S

type S map[string]any

S is used to easily craft JSON responses, see ResourceNotFound.

type UserError

type UserError struct {
	Code    int
	Message string
}

UserError is a custom error type that means this error will be shown to the user. The user gets a JSON containing the code and the message instead of an InternalServerError.

func (*UserError) Error

func (e *UserError) Error() string

Directories

Path Synopsis
pkg
pluralize
MIT License
MIT License
chi
gin

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL