pub trait Resource: Send + Sync {
Show 39 methods
// Required methods
fn resource_name(&self) -> &'static str;
fn base_path(&self) -> &'static str;
fn table_name(&self) -> &'static str;
fn clone_box(&self) -> Box<dyn Resource>;
// Provided methods
fn primary_key(&self) -> &'static str { ... }
fn menu_group(&self) -> Option<&'static str> { ... }
fn menu(&self) -> &'static str { ... }
fn allowed_roles(&self) -> Vec<String> { ... }
fn allowed_actions(&self) -> Option<Vec<MenuAction>> { ... }
fn custom_actions(&self) -> Vec<CustomAction> { ... }
fn permit_keys(&self) -> Vec<&'static str> { ... }
fn readonly_keys(&self) -> Vec<&'static str> { ... }
fn soft_delete(&self) -> bool { ... }
fn form_structure(&self) -> Option<Value> { ... }
fn filterable_fields(&self) -> Vec<FilterField> { ... }
fn file_fields(&self) -> Vec<FileField> { ... }
fn search_fields(&self) -> Vec<&'static str> { ... }
fn list<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait { ... }
fn get<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait { ... }
fn create<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
body: Value,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait { ... }
fn update<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
body: Value,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait { ... }
fn delete<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait { ... }
fn form_fields(&self) -> Vec<Value> { ... }
fn list_page<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait { ... }
fn new_page<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait { ... }
fn edit_page<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait { ... }
fn view_page<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait { ... }
fn attach_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
csrf: Option<String>,
file: UploadedFile,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Self: 'async_trait { ... }
fn serve_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Self: 'async_trait { ... }
fn detach_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Self: 'async_trait { ... }
fn history_page<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait { ... }
fn create_form<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
form: HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait { ... }
fn update_form<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
form: HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait { ... }
fn delete_form<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait { ... }
fn run_action<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
name: &'life2 str,
id: String,
body: Value,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait { ... }
fn export<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
format: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait { ... }
fn authorize(&self, ctx: &ReqCtx, action: Action<'_>) -> bool { ... }
fn filter_writable(
&self,
body: Value,
) -> Result<Map<String, Value>, ApiResponse> { ... }
fn generate_menu(&self) -> Option<MenuItem> { ... }
}Required Methods§
fn resource_name(&self) -> &'static str
fn base_path(&self) -> &'static str
Sourcefn table_name(&self) -> &'static str
fn table_name(&self) -> &'static str
Backing table (SQL) or collection (Mongo) name.
fn clone_box(&self) -> Box<dyn Resource>
Provided Methods§
fn primary_key(&self) -> &'static str
fn allowed_roles(&self) -> Vec<String>
fn allowed_actions(&self) -> Option<Vec<MenuAction>>
Sourcefn custom_actions(&self) -> Vec<CustomAction>
fn custom_actions(&self) -> Vec<CustomAction>
Extra id-scoped operations beyond CRUD, exposed at
POST /{base}/{id}/action/{name} and as buttons on the detail page.
Sourcefn permit_keys(&self) -> Vec<&'static str>
fn permit_keys(&self) -> Vec<&'static str>
Mass-assignment allow-list for create/update.
Sourcefn readonly_keys(&self) -> Vec<&'static str>
fn readonly_keys(&self) -> Vec<&'static str>
Columns that must never be client-set.
Sourcefn soft_delete(&self) -> bool
fn soft_delete(&self) -> bool
Whether delete should soft-delete (set deleted = true).
Sourcefn form_structure(&self) -> Option<Value>
fn form_structure(&self) -> Option<Value>
Custom form layout for create/edit pages. Return None to derive fields
from permit_keys(). Shape: { "groups": [{ "fields": [...] }] }.
Sourcefn filterable_fields(&self) -> Vec<FilterField>
fn filterable_fields(&self) -> Vec<FilterField>
Columns exposed as filters on the list page. Empty (the default) means no
filter bar is shown. Build entries with FilterField::text/select/boolean.
Sourcefn file_fields(&self) -> Vec<FileField>
fn file_fields(&self) -> Vec<FileField>
File attachments this resource accepts. Empty (the default) means no
upload widgets are shown. Each field renders an <input type=file> on the
detail page, backed by the attach/serve/detach routes. Requires an
attachment backend (adminx-storage) to be registered; without one the
widgets are hidden.
Sourcefn search_fields(&self) -> Vec<&'static str>
fn search_fields(&self) -> Vec<&'static str>
Columns included in this resource’s full-text search document. Empty (the
default) means the resource is not searchable — no search box, no
indexing. When non-empty and a search backend (adminx-search) is
registered, create/update keep the index in sync, delete removes the
record, and the list page grows a ?q= search box.
fn list<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
fn get<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
Sourcefn create<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
body: Value,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
fn create<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
body: Value,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
Create. The body lives in crud::create so an
overriding impl can delegate to it and keep the audit recording; see that
module for why.
Sourcefn update<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
body: Value,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn update<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
body: Value,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
Update. Body in crud::update.
Sourcefn delete<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn delete<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
Delete. Body in crud::delete.
Sourcefn form_fields(&self) -> Vec<Value>
fn form_fields(&self) -> Vec<Value>
The form fields to render on create/edit. Derived from an explicit
form_structure() when present, otherwise from permit_keys().
fn list_page<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
fn new_page<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
fn edit_page<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn view_page<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
Sourcefn attach_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
csrf: Option<String>,
file: UploadedFile,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Self: 'async_trait,
fn attach_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
csrf: Option<String>,
file: UploadedFile,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Self: 'async_trait,
Store an uploaded file against this record under field. Called by the
web adapter after it parses the multipart body; redirects back to the
detail page on success. Gated on Update — attaching a file changes the
record.
Sourcefn serve_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Self: 'async_trait,
fn serve_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Self: 'async_trait,
Stream a stored attachment back. Gated on Read — seeing the file is a
read of the record it belongs to. Returns the bytes with the stored
content type and a download-friendly filename.
Sourcefn detach_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Self: 'async_trait,
fn detach_file<'life0, 'life1, 'life2, 'life3, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
field: &'life3 str,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
'life3: 'async_trait,
Self: 'async_trait,
Remove one field’s attachment. Gated on Update; redirects back to the
detail page.
Sourcefn history_page<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn history_page<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
The recorded history of one record. Reads through the audit seam, so with no auditor registered it renders an empty log rather than 404 — the route exists either way and the page explains itself.
Sourcefn create_form<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
form: HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
fn create_form<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
form: HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
Handle a submitted create form; redirects to the list on success.
Sourcefn update_form<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
form: HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn update_form<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
form: HashMap<String, String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
Handle a submitted edit form; redirects to the item view on success.
Sourcefn delete_form<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn delete_form<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
id: &'life2 str,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
Handle a delete from the list UI; redirects back to the list. csrf is
the submitted hidden field, checked against the cookie before anything
is removed.
Sourcefn run_action<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
name: &'life2 str,
id: String,
body: Value,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn run_action<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
name: &'life2 str,
id: String,
body: Value,
csrf: Option<String>,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
Look up a custom action by name and run it (after auth + CSRF checks).
csrf is the submitted hidden field; the action button posts a form, so
it’s guarded like the other mutating form handlers.
Sourcefn export<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
format: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
fn export<'life0, 'life1, 'life2, 'async_trait>(
&'life0 self,
ctx: &'life1 ReqCtx,
format: &'life2 str,
) -> Pin<Box<dyn Future<Output = ApiResponse> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
'life2: 'async_trait,
Self: 'async_trait,
Export the resource’s rows as json or csv (used by ?download=).
Whether the principal in ctx may perform action on this resource.
Delegates to the authorization seam: always allowed when auth is not
configured; a registered Authorizer decides
per action; otherwise the principal must hold one of allowed_roles().
Sourcefn filter_writable(
&self,
body: Value,
) -> Result<Map<String, Value>, ApiResponse>
fn filter_writable( &self, body: Value, ) -> Result<Map<String, Value>, ApiResponse>
Apply the permit/readonly/primary-key rules to an incoming JSON body, returning the writable column map or a ready-made error response.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".
Implementations on Foreign Types§
Source§impl Resource for AuditVersionResource
impl Resource for AuditVersionResource
Source§fn permit_keys(&self) -> Vec<&'static str>
fn permit_keys(&self) -> Vec<&'static str>
Nothing is writable. This also means filter_writable can never admit a
column, so even if a mutating override were removed the defaults would
reject the body rather than insert a forged entry.