@@ -18,6 +18,11 @@ pub(crate) enum ArgValidationError {
1818 ) ) ]
1919 AmbiguousCanisterName ,
2020
21+ #[ snafu( transparent) ]
22+ EnvironmentError {
23+ source : crate :: commands:: GetEnvironmentError ,
24+ } ,
25+
2126 #[ snafu( transparent) ]
2227 GetAgentForEnv {
2328 source : crate :: commands:: GetAgentForEnvError ,
@@ -39,21 +44,62 @@ pub(crate) enum ArgValidationError {
3944 } ,
4045}
4146
47+ #[ derive( Args , Debug ) ]
48+ pub ( crate ) struct CanisterEnvironmentArgs {
49+ /// Name or principal of canister to target
50+ /// When using a name an environment must be specified
51+ pub ( crate ) canister : Canister ,
52+
53+ /// Name of the target environment
54+ #[ arg( long) ]
55+ pub ( crate ) environment : Option < Environment > ,
56+ }
57+
4258#[ derive( Args , Debug ) ]
4359pub ( crate ) struct CanisterCommandArgs {
44- /// Name of canister to target
60+ // Note: Could have flattened CanisterEnvironmentArg to avoid adding child field
61+ /// Name or principal of canister to target
62+ /// When using a name an environment must be specified
4563 pub ( crate ) canister : Canister ,
4664
65+ /// Name of the network to target
4766 #[ arg( long) ]
4867 pub ( crate ) network : Option < Network > ,
4968
69+ /// Name of the target environment
5070 #[ arg( long) ]
5171 pub ( crate ) environment : Option < Environment > ,
5272
73+ /// The identity to use for this request
5374 #[ command( flatten) ]
5475 pub ( crate ) identity : IdentityOpt ,
5576}
5677
78+ impl CanisterEnvironmentArgs {
79+ pub async fn get_cid_for_environment (
80+ & self ,
81+ ctx : & Context ,
82+ ) -> Result < Principal , ArgValidationError > {
83+ let arg_canister = self . canister . clone ( ) ;
84+ let arg_environment = self . environment . clone ( ) . unwrap_or_default ( ) ;
85+ let environment_name = arg_environment. name ( ) ;
86+
87+ let principal = match arg_canister {
88+ Canister :: Name ( canister_name) => {
89+ ctx. get_canister_id_for_env ( & canister_name, environment_name)
90+ . await ?
91+ }
92+ Canister :: Principal ( principal) => {
93+ // Make sure a valid environment was requested
94+ let _ = ctx. get_environment ( environment_name) . await ?;
95+ principal
96+ }
97+ } ;
98+
99+ Ok ( principal)
100+ }
101+ }
102+
57103impl CanisterCommandArgs {
58104 pub async fn get_cid_and_agent (
59105 & self ,
@@ -123,6 +169,15 @@ impl From<&str> for Canister {
123169 }
124170}
125171
172+ impl Display for Canister {
173+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
174+ match self {
175+ Canister :: Name ( n) => f. write_str ( n) ,
176+ Canister :: Principal ( principal) => f. write_str ( & principal. to_string ( ) ) ,
177+ }
178+ }
179+ }
180+
126181#[ derive( Clone , Debug , PartialEq ) ]
127182pub ( crate ) enum Network {
128183 Name ( String ) ,
@@ -176,7 +231,11 @@ impl Display for Environment {
176231mod tests {
177232 use candid:: Principal ;
178233
179- use crate :: commands:: args:: { Canister , Network } ;
234+ use super :: * ;
235+ use icp:: MockProjectLoader ;
236+ use std:: sync:: Arc ;
237+
238+ use crate :: { commands:: args:: Environment , store_id:: MockInMemoryIdStore } ;
180239
181240 #[ test]
182241 fn canister_by_name ( ) {
@@ -213,4 +272,49 @@ mod tests {
213272 Network :: Url ( "http://www.example.com" . to_string( ) ) ,
214273 ) ;
215274 }
275+
276+ #[ tokio:: test]
277+ async fn test_get_cid_for_environment ( ) {
278+ use crate :: store_id:: { Access as IdAccess , Key } ;
279+ use candid:: Principal ;
280+
281+ let ids_store = Arc :: new ( MockInMemoryIdStore :: new ( ) ) ;
282+
283+ // Register a canister ID for the dev environment
284+ let canister_id = Principal :: from_text ( "rrkah-fqaaa-aaaaa-aaaaq-cai" ) . unwrap ( ) ;
285+ ids_store
286+ . register (
287+ & Key {
288+ network : "local" . to_string ( ) ,
289+ environment : "dev" . to_string ( ) ,
290+ canister : "backend" . to_string ( ) ,
291+ } ,
292+ & canister_id,
293+ )
294+ . unwrap ( ) ;
295+
296+ let ctx = Context {
297+ project : Arc :: new ( MockProjectLoader :: complex ( ) ) ,
298+ ids : ids_store,
299+ ..Context :: mocked ( )
300+ } ;
301+
302+ let args = CanisterEnvironmentArgs {
303+ canister : Canister :: Name ( "backend" . to_string ( ) ) ,
304+ environment : Some ( Environment :: Name ( "dev" . to_string ( ) ) ) ,
305+ } ;
306+
307+ assert ! ( matches!( args. get_cid_for_environment( & ctx) . await , Ok ( id) if id == canister_id) ) ;
308+
309+ let args = CanisterEnvironmentArgs {
310+ canister : Canister :: Name ( "INVALID" . to_string ( ) ) ,
311+ environment : Some ( Environment :: Name ( "dev" . to_string ( ) ) ) ,
312+ } ;
313+
314+ let res = args. get_cid_for_environment ( & ctx) . await ;
315+ assert ! (
316+ res. is_err( ) ,
317+ "An invalid canister name should result in an error"
318+ ) ;
319+ }
216320}
0 commit comments