Working with Rust and Rocket here. I have an endpoint to upload one file at a time with form-data:
use rocket::form::{Form, FromForm};
use rocket::fs::TempFile;
use std::ffi::OsStr;
use std::path::{Path};
use uuid::Uuid;
#[post("/file_upload", format = "multipart/form-data", data = "<form>")]
pub async fn file_upload(mut form: Form<Upload<'_>>) -> std::io::Result<String> {
// Get raw file
let file_name = form.file.raw_name().unwrap().dangerous_unsafe_unsanitized_raw().as_str();name
// Get extension of file name
let extension = Path::new(file_name).extension().and_then(OsStr::to_str).unwrap();
// Generate new UUID
let id: String = Uuid::new_v4().to_string();
// Build path to save file
let file_path = String::from("media/temp_files") + "/" + &id + "." + extension;
// Save file
form.file.persist_to(file_path).await?;
Ok(String::from("Ok"))
}
This works, but I am mixing persistence, business logic and http infrastructure in the same module.
I want to rely on Rocket only to retrieve the file stream and metadata (file name, size and content type), and pass it to another function that would be in charge or validation, image processing, etc.
I have access to the metadata, but I don't know how to retrieve the Buffered content from the TempFile struct.
// rocket-0.5.0-rc.2/src/fs/temp_file.rs
[…]
pub enum TempFile<'v> {
#[doc(hidden)]
File {
file_name: Option<&'v FileName>,
content_type: Option<ContentType>,
path: Either<TempPath, PathBuf>,
len: u64,
},
#[doc(hidden)]
Buffered {
content: &'v str,
}
}
[…]
I don't see any method returning it.Is there any method I'm missing to retrieve the raw file content? Or maybe there is a different struct/trait in Rocket to achieve this.
Related
Getting an error when creating a tempfile for socket use.:
Error: Custom { kind: AlreadyExists, error: PathError { path: "/tmp", err: Custom { kind: AlreadyExists, error: "too many temporary files exist" } } }
use tempfile::{tempfile, Builder, NamedTempFile};
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let file = Builder::new().prefix("testsock").rand_bytes(0).tempfile()?;
Ok(())
}
Is there any way I can override the file?
The two many files exist error occurs if the file it is trying to create already exists. You can solve this error by deleting the files in your temp dir. In your case this is probably in /tmp/testsock/, you may also need to remove the folder testsock if just deleting the files does not work.
The default temp file will be used if .tempfile()
However you can specify the tempfile if you use the following.
let tmp = Builder::new()
.prefix("example")
.rand_bytes(0)
.tempfile_in("./")?;
The documentation for the tempfile builder can be found:
https://docs.rs/tempfile/latest/tempfile/struct.Builder.html
I want to execute this Move script, e.g. at sources/top_up.move:
script {
use std::signer;
use aptos_framework::aptos_account;
use aptos_framework::aptos_coin;
use aptos_framework::coin;
fun main(src: &signer, dest: address, desired_balance: u64) {
let src_addr = signer::address_of(src);
let balance = coin::balance<aptos_coin::AptosCoin>(src_addr);
if (balance < desired_balance) {
aptos_account::transfer(src, dest, desired_balance - balance);
};
}
}
This is calling functions on the aptos_coin.move module, which is deployed on chain. What it does isn't so important for this question, but in short, it checks that the balance of the destination account is less than desired_balance, and if so, tops it up to desired_balance.
I can execute this Move script via the CLI easily like this:
aptos move compile
aptos move run-script --compiled-script-path build/MyModule/bytecode_scripts/main.mv
Or even just this:
aptos move run-script --script-path sources/top_up.move
What I want to know is whether I can do this using the Rust SDK?
First, you need to compile the script, as you did above. Imagine you have a project layout like this:
src/
main.rs
move/
Move.toml
sources/
top_up.mv
You would want to go in to move/ and run aptos move compile, like you said above. From there, you can depend on the compiled script in your code (see below).
With that complete, here is a minimal code example demonstrating how to execute a Move script using the Rust SDK.
Cargo.toml:
[package]
name = "my-example"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1"
aptos-sdk = { git = "https://github.com/aptos-labs/aptos-core", branch = "mainnet" }
src/main.rs:
use aptos_sdk::crypto::ed25519::Ed25519PublicKey;
use aptos_sdk::types::transaction::authenticator::AuthenticationKey;
use aptos_sdk::{
rest_client::Client,
transaction_builder::TransactionFactory,
types::{
account_address::AccountAddress,
chain_id::ChainId,
transaction::{Script, SignedTransaction, TransactionArgument},
LocalAccount,
},
};
static SCRIPT: &[u8] =
include_bytes!("../../move/build/MyModule/bytecode_scripts/main.mv");
fn main() -> anyhow::Result<()> {
// Prior to the follow code we assume you've already acquired the necessary
// information such as chain_id, the private key of the account submitting
// the transaction, arguments for the Move script, etc.
// Build a transaction factory.
let txn_factory = TransactionFactory::new(chain_id);
// Build a local representation of an account.
let account = LocalAccount::new(
AuthenticationKey::ed25519(&Ed25519PublicKey::from(&private_key)).derived_address()
private_key,
0,
);
// Build an API client.
let client = Client::new("https://fullnode.mainnet.aptoslabs.com");
// Create a builder where the payload is the script.
let txn_builder = transaction_factory.script(Script::new(
SCRIPT.to_vec(),
// type args
vec![],
// args
vec![
TransactionArgument::Address(dest_address),
TransactionArgument::U64(desired_balance),
],
)));
// Build the transaction request and sign it.
let signed_txn = account.sign_with_transaction_builder(
txn_builder
);
// Submit the transaction.
client.submit_and_wait_bcs(&signed_transaction).await?;
}
Is it possible to access current file name in Rust by
// main.rs
fn main() {
println!("filename: {}", FILE_NAME);
}
?
(This program should print filename: main.rs)
You can use the std::file macro to get the current source filename at the compile time.
let this_file = file!();
If you want to remove the path from the returned filename, you can construct a Path with it and call the file_name method.
let filename_only = Path::new(this_file).file_name().and_then(|s| s.to_str()).unwrap();
Playground
I am using File Storage system for saving some data models confirming to Codable Protocol.
My Save function is as below:
func save<T: Encodable>(value: T, for key: String, on path: URL) throws {
let url = path.appendingPathComponent(key, isDirectory: false)
do {
try ANFileManager.createDirectoryAtPath(path: url.deletingLastPathComponent())
let archiver = NSKeyedArchiver(requiringSecureCoding: true)
archiver.outputFormat = .binary
try archiver.encodeEncodable(value, forKey: NSKeyedArchiveRootObjectKey)
archiver.finishEncoding()
// then you can use encoded data
try archiver.encodedData.write(to: url)
} catch {
throw StorageError.cantWrite(error)
}
}
My fetch function is as below:
func fetchValue<T: Decodable>(for key: String, from path: URL) throws -> T {
let url = path.appendingPathComponent(key)
let data = try Data(contentsOf: url)
let unarchiver = try NSKeyedUnarchiver(forReadingFrom: data)
unarchiver.decodingFailurePolicy = .setErrorAndReturn
guard let decoded = unarchiver.decodeDecodable(T.self, forKey:
NSKeyedArchiveRootObjectKey) else {
throw StorageError.notFound
}
unarchiver.finishDecoding()
if let error = unarchiver.error {
throw StorageError.cantRead(error)
}
else {
return decoded
}
}
Save and fetch are working fine but at runtime seeing some below warning in xcode console.
*** -[NSKeyedUnarchiver validateAllowedClass:forKey:] allowed unarchiving safe plist type ''NSString' (0x7fff863014d0) [/Applications/Xcode_13.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Foundation.framework]' for key 'NS.keys', even though it was not explicitly included in the client allowed classes set: '{(
"'NSDictionary' (0x7fff862db9a0) [/Applications/Xcode_13.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreFoundation.framework]",
"'NSDate' (0x7fff862db798) [/Applications/Xcode_13.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/CoreFoundation.framework]"
)}'. This will be disallowed in the future.
What should be done to suppress the warning ?
The problem is the failure to require secure coding on the unarchiver:
https://developer.apple.com/documentation/foundation/nskeyedunarchiver/1410824-requiressecurecoding
But more broadly it is very odd to pass through a keyed archiver when Codable is already saveable directly.
With this function I receive a file via graphql, How do I save the received content to a folder?
async fn single_upload(&self, ctx: &Context<'_>, file: Upload) -> FileInfo {
let mut storage = ctx.data_unchecked::<FileStorage>().lock().await;
println!("files count: {}", storage.len());
let entry = storage.vacant_entry();
let upload = file.value(ctx).unwrap();
let info = FileInfo {
id: entry.key().into(),
filename: upload.filename.clone(),
mimetype: upload.content_type,
};
entry.insert(info.clone());
info
}
I am using async-graphql and actix-web