Logo

Green Barrel

ORM-like API MongoDB for Rust

To simulate fields of type ForeignKey and ManyToMany, a simplified alternative (Dynamic Widgets) is used. For examples of how to add fields to the Model, see tests. For maximum convenience use actix-green-panel.

crates.io crates.io crates.io crates.io

Attention

MongoDB Rust Driver version 1.2.5 is used.

Actix-green-panel is the recommended part of the entire ecosystem ( green-barrel, metamorphose, actix-green-pane ). For those who use actix-green-panel - Follow our updates.

Requirements

Model parameters

( all parameters are optional )

| Parameter: | Default: | Description: | | :------------------ | :----------- | :--------------------------------------------------------------------------------------------------- | | dbclientname | empty string | Used to connect to a MongoDB cluster. | | dbquerydocslimit | 1000 | limiting query results. | | isadddocs | true | Create documents in the database. false - Alternatively, use it to validate data from web forms. | | isupdocs | true | Update documents in the database. | | isdeldocs | true | Delete documents from the database. | | ignorefields | empty string | Fields that are not included in the database (separated by commas). | | isuseaddvalid | false | Allows additional validation - impl AdditionalValidation for ModelName. | | isusehooks | false | Allows hooks methods - impl Hooks for ModelName. | | isusecustomhtml | false | Allows the ability to customization html code for web forms - impl GenerateHtml for ModelName. |

Match field types and widget types

| Field type: | Widget type: | | :---------------------- | :------------------ | | Option< bool > | "checkBox" | | - | - | | Option< String > | "inputSlug" | | - | - | | Option< String > | "inputColor" | | Option< String > | "inputDate" | | Option< String > | "inputDateTime" | | Option< String > | "inputEmail" | | Option< String > | "inputPassword" | | Option< String > | "inputPhone" | | Option< String > | "inputText" | | Option< String > | "inputUrl" | | Option< String > | "inputIP" | | Option< String > | "inputIPv4" | | Option< String > | "inputIPv6" | | - | - | | Option< String > | "textArea" | | - | - | | Option< String > | "inputFile" | | Option< String > | "inputImage" | | - | - | | Option< i32 > | "numberI32" | | Option< u32 > | "numberU32" | | Option< i64 > | "numberI64" | | Option< f64 > | "numberF64" | | - | - | | Option< String > | "radioText" | | Option< i32 > | "radioI32" | | Option< u32 > | "radioU32" | | Option< i64 > | "radioI64" | | Option< f64 > | "radioF64" | | - | - | | Option< i32 > | "rangeI32" | | Option< u32 > | "rangeU32" | | Option< i64 > | "rangeI64" | | Option< f64 > | "rangeF64" | | - | - | | Option< String > | "selectText" | | Option< String > | "selectTextDyn" | | Option< Vec< String > > | "selectTextMult" | | Option< Vec< String > > | "selectTextMultDyn" | | Option< i32 > | "selectI32" | | Option< i32 > | "selectI32Dyn" | | Option< Vec< i32 > > | "selectI32Mult" | | Option< Vec< i32 > > | "selectI32MultDyn" | | Option< u32 > | "selectU32" | | Option< u32 > | "selectU32Dyn" | | Option< Vec< u32 > > | "selectU32Mult" | | Option< Vec< u32 > > | "selectU32MultDyn" | | Option< i64 > | "selectI64" | | Option< i64 > | "selectI64Dyn" | | Option< Vec< i64 > > | "selectI64Mult" | | Option< Vec< i64 > > | "selectI64MultDyn" | | Option< f64 > | "selectF64" | | Option< f64 > | "selectF64Dyn" | | Option< Vec< f64 > > | "selectF64Mult" | | Option< Vec< f64 > > | "selectF64MultDyn" | | - | - | | Option< String > | "hiddenText" | | Option< i32 > | "hiddenI32" | | Option< u32 > | "hiddenU32" | | Option< i64 > | "hiddenI64" | | Option< f64 > | "hiddenF64" |

Field attributes

( all attributes are optional )

