Skip to main content

cli/
extensions.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Local glue between cli's dispatch and the `WeftExtensions`
3//! trait surface.
4//!
5//! In OSS builds without the `client` feature, `main.rs` constructs a
6//! `NoopWeftExtensions` from the shim crate directly. With
7//! `client` enabled, this module provides
8//! [`EnabledWeftExtensions`], which downcasts the trait's opaque
9//! arguments back to `cli::cli::AuthCommands` and delegates to the
10//! CLI-owned hosted runtime. Closed builds can continue replacing the shim
11//! package through `[patch.crates-io]` without owning Heddle's transport.
12
13#![cfg(feature = "client")]
14
15use std::any::Any;
16
17use anyhow::{Result, anyhow};
18use async_trait::async_trait;
19use weft_client_shim::{CliContext, WeftExtensions};
20
21use crate::{
22    cli::{AgentTemplateArg, AuthCommands, AuthTrustCommands, commands::cmd_auth},
23    hosted_runtime::{
24        auth_requests::{AuthCommand, AuthTrustCommand},
25        device_flow::AgentTemplate,
26    },
27};
28
29pub struct EnabledWeftExtensions;
30
31#[async_trait]
32impl WeftExtensions for EnabledWeftExtensions {
33    async fn auth(
34        &self,
35        ctx: &(dyn CliContext + 'static),
36        command: &(dyn Any + Send + Sync),
37    ) -> Result<()> {
38        let command = downcast::<AuthCommands>(command, "AuthCommands")?;
39        cmd_auth(ctx, auth_command(command.clone())).await
40    }
41
42    async fn whoami(&self, ctx: &(dyn CliContext + 'static), server: Option<String>) -> Result<()> {
43        crate::hosted_runtime::whoami::cmd_whoami(ctx, server).await
44    }
45}
46
47fn auth_command(command: AuthCommands) -> AuthCommand {
48    match command {
49        AuthCommands::Login {
50            server,
51            open_browser,
52            credential,
53        } => AuthCommand::Login {
54            server,
55            open_browser,
56            credential,
57        },
58        AuthCommands::Logout { server } => AuthCommand::Logout { server },
59        AuthCommands::Status { server } => AuthCommand::Status { server },
60        AuthCommands::Trust { command } => AuthCommand::Trust {
61            command: match command {
62                AuthTrustCommands::Show(args) => AuthTrustCommand::Show {
63                    server: args.server,
64                },
65                AuthTrustCommands::Replace(args) => AuthTrustCommand::Replace {
66                    server: args.server,
67                    expected_current_public_key: args.expect_current_public_key,
68                    key_id: args.key_id,
69                    public_key: args.public_key,
70                },
71            },
72        },
73        AuthCommands::DeriveAgent {
74            server,
75            agent_id,
76            ttl_secs,
77            scopes,
78            allowed_operations,
79            template,
80            out,
81        } => AuthCommand::DeriveAgent {
82            server,
83            agent_id,
84            ttl_secs,
85            scopes,
86            allowed_operations,
87            template: template.map(agent_template),
88            out,
89        },
90        AuthCommands::CreateServiceToken {
91            name,
92            namespace,
93            server,
94            out,
95        } => AuthCommand::CreateServiceToken {
96            name,
97            namespace,
98            server,
99            out,
100        },
101    }
102}
103
104fn agent_template(template: AgentTemplateArg) -> AgentTemplate {
105    match template {
106        AgentTemplateArg::Reviewer => AgentTemplate::Reviewer,
107        AgentTemplateArg::Contributor => AgentTemplate::Contributor,
108        AgentTemplateArg::CiLanding => AgentTemplate::CiLanding,
109    }
110}
111
112fn downcast<'a, T: 'static>(
113    value: &'a (dyn Any + Send + Sync),
114    name: &'static str,
115) -> Result<&'a T> {
116    value
117        .downcast_ref::<T>()
118        .ok_or_else(|| anyhow!("WeftExtensions trait dispatch: failed to downcast to {name}"))
119}