From f9bdfd0e0069746c636032c5149d95fc07d4ea1d Mon Sep 17 00:00:00 2001 From: Quaternions Date: Wed, 3 Jul 2024 12:20:11 -0700 Subject: [PATCH] improve type safety for secrets --- rbx_asset/src/context.rs | 19 +++++-- src/main.rs | 108 ++++++++++++++++++--------------------- 2 files changed, 66 insertions(+), 61 deletions(-) diff --git a/rbx_asset/src/context.rs b/rbx_asset/src/context.rs index 0d3826b..90ce74e 100644 --- a/rbx_asset/src/context.rs +++ b/rbx_asset/src/context.rs @@ -201,15 +201,26 @@ fn read_readable(mut readable:impl std::io::Read)->std::io::Result>{ } #[derive(Clone)] -pub struct RobloxContext{ +pub struct ApiKey(String); +impl ApiKey{ + pub fn new(api_key:String)->Self{ + Self(api_key) + } + pub fn get(self)->String{ + self.0 + } +} + +#[derive(Clone)] +pub struct CloudContext{ pub api_key:String, pub client:reqwest::Client, } -impl RobloxContext{ - pub fn new(api_key:String)->Self{ +impl CloudContext{ + pub fn new(api_key:ApiKey)->Self{ Self{ - api_key, + api_key:api_key.get(), client:reqwest::Client::new(), } } diff --git a/src/main.rs b/src/main.rs index 6d451a3..23d978b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,7 +2,7 @@ use std::{io::Read,path::PathBuf}; use clap::{Args,Parser,Subcommand}; use anyhow::Result as AResult; use futures::StreamExt; -use rbx_asset::context::{AssetVersion,InventoryItem,RobloxContext}; +use rbx_asset::context::{ApiKey, AssetVersion, CloudContext, InventoryItem}; type AssetID=u64; type AssetIDFileMap=Vec<(AssetID,PathBuf)>; @@ -279,21 +279,21 @@ async fn main()->AResult<()>{ end_version:subcommand.end_version, start_version:subcommand.start_version.unwrap_or(0), output_folder:subcommand.output_folder.unwrap_or_else(||std::env::current_dir().unwrap()), - api_key:ApiKey::from_args( + api_key:api_key_from_args( subcommand.api_key_literal, subcommand.api_key_envvar, subcommand.api_key_file, - ).await?.get(), + ).await?, asset_id:subcommand.asset_id, }).await, Commands::Download(subcommand)=>{ let output_folder=subcommand.output_folder.unwrap_or_else(||std::env::current_dir().unwrap()); download_list( - ApiKey::from_args( + api_key_from_args( subcommand.api_key_literal, subcommand.api_key_envvar, subcommand.api_key_file, - ).await?.get(), + ).await?, subcommand.asset_ids.into_iter().map(|asset_id|{ let mut path=output_folder.clone(); path.push(asset_id.to_string()); @@ -303,11 +303,11 @@ async fn main()->AResult<()>{ }, Commands::DownloadDecompile(subcommand)=>{ download_decompile(DownloadDecompileConfig{ - api_key:ApiKey::from_args( + api_key:api_key_from_args( subcommand.api_key_literal, subcommand.api_key_envvar, subcommand.api_key_file, - ).await?.get(), + ).await?, asset_id:subcommand.asset_id, output_folder:subcommand.output_folder.unwrap_or_else(||std::env::current_dir().unwrap()), style:subcommand.style.rox(), @@ -317,20 +317,20 @@ async fn main()->AResult<()>{ }).await }, Commands::DownloadGroupInventoryJson(subcommand)=>download_group_inventory_json( - ApiKey::from_args( + api_key_from_args( subcommand.api_key_literal, subcommand.api_key_envvar, subcommand.api_key_file, - ).await?.get(), + ).await?, subcommand.group, subcommand.output_folder.unwrap_or_else(||std::env::current_dir().unwrap()), ).await, Commands::CreateAsset(subcommand)=>create(CreateConfig{ - api_key:ApiKey::from_args( + api_key:api_key_from_args( subcommand.api_key_literal, subcommand.api_key_envvar, subcommand.api_key_file, - ).await?.get(), + ).await?, creator_user_id:subcommand.creator_user_id, creator_group_id:subcommand.creator_group_id, input_file:subcommand.input_file, @@ -338,20 +338,20 @@ async fn main()->AResult<()>{ description:subcommand.description.unwrap_or_else(||String::with_capacity(0)), }).await, Commands::UploadAsset(subcommand)=>upload_asset(UploadAssetConfig{ - api_key:ApiKey::from_args( + api_key:api_key_from_args( subcommand.api_key_literal, subcommand.api_key_envvar, subcommand.api_key_file, - ).await?.get(), + ).await?, asset_id:subcommand.asset_id, input_file:subcommand.input_file, }).await, Commands::UploadPlace(subcommand)=>upload_place(UploadPlaceConfig{ - api_key:ApiKey::from_args( + api_key:api_key_from_args( subcommand.api_key_literal, subcommand.api_key_envvar, subcommand.api_key_file, - ).await?.get(), + ).await?, place_id:subcommand.place_id, universe_id:subcommand.universe_id, input_file:subcommand.input_file, @@ -366,22 +366,22 @@ async fn main()->AResult<()>{ input_folder:subcommand.input_folder.unwrap_or_else(||std::env::current_dir().unwrap()), template:subcommand.template, style:subcommand.style.map(|s|s.rox()), - api_key:ApiKey::from_args( + api_key:api_key_from_args( subcommand.api_key_literal, subcommand.api_key_envvar, subcommand.api_key_file, - ).await?.get(), + ).await?, asset_id:subcommand.asset_id, }).await, Commands::CompileUploadPlace(subcommand)=>compile_upload_place(CompileUploadPlaceConfig{ input_folder:subcommand.input_folder.unwrap_or_else(||std::env::current_dir().unwrap()), template:subcommand.template, style:subcommand.style.map(|s|s.rox()), - api_key:ApiKey::from_args( + api_key:api_key_from_args( subcommand.api_key_literal, subcommand.api_key_envvar, subcommand.api_key_file, - ).await?.get(), + ).await?, place_id:subcommand.place_id, universe_id:subcommand.universe_id, }).await, @@ -406,11 +406,11 @@ async fn main()->AResult<()>{ Commands::DownloadAndDecompileHistoryIntoGit(subcommand)=>download_and_decompile_history_into_git(DownloadAndDecompileHistoryConfig{ git_committer_name:subcommand.git_committer_name, git_committer_email:subcommand.git_committer_email, - api_key:ApiKey::from_args( + api_key:api_key_from_args( subcommand.api_key_literal, subcommand.api_key_envvar, subcommand.api_key_file, - ).await?.get(), + ).await?, asset_id:subcommand.asset_id, output_folder:std::env::current_dir()?, style:subcommand.style.rox(), @@ -421,24 +421,18 @@ async fn main()->AResult<()>{ } } -struct ApiKey(String); -impl ApiKey{ - fn get(self)->String{ - self.0 - } - async fn from_args(literal:Option,environment:Option,file:Option)->AResult{ - let api_key=match (literal,environment,file){ - (Some(api_key_literal),None,None)=>api_key_literal, - (None,Some(api_key_environment),None)=>std::env::var(api_key_environment)?, - (None,None,Some(api_key_file))=>tokio::fs::read_to_string(api_key_file).await?, - _=>Err(anyhow::Error::msg("Illegal api key argument triple"))?, - }; - Ok(Self(api_key)) - } +async fn api_key_from_args(literal:Option,environment:Option,file:Option)->AResult{ + let api_key=match (literal,environment,file){ + (Some(api_key_literal),None,None)=>api_key_literal, + (None,Some(api_key_environment),None)=>std::env::var(api_key_environment)?, + (None,None,Some(api_key_file))=>tokio::fs::read_to_string(api_key_file).await?, + _=>Err(anyhow::Error::msg("Illegal api key argument triple"))?, + }; + Ok(ApiKey::new(api_key)) } struct CreateConfig{ - api_key:String, + api_key:ApiKey, model_name:String, description:String, input_file:PathBuf, @@ -448,7 +442,7 @@ struct CreateConfig{ ///This is hardcoded to create models atm async fn create(config:CreateConfig)->AResult<()>{ - let resp=RobloxContext::new(config.api_key) + let resp=CloudContext::new(config.api_key) .create_asset(rbx_asset::context::CreateAssetRequest{ assetType:rbx_asset::context::AssetType::Model, displayName:config.model_name, @@ -466,12 +460,12 @@ async fn create(config:CreateConfig)->AResult<()>{ } struct UploadAssetConfig{ - api_key:String, + api_key:ApiKey, asset_id:u64, input_file:PathBuf, } async fn upload_asset(config:UploadAssetConfig)->AResult<()>{ - let context=RobloxContext::new(config.api_key); + let context=CloudContext::new(config.api_key); let resp=context.update_asset(rbx_asset::cloud::UpdateAssetRequest{ assetId:config.asset_id, displayName:None, @@ -482,13 +476,13 @@ async fn upload_asset(config:UploadAssetConfig)->AResult<()>{ } struct UploadPlaceConfig{ - api_key:String, + api_key:ApiKey, place_id:u64, universe_id:u64, input_file:PathBuf, } async fn upload_place(config:UploadPlaceConfig)->AResult<()>{ - let context=RobloxContext::new(config.api_key); + let context=CloudContext::new(config.api_key); context.update_place(rbx_asset::context::UpdatePlaceRequest{ placeId:config.place_id, universeId:config.universe_id, @@ -496,8 +490,8 @@ async fn upload_place(config:UploadPlaceConfig)->AResult<()>{ Ok(()) } -async fn download_list(api_key:String,asset_id_file_map:AssetIDFileMap)->AResult<()>{ - let context=RobloxContext::new(api_key); +async fn download_list(api_key:ApiKey,asset_id_file_map:AssetIDFileMap)->AResult<()>{ + let context=CloudContext::new(api_key); futures::stream::iter(asset_id_file_map.into_iter() .map(|(asset_id,file)|{ let context=&context; @@ -520,7 +514,7 @@ async fn download_list(api_key:String,asset_id_file_map:AssetIDFileMap)->AResult Ok(()) } -async fn get_inventory_pages(context:&RobloxContext,group:u64)->AResult>{ +async fn get_inventory_pages(context:&CloudContext,group:u64)->AResult>{ let mut cursor:Option=None; let mut asset_list=Vec::new(); loop{ @@ -534,8 +528,8 @@ async fn get_inventory_pages(context:&RobloxContext,group:u64)->AResultAResult<()>{ - let context=RobloxContext::new(api_key); +async fn download_group_inventory_json(api_key:ApiKey,group:u64,output_folder:PathBuf)->AResult<()>{ + let context=CloudContext::new(api_key); let item_list=get_inventory_pages(&context,group).await?; let mut path=output_folder.clone(); @@ -545,7 +539,7 @@ async fn download_group_inventory_json(api_key:String,group:u64,output_folder:Pa Ok(()) } -async fn get_version_history(context:&RobloxContext,asset_id:AssetID)->AResult>{ +async fn get_version_history(context:&CloudContext,asset_id:AssetID)->AResult>{ let mut cursor:Option=None; let mut asset_list=Vec::new(); loop{ @@ -565,7 +559,7 @@ struct DownloadHistoryConfig{ end_version:Option, start_version:u64, output_folder:PathBuf, - api_key:String, + api_key:ApiKey, asset_id:AssetID, } @@ -606,7 +600,7 @@ async fn download_history(mut config:DownloadHistoryConfig)->AResult<()>{ None=>Err(anyhow::Error::msg("Cannot continue from versions.json - there are no previous versions"))?, } } - let context=RobloxContext::new(config.api_key); + let context=CloudContext::new(config.api_key); //limit concurrent downloads let mut join_set=tokio::task::JoinSet::new(); @@ -739,7 +733,7 @@ async fn decompile(config:DecompileConfig)->AResult<()>{ } struct DownloadDecompileConfig{ - api_key:String, + api_key:ApiKey, asset_id:AssetID, style:rox_compiler::Style, output_folder:PathBuf, @@ -749,7 +743,7 @@ struct DownloadDecompileConfig{ } async fn download_decompile(config:DownloadDecompileConfig)->AResult<()>{ - let context=RobloxContext::new(config.api_key); + let context=CloudContext::new(config.api_key); let file=context.get_asset(rbx_asset::context::GetAssetRequest{asset_id:config.asset_id,version:None}).await?; let dom=load_dom(std::io::Cursor::new(file))?; @@ -907,7 +901,7 @@ async fn decompile_history_into_git(config:DecompileHistoryConfig)->AResult<()>{ } struct DownloadAndDecompileHistoryConfig{ - api_key:String, + api_key:ApiKey, asset_id:AssetID, git_committer_name:String, git_committer_email:String, @@ -919,7 +913,7 @@ struct DownloadAndDecompileHistoryConfig{ } async fn download_and_decompile_history_into_git(config:DownloadAndDecompileHistoryConfig)->AResult<()>{ - let context=RobloxContext::new(config.api_key); + let context=CloudContext::new(config.api_key); //poll paged list of all asset versions let asset_list=get_version_history(&context,config.asset_id).await?; @@ -990,7 +984,7 @@ struct CompileUploadAssetConfig{ input_folder:PathBuf, template:Option, style:Option, - api_key:String, + api_key:ApiKey, asset_id:AssetID, } async fn compile_upload_asset(config:CompileUploadAssetConfig)->AResult<()>{ @@ -1010,7 +1004,7 @@ async fn compile_upload_asset(config:CompileUploadAssetConfig)->AResult<()>{ rbx_binary::to_writer(std::io::Cursor::new(&mut data),&dom,dom.root().children())?; //upload it - let context=RobloxContext::new(config.api_key); + let context=CloudContext::new(config.api_key); let resp=context.update_asset(rbx_asset::cloud::UpdateAssetRequest{ assetId:config.asset_id, displayName:None, @@ -1024,7 +1018,7 @@ struct CompileUploadPlaceConfig{ input_folder:PathBuf, template:Option, style:Option, - api_key:String, + api_key:ApiKey, place_id:u64, universe_id:u64, } @@ -1045,7 +1039,7 @@ async fn compile_upload_place(config:CompileUploadPlaceConfig)->AResult<()>{ rbx_binary::to_writer(std::io::Cursor::new(&mut data),&dom,dom.root().children())?; //upload it - let context=RobloxContext::new(config.api_key); + let context=CloudContext::new(config.api_key); context.update_place(rbx_asset::context::UpdatePlaceRequest{ universeId:config.universe_id, placeId:config.place_id,