| Attribute: | Default: | Description: | | :----------- | :----------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | | id | empty string | The value is determined automatically. Format: "model-name--field-name". | | label | empty string | Web form field name. | | widget | "inputText" | Widget name. | | inputtype | "text" | The value is determined automatically. | | name | empty string | The value is determined automatically. | | value | empty string | Default value. | | accept | empty string | Example: "image/jpeg,image/png,image/gif". | | placeholder | empty string | Displays prompt text. | | pattern | empty string | Validating a field using a client-side regex. | | minlength | 0 | The minimum number of characters allowed in the text. | | maxlength | 256 | The maximum number of characters allowed in the text. | | required | false | Mandatory field. | | checked | false | A pre-activated radio button or checkbox. | | unique | false | The unique value of a field in a collection. | | disabled | false | Blocks access and modification of the element. | | readonly | false | Specifies that the field cannot be modified by the user. | | step | "1" | Increment step for numeric fields. | | min | empty string | The lower value for entering a number or date. | | max | empty string | The top value for entering a number or date. | | options | empty array | Example: r#"[[1,"Volvo"], [2,"Saab"]]"#. | | thumbnails | empty array | From one to four inclusive. Example: r#"[["xs",150],["sm",300],["md",600],["lg",1200]]"#. Hint: An Intel i7-4770 processor or better is recommended. | | slugsources | empty array | Example: r#"["title"]"# or r#"["hash", "username"]"# or r#"["email", "firstname", "lastname"]"#. | | ishide | false | Hide field from user. | | otherattrs | empty string | Example: r# "autofocus tabindex="some number" size="some number""#. | | css_classes | empty string | Example: "class-name-1 class-name-2". | | hint | empty string | Additional explanation for the user. | | warning | empty string | The value is determined automatically. | | error | empty string | The value is determined automatically. | | alert | empty string | Alert message for the entire web form. The value is determined automatically. |

Install mongodb (if not installed)

```shell

Ubuntu, Mint:

$ sudo apt install mongodb

OR

Ubuntu 20.04, Mint 20.x:

$ sudo apt update $ sudo apt install dirmngr gnupg apt-transport-https ca-certificates $ wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add - $ sudo add-apt-repository 'deb [arch=amd64] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse' $ sudo apt update $ sudo apt install mongodb-org $ sudo systemctl enable --now mongod

For check

$ mongod --version $ mongo --eval 'db.runCommand({ connectionStatus: 1 })' $ reboot #

Configuration file:

$ sudo nano /etc/mongod.conf #

Systemd:

$ sudo systemctl status mongod $ sudo systemctl start mongod $ sudo systemctl stop mongod $ sudo systemctl restart mongod $ sudo systemctl enable mongod $ sudo systemctl disable mongod #

Uninstall:

$ sudo systemctl stop mongod $ sudo systemctl disable mongod $ sudo apt --purge remove mongodb* # OR (for mongodb-org) - $ sudo apt --purge remove mongodb-org** $ sudo rm -r /var/log/mongodb $ sudo rm -r /var/lib/mongodb $ sudo rm -f /etc/mongod.conf $ sudo apt-add-repository --remove 'deb [arch=amd64] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/4.4 multiverse' # for mongodb-org $ sudo apt update ```

Example Usage:

Cargo.toml

```toml [dependencies] green-barrel = "0.12" metamorphose = "0.7" regex = "1.5.6" serde_json = "1.0.81"

[dependencies.mongodb] default-features = false features = ["sync"] version = "1.2.5"

[dependencies.serde] features = ["derive"] version = "1.0.137" ```

src/settings.rs

```rust // General settings for the project. // Project name. // Hint: PROJECTNAM it is recommended not to change. // Valid characters: _ a-z A-Z 0-9 // Max size: 21 // First character: a-z A-Z pub const PROJECTNAME: &str = "store";

// Unique project key. // Hint: UNIQUEPROJECTKEY it is recommended not to change. // Valid characters: a-z A-Z 0-9 // Size: 8-16 // Example: "7rzgacfqQB3B7q7T" pub const UNIQUEPROJECTKEY: &str = "bhjRV8ry9X5LQBw";

// Settings for user accounts. pub mod users { // Valid characters: _ a-z A-Z 0-9 // Max size: 31 // First character: a-z A-Z pub const SERVICENAME: &str = "accounts"; // Valid characters: _ a-z A-Z 0-9 // Max size: 21 // First character: a-z A-Z pub const DATABASENAME: &str = "accounts"; pub const DBCLIENTNAME: &str = "default"; pub const DBQUERYDOCS_LIMIT: u32 = 1000; } ```

