Documentation
¶
Index ¶
- Variables
- func DefaultHasOwnership[T any](c router.Context, resource string, obj *T) bool
- func NewUserError(code int, message string) error
- type Field
- type FieldIgnoreRule
- type FieldQueryOperation
- type PathOption
- type Resource
- func (r *Resource[T]) AddTag(tag string)
- func (r *Resource[T]) AfterDelete(f func(ctx context.Context, obj *T) error)
- func (r *Resource[T]) AfterSave(permission access.Permission, f func(ctx context.Context, obj *T) error)
- func (r *Resource[T]) AllowField(field string, permissions []access.Permission) *Resource[T]
- func (r *Resource[T]) AllowFields(fields []string, permissions []access.Permission) *Resource[T]
- func (r *Resource[T]) BeforeDelete(f func(ctx context.Context, obj *T) error)
- func (r *Resource[T]) BeforeListResponse(responseType any, f func(ctx context.Context, obj []*T) (any, error))
- func (r *Resource[T]) BeforeRequest(requestType any, permission access.Permission, ...)
- func (r *Resource[T]) BeforeResponse(responseType any, permission access.Permission, ...)
- func (r *Resource[T]) BeforeSave(permission access.Permission, f func(ctx context.Context, obj *T) error)
- func (r *Resource[T]) BelongsTo(resource ResourceInterface, field string)
- func (r *Resource[T]) Create(ctx context.Context, resource *T) error
- func (r *Resource[T]) DELETE(f func(c router.Context))
- func (r *Resource[T]) Delete(ctx context.Context, primaryId any) error
- func (r *Resource[T]) Deprecate(permissions []access.Permission)
- func (r *Resource[T]) Disable(permissions []access.Permission)
- func (r *Resource[T]) DisableAllDocs()
- func (r *Resource[T]) DisableCreate()
- func (r *Resource[T]) DisableCreateDocs()
- func (r *Resource[T]) DisableDelete()
- func (r *Resource[T]) DisableDeleteDocs()
- func (r *Resource[T]) DisableDocs(permissions []access.Permission)
- func (r *Resource[T]) DisableList()
- func (r *Resource[T]) DisableListDocs()
- func (r *Resource[T]) DisableRead()
- func (r *Resource[T]) DisableReadDocs()
- func (r *Resource[T]) DisableUpdate()
- func (r *Resource[T]) DisableUpdateDocs()
- func (r *Resource[T]) EnableACL(acl access.ACL, grantPermissionsOnCreate []access.Permission, ...)
- func (r *Resource[T]) EnableRBAC(rbac access.RBAC)
- func (r *Resource[T]) GET(f func(c router.Context))
- func (r *Resource[T]) GenerateRestAPI(routes router.Router, db *gorm.DB, openAPI *openapi.Builder) error
- func (r *Resource[T]) Get(ctx context.Context, primaryId any) (*T, error)
- func (r *Resource[T]) IgnoreAllFields() *Resource[T]
- func (r *Resource[T]) IgnoreField(field string, accessMethod []access.Permission) *Resource[T]
- func (r *Resource[T]) IgnoreFieldUnlessRole(field string, accessMethod []access.Permission, roles []string) *Resource[T]
- func (r *Resource[T]) IgnoreFields(fields []string, accessMethod []access.Permission) *Resource[T]
- func (r *Resource[T]) IgnoreFieldsUnlessRole(fields []string, accessMethod []access.Permission, roles []string) *Resource[T]
- func (r *Resource[T]) IsValid(v any, schema *openapi.Schema) []error
- func (r *Resource[T]) MaxInputBytes(maxInputBytes int64)
- func (r *Resource[T]) Name() string
- func (r *Resource[T]) PATCH(f func(c router.Context))
- func (r *Resource[T]) POST(f func(c router.Context))
- func (r *Resource[T]) PUT(f func(c router.Context))
- func (r *Resource[T]) Patch(ctx context.Context, primaryId any, resource *T) error
- func (r *Resource[T]) Path(permission access.Permission, path string)
- func (r *Resource[T]) Plural(pluralName string)
- func (r *Resource[T]) PluralName() string
- func (r *Resource[T]) Preload(association ...string)
- func (r *Resource[T]) PrimaryField() string
- func (r *Resource[T]) PrimaryFieldURLParam() string
- func (r *Resource[T]) SetFieldQueryOperation(field string, op FieldQueryOperation)
- func (r *Resource[T]) SetHasOwnership(f func(c router.Context, resource string, obj *T) bool)
- func (r *Resource[T]) SetQueryParamAlias(queryParam string, alias string)
- func (r *Resource[T]) TXContextKey(txKey string)
- func (r *Resource[T]) Tags(tags []string)
- func (r *Resource[T]) Update(ctx context.Context, primaryId any, resource *T) error
- type ResourceInterface
- type S
- type UserError
Constants ¶
This section is empty.
Variables ¶
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 ¶
DefaultHasOwnership returns true by default and does not handle ownership. Call SetHasOwnership() to add ownership.
func NewUserError ¶
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
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 ¶
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]) AfterDelete ¶
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 ¶
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]) 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 ¶
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]) 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]) IgnoreAllFields ¶
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]) MaxInputBytes ¶
MaxInputBytes sets the maximum bytes when reading a resource from a client. 10MB by default.
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]) PluralName ¶
PluralName returns the plural name.
func (*Resource[T]) Preload ¶
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 ¶
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 ¶
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 ¶
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
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 ¶
TXContextKey overrides the transaction key used for queries, by default this is "gorm_tx".