src/migration.rs

```rust use green-barrel::{Monitor, ToModel, MONGODBCLIENTSTORE}; use crate::{models, settings};

// Migration Service Mango. pub fn mangomigration() -> Result<(), Box> { // Caching MongoDB clients. { let mut clientstore = MONGODBCLIENTSTORE.write()?; clientstore.insert( "default".tostring(), mongodb::sync::Client::withuristr("mongodb://localhost:27017")?, ); } // Monitor initialization. let monitor = Monitor { projectname: settings::PROJECTNAME, uniqueprojectkey: settings::UNIQUEPROJECTKEY, // Register models. models: vec![models::UserProfile::meta()?], }; // Run migration monitor.migrat()?; // Ok(()) } ```

src/models.rs

```rust use green-barrel::*; use metamorphose::Model; use regex::RegexBuilder; use serde::{Deserialize, Serialize};

use crate::settings::{ users::{DATABASENAME, DBCLIENTNAME, DBQUERYDOCSLIMIT, SERVICENAME}, PROJECTNAME, UNIQUEPROJECTKEY, };

// User profiles

[Model(

is_del_docs = false,
is_use_add_valid = true,
is_use_hooks = true,
//is_use_custom_html = true,
ignore_fields = "confirm_password"

)]

[derive(Serialize, Deserialize, Default, Debug)]

pub struct UserProfile { #[serde(default)] #[fieldattrs( widget = "inputText", label = "Username", placeholder = "Enter your username", unique = true, required = true, maxlength = 150, hint = "Valid characters: a-z A-Z 0-9 _ @ + .
Max size: 150" )] pub username: Option, // #[serde(default)] #[field
attrs( widget = "inputSlug", label = "Slug", unique = true, readonly = true, ishide = true, hint = "To create a human readable url", slugsources = r#"["hash", "username"]"# )] pub slug: Option, // #[serde(default)] #[fieldattrs( widget = "inputText", label = "First name", placeholder = "Enter your First name", maxlength = 150 )] pub firstname: Option, // #[serde(default)] #[fieldattrs( widget = "inputText", label = "Last name", placeholder = "Enter your Last name", maxlength = 150 )] pub lastname: Option, // #[serde(default)] #[fieldattrs( widget = "inputEmail", label = "E-mail", placeholder = "Please enter your email", required = true, unique = true, maxlength = 320, hint = "Your actual E-mail" )] pub email: Option, // #[serde(default)] #[fieldattrs( widget = "inputPhone", label = "Phone number", placeholder = "Please enter your phone number", unique = true, maxlength = 30, hint = "Your actual phone number" )] pub phone: Option, // #[serde(default)] #[fieldattrs( widget = "inputPassword", label = "Password", placeholder = "Enter your password", required = true, minlength = 8, hint = "Valid characters: a-z A-Z 0-9 @ # $ % ^ & + = * ! ~ ) (
Min size: 8" )] pub password: Option, // #[serde(default)] #[field
attrs( widget = "inputPassword", label = "Confirm password", placeholder = "Repeat your password", required = true, minlength = 8, hint = "Repeat your password" )] pub confirmpassword: Option, // #[serde(default)] #[fieldattrs( widget = "checkBox", label = "is staff?", checked = true, hint = "User can access the admin site?" )] pub isstaff: Option, // #[serde(default)] #[fieldattrs( widget = "checkBox", label = "is active?", checked = true, hint = "Is this an active account?" )] pub is_active: Option, }

// Model parameter: isuseaddvalid = true impl AdditionalValidation for UserProfile { fn addvalidation<'a>( &self, ) -> Result, Box> { // Hint: errormap.insert("fieldname", "Error message.") let mut error_map: std::collections::HashMap<&'a str, &'a str> = std::collections::HashMap::new();

    // Get clean data
    let hash = self.hash.clone().unwrap_or_default();
    let password = self.password.clone().unwrap_or_default();
    let confirm_password = self.confirm_password.clone().unwrap_or_default();
    let username = self.username.clone().unwrap_or_default();

    // Fields validation
    if hash.is_empty() && password != confirm_password {
        error_map.insert("confirm_password", "Password confirmation does not match.");
    }
    if !RegexBuilder::new(r"^[a-z\d_@+.]+$")
        .case_insensitive(true)
        .build()
        .unwrap()
        .is_match(username.as_str())
    {
        error_map.insert(
            "username",
            "Invalid characters present.<br>\
                Valid characters: a-z A-Z 0-9 _ @ + .",
        );
    }
    //
    Ok(error_map)
}

}

// Model parameter: isusehooks = true impl Hooks for UserProfile { fn precreate(&self) { println!("!!!Pre Create!!!"); } // fn postcreate(&self) { println!("!!!Post Create!!!"); } // fn preupdate(&self) { println!("!!!Pre Update!!!"); } // fn postupdate(&self) { println!("!!!Post Update!!!"); } // fn predelete(&self) { println!("!!!Pre Delet!!!"); } // fn postdelete(&self) { println!("!!!Post Delet!!!"); } }

// Model parameter: isusecustomhtml = true // See documentation: green-barrel > widgets > generatehtmlcode > GenerateHtmlCode > generatehtml() > source /* impl GenerateHtml for UserProfile { fn generate_html(&self) { Add your custom code... } } */ ```

src/main.rs

```rust mod migration; mod models; mod settings;

use green-barrel::*;

fn main() -> Result<(), Box> { // Run migration. migration::mango_migration()?;

// Convert Model.
// -----------------------------------------------------------------------------------------
//println!("{:?}", models::UserProfile::to_wig()?);
//println!("{}", models::UserProfile::to_json()?);
/*
println!(
    "Html code:\n{}",
    models::UserProfile::to_html(
        Some("/login"),
        Some(HttpMethod::POST),
        Some(Enctype::Multipart)
    )?
);
*/

// Create model instance.
// -----------------------------------------------------------------------------------------
let mut user = models::UserProfile {
    username: Some("user_1".to_string()),
    email: Some("user_1@@noreply.net".to_string()),
    password: Some("12345678".to_string()),
    confirm_password: Some("12345678".to_string()),
    is_staff: Some(false),
    ..Default::default()
};

// Check model.
// -----------------------------------------------------------------------------------------
/*
let output_data = user.check(None)?;
println!("Boolean: {}", output_data.is_valid());
println!("Hash: {}", output_data.hash());
println!("Object Id: {:?}", output_data.object_id());
println!("Created at: {}", user.get_created_at());
println!("Updated at: {}", user.get_updated_at());
// Printing errors to the console ( for development ).
if !output_data.is_valid() {
    output_data.print_err();
}
//
//println!("Slug: {}\n\n", output_data.to_wig().get("slug").unwrap().value);
//
//println!("bson::Document:\n{:?}\n\n", output_data.get_doc());
//println!("Widget map:\n{:?}\n\n", output_data.to_wig());
//println!("Json:\n{}\n\n", output_data.to_json()?);
/*
println!(
    "Html:\n{}\n\n",
    output_data.to_html(
        Some("/login"),
        Some(HttpMethod::POST),
        Some(Enctype::Multipart)
    )?
);
*/
*/

// Create or update a document in the database ( check() + save() ).
// -----------------------------------------------------------------------------------------
let output_data = user.save(None, None)?;
println!("Boolean: {}", output_data.is_valid());
println!("Hash: {}", output_data.hash());
println!("Object Id: {:?}", output_data.object_id());
println!("Created at: {}", user.get_created_at());
println!("Updated at: {}", user.get_updated_at());
// Printing errors to the console ( for development ).
if !output_data.is_valid() {
    output_data.print_err();
}
//
//println!("Slug: {}\n\n", output_data.to_wig().get("slug").unwrap().value);
//
//println!("bson::Document:\n{:?}\n\n", output_data.get_doc());
//println!("Widget map:\n{:?}\n\n", output_data.to_wig());
//println!("Json:\n{}\n\n", output_data.to_json()?);
/*
println!(
    "Html:\n{}\n\n",
    output_data.to_html(
        Some("/login"),
        Some(HttpMethod::POST),
        Some(Enctype::Multipart)
    )?
);
*/

// Remove document from collection in database.
// -----------------------------------------------------------------------------------------
/*
let output_data  = user.delete(None)?;
if !output_data.is_valid() {
    println!("{}", output_data.err_msg());
}
*/

Ok(())

} ```

Changelog

License

This project is licensed under the MIT and Apache Version 2.0