mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-24 02:38:06 +02:00
feat(cli): alias subcommand; remove --alias flag (RFC-011 D4) (#244)
Operator aliases move from the --alias flag on query/mutate to a dedicated 'omnigraph alias <name> [args]' subcommand, so an alias can never shadow or be shadowed by a built-in verb. Unknown name errors listing defined aliases. Removes the legacy alias machinery from query/mutate (net -156 lines); legacy omnigraph.yaml aliases lose their CLI entry point.
This commit is contained in:
parent
2ed05d2cb1
commit
b395757e21
9 changed files with 128 additions and 379 deletions
|
|
@ -99,12 +99,10 @@ pub(crate) enum Command {
|
|||
legacy_uri: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long, conflicts_with_all = ["query", "query_string"])]
|
||||
alias: Option<String>,
|
||||
#[arg(long, conflicts_with_all = ["alias", "query_string"])]
|
||||
#[arg(long, conflicts_with = "query_string")]
|
||||
query: Option<PathBuf>,
|
||||
/// Inline GQ source — alternative to `--query <path>` and `--alias <name>`.
|
||||
#[arg(short = 'e', long = "query-string", value_name = "GQ", conflicts_with_all = ["query", "alias"])]
|
||||
/// Inline GQ source — alternative to `--query <path>`.
|
||||
#[arg(short = 'e', long = "query-string", value_name = "GQ", conflicts_with = "query")]
|
||||
query_string: Option<String>,
|
||||
#[arg(long)]
|
||||
name: Option<String>,
|
||||
|
|
@ -118,8 +116,6 @@ pub(crate) enum Command {
|
|||
format: Option<ReadOutputFormat>,
|
||||
#[arg(long, conflicts_with = "format")]
|
||||
json: bool,
|
||||
#[arg()]
|
||||
alias_args: Vec<String>,
|
||||
},
|
||||
/// Execute a graph mutation query against a branch.
|
||||
///
|
||||
|
|
@ -135,12 +131,10 @@ pub(crate) enum Command {
|
|||
legacy_uri: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long, conflicts_with_all = ["query", "query_string"])]
|
||||
alias: Option<String>,
|
||||
#[arg(long, conflicts_with_all = ["alias", "query_string"])]
|
||||
#[arg(long, conflicts_with = "query_string")]
|
||||
query: Option<PathBuf>,
|
||||
/// Inline GQ source — alternative to `--query <path>` and `--alias <name>`.
|
||||
#[arg(short = 'e', long = "query-string", value_name = "GQ", conflicts_with_all = ["query", "alias"])]
|
||||
/// Inline GQ source — alternative to `--query <path>`.
|
||||
#[arg(short = 'e', long = "query-string", value_name = "GQ", conflicts_with = "query")]
|
||||
query_string: Option<String>,
|
||||
#[arg(long)]
|
||||
name: Option<String>,
|
||||
|
|
@ -150,8 +144,28 @@ pub(crate) enum Command {
|
|||
branch: Option<String>,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
#[arg()]
|
||||
alias_args: Vec<String>,
|
||||
},
|
||||
/// Invoke an operator alias (RFC-011 Decision 4).
|
||||
///
|
||||
/// An alias is a personal binding under `aliases:` in
|
||||
/// ~/.omnigraph/config.yaml — name → (server, graph, stored-query name,
|
||||
/// default params). `omnigraph alias <name> [args]` invokes the bound
|
||||
/// stored query on its server. Living in its own namespace, an alias can
|
||||
/// never shadow or be shadowed by a built-in verb. Replaces the removed
|
||||
/// `--alias` flag on `query`/`mutate`.
|
||||
Alias {
|
||||
/// Alias name (a key under `aliases:` in ~/.omnigraph/config.yaml).
|
||||
name: String,
|
||||
/// Positional args bound to the alias's declared `args` params, in order.
|
||||
args: Vec<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[command(flatten)]
|
||||
params: ParamsArgs,
|
||||
#[arg(long, conflicts_with = "json")]
|
||||
format: Option<ReadOutputFormat>,
|
||||
#[arg(long, conflicts_with = "format")]
|
||||
json: bool,
|
||||
},
|
||||
/// Load data into a graph (local or remote)
|
||||
Load {
|
||||
|
|
|
|||
|
|
@ -729,44 +729,6 @@ pub(crate) fn parse_alias_value(value: &str) -> Value {
|
|||
serde_json::from_str(value).unwrap_or_else(|_| Value::String(value.to_string()))
|
||||
}
|
||||
|
||||
pub(crate) fn merged_params_json(
|
||||
alias_name: Option<&str>,
|
||||
alias_arg_names: &[String],
|
||||
alias_arg_values: &[String],
|
||||
explicit: Option<Value>,
|
||||
) -> Result<Option<Value>> {
|
||||
if alias_arg_values.len() > alias_arg_names.len() {
|
||||
let alias = alias_name.unwrap_or("<alias>");
|
||||
bail!(
|
||||
"alias '{}' expects at most {} args but got {}",
|
||||
alias,
|
||||
alias_arg_names.len(),
|
||||
alias_arg_values.len()
|
||||
);
|
||||
}
|
||||
|
||||
let mut merged = serde_json::Map::new();
|
||||
for (arg_name, arg_value) in alias_arg_names.iter().zip(alias_arg_values.iter()) {
|
||||
merged.insert(arg_name.clone(), parse_alias_value(arg_value));
|
||||
}
|
||||
|
||||
match explicit {
|
||||
Some(Value::Object(object)) => {
|
||||
for (key, value) in object {
|
||||
merged.insert(key, value);
|
||||
}
|
||||
}
|
||||
Some(_) => bail!("params JSON must be an object"),
|
||||
None => {}
|
||||
}
|
||||
|
||||
if merged.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(Value::Object(merged)))
|
||||
}
|
||||
}
|
||||
|
||||
/// The format cascade (RFC-007 §D3): `--json` > `--format` > alias format >
|
||||
/// legacy `cli.output_format` (RFC-008 window) > operator `defaults.output`
|
||||
/// > table.
|
||||
|
|
@ -790,43 +752,6 @@ pub(crate) fn resolve_read_format(
|
|||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_alias<'a>(
|
||||
config: &'a OmnigraphConfig,
|
||||
alias_name: Option<&'a str>,
|
||||
expected: AliasCommand,
|
||||
) -> Result<Option<(&'a str, &'a omnigraph_server::AliasConfig)>> {
|
||||
let Some(alias_name) = alias_name else {
|
||||
return Ok(None);
|
||||
};
|
||||
let alias = config.alias(alias_name)?;
|
||||
if alias.command != expected {
|
||||
bail!(
|
||||
"alias '{}' is a {:?} alias, not a {:?} alias",
|
||||
alias_name,
|
||||
alias.command,
|
||||
expected
|
||||
);
|
||||
}
|
||||
Ok(Some((alias_name, alias)))
|
||||
}
|
||||
|
||||
pub(crate) fn normalize_legacy_alias_uri(
|
||||
uri: Option<String>,
|
||||
target_available: bool,
|
||||
alias_name: Option<&str>,
|
||||
mut alias_args: Vec<String>,
|
||||
) -> (Option<String>, Vec<String>) {
|
||||
let Some(candidate) = uri else {
|
||||
return (None, alias_args);
|
||||
};
|
||||
|
||||
if alias_name.is_some() && target_available {
|
||||
alias_args.insert(0, candidate);
|
||||
return (None, alias_args);
|
||||
}
|
||||
|
||||
(Some(candidate), alias_args)
|
||||
}
|
||||
|
||||
|
||||
pub(crate) fn read_target_from_cli(branch: Option<String>, snapshot: Option<String>) -> ReadTarget {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ use omnigraph_api_types::{
|
|||
};
|
||||
use omnigraph_server::queries::{QueryRegistry, check, format_check_breakages};
|
||||
use omnigraph_server::{
|
||||
AliasCommand, OmnigraphConfig, PolicyAction, PolicyDecision, PolicyEngine, PolicyRequest,
|
||||
OmnigraphConfig, PolicyAction, PolicyDecision, PolicyEngine, PolicyRequest,
|
||||
PolicyTestConfig, ReadOutputFormat, graph_resource_id_for_selection, load_config,
|
||||
};
|
||||
use reqwest::Method;
|
||||
|
|
@ -569,7 +569,6 @@ async fn main() -> Result<()> {
|
|||
uri,
|
||||
legacy_uri,
|
||||
config,
|
||||
alias,
|
||||
query,
|
||||
query_string,
|
||||
name,
|
||||
|
|
@ -578,182 +577,61 @@ async fn main() -> Result<()> {
|
|||
snapshot,
|
||||
format,
|
||||
json,
|
||||
alias_args,
|
||||
} => {
|
||||
if alias.is_none() && query.is_none() && query_string.is_none() {
|
||||
bail!("exactly one of --query, --query-string, or --alias must be provided");
|
||||
if query.is_none() && query_string.is_none() {
|
||||
bail!("provide a query: --query <file> or -e '<inline gq>'");
|
||||
}
|
||||
|
||||
let config = load_cli_config(config.as_ref())?;
|
||||
// Operator aliases (RFC-007 PR 3): pure bindings to stored
|
||||
// queries. A legacy file-alias with the same name wins during
|
||||
// the RFC-008 window (with a warning); an alias name found
|
||||
// only in the operator layer takes the invoke path here.
|
||||
if let Some(alias_name) = alias.as_deref() {
|
||||
let operator_config = crate::operator::load_operator_config()?;
|
||||
if let Some(operator_alias) = operator_config.aliases.get(alias_name) {
|
||||
if config.alias(alias_name).is_ok() {
|
||||
eprintln!(
|
||||
"warning: alias '{alias_name}' is defined in both omnigraph.yaml (legacy, wins during the deprecation window) and the operator config; the legacy definition applies"
|
||||
);
|
||||
} else {
|
||||
// The hidden legacy-uri positional swallows the first
|
||||
// bare arg; an operator alias always knows its target,
|
||||
// so reclaim it as the first positional param.
|
||||
let (_, alias_args) = normalize_legacy_alias_uri(
|
||||
legacy_uri.clone(),
|
||||
true,
|
||||
Some(alias_name),
|
||||
alias_args.clone(),
|
||||
);
|
||||
let output = execute_operator_alias(
|
||||
&http_client,
|
||||
&config,
|
||||
alias_name,
|
||||
operator_alias,
|
||||
&alias_args,
|
||||
load_params_json(¶ms)?,
|
||||
)
|
||||
.await?;
|
||||
let format =
|
||||
resolve_read_format(&config, format, json, operator_alias.format);
|
||||
print_read_output(&output, format, &config)?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
let alias = resolve_alias(&config, alias.as_deref(), AliasCommand::Read)?;
|
||||
let alias_name = alias.as_ref().map(|(name, _)| *name);
|
||||
let alias_config = alias.as_ref().map(|(_, alias)| *alias);
|
||||
let alias_graph = alias_config.and_then(|alias| alias.graph.as_deref());
|
||||
let target_available = alias_graph.is_some() || config.cli_graph_name().is_some();
|
||||
let (legacy_uri, alias_args) =
|
||||
normalize_legacy_alias_uri(legacy_uri, target_available, alias_name, alias_args);
|
||||
// `--target` is gone; resolve an alias's legacy `graph` name to its
|
||||
// URI (a positional URI still wins).
|
||||
let uri = match uri.or(legacy_uri) {
|
||||
Some(uri) => Some(uri),
|
||||
None => match alias_graph {
|
||||
Some(name) => Some(config.resolve_target_uri(None, Some(name), None)?),
|
||||
None => None,
|
||||
},
|
||||
};
|
||||
let client = client::GraphClient::resolve(
|
||||
&config,
|
||||
cli.server.as_deref(),
|
||||
cli.graph.as_deref(),
|
||||
uri,
|
||||
uri.or(legacy_uri),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let query_source = resolve_query_source(
|
||||
&config,
|
||||
query.as_ref(),
|
||||
query_string.as_deref(),
|
||||
alias_config.map(|a| a.query.as_str()),
|
||||
)?;
|
||||
let params_json = merged_params_json(
|
||||
alias_name,
|
||||
alias_config
|
||||
.map(|alias| alias.args.as_slice())
|
||||
.unwrap_or(&[]),
|
||||
&alias_args,
|
||||
load_params_json(¶ms)?,
|
||||
)?;
|
||||
let target = resolve_read_target(
|
||||
&config,
|
||||
branch,
|
||||
snapshot,
|
||||
alias_config.and_then(|alias| alias.branch.clone()),
|
||||
)?;
|
||||
let query_name = name.or_else(|| alias_config.and_then(|alias| alias.name.clone()));
|
||||
let query_source =
|
||||
resolve_query_source(&config, query.as_ref(), query_string.as_deref(), None)?;
|
||||
let params_json = load_params_json(¶ms)?;
|
||||
let target = resolve_read_target(&config, branch, snapshot, None)?;
|
||||
let output = client
|
||||
.query(
|
||||
target,
|
||||
&query_source,
|
||||
query_name.as_deref(),
|
||||
params_json.as_ref(),
|
||||
)
|
||||
.query(target, &query_source, name.as_deref(), params_json.as_ref())
|
||||
.await?;
|
||||
let format = resolve_read_format(
|
||||
&config,
|
||||
format,
|
||||
json,
|
||||
alias_config.and_then(|alias| alias.format),
|
||||
);
|
||||
let format = resolve_read_format(&config, format, json, None);
|
||||
print_read_output(&output, format, &config)?;
|
||||
}
|
||||
Command::Mutate {
|
||||
uri,
|
||||
legacy_uri,
|
||||
config,
|
||||
alias,
|
||||
query,
|
||||
query_string,
|
||||
name,
|
||||
params,
|
||||
branch,
|
||||
json,
|
||||
alias_args,
|
||||
} => {
|
||||
if alias.is_none() && query.is_none() && query_string.is_none() {
|
||||
bail!("exactly one of --query, --query-string, or --alias must be provided");
|
||||
if query.is_none() && query_string.is_none() {
|
||||
bail!("provide a mutation query: --query <file> or -e '<inline gq>'");
|
||||
}
|
||||
|
||||
let config = load_cli_config(config.as_ref())?;
|
||||
let alias = resolve_alias(&config, alias.as_deref(), AliasCommand::Change)?;
|
||||
let alias_name = alias.as_ref().map(|(name, _)| *name);
|
||||
let alias_config = alias.as_ref().map(|(_, alias)| *alias);
|
||||
let alias_graph = alias_config.and_then(|alias| alias.graph.as_deref());
|
||||
let target_available = alias_graph.is_some() || config.cli_graph_name().is_some();
|
||||
let (legacy_uri, alias_args) =
|
||||
normalize_legacy_alias_uri(legacy_uri, target_available, alias_name, alias_args);
|
||||
// `--target` is gone; resolve an alias's legacy `graph` name to its
|
||||
// URI (a positional URI still wins).
|
||||
let uri = match uri.or(legacy_uri) {
|
||||
Some(uri) => Some(uri),
|
||||
None => match alias_graph {
|
||||
Some(name) => Some(config.resolve_target_uri(None, Some(name), None)?),
|
||||
None => None,
|
||||
},
|
||||
};
|
||||
let client = client::GraphClient::resolve_with_policy(
|
||||
&config,
|
||||
cli.server.as_deref(),
|
||||
cli.graph.as_deref(),
|
||||
uri,
|
||||
uri.or(legacy_uri),
|
||||
cli.as_actor.as_deref(),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let query_source = resolve_query_source(
|
||||
&config,
|
||||
query.as_ref(),
|
||||
query_string.as_deref(),
|
||||
alias_config.map(|a| a.query.as_str()),
|
||||
)?;
|
||||
let params_json = merged_params_json(
|
||||
alias_name,
|
||||
alias_config
|
||||
.map(|alias| alias.args.as_slice())
|
||||
.unwrap_or(&[]),
|
||||
&alias_args,
|
||||
load_params_json(¶ms)?,
|
||||
)?;
|
||||
let branch = resolve_branch(
|
||||
&config,
|
||||
branch,
|
||||
alias_config.and_then(|alias| alias.branch.clone()),
|
||||
"main",
|
||||
);
|
||||
let query_name = name.or_else(|| alias_config.and_then(|alias| alias.name.clone()));
|
||||
let query_source =
|
||||
resolve_query_source(&config, query.as_ref(), query_string.as_deref(), None)?;
|
||||
let params_json = load_params_json(¶ms)?;
|
||||
let branch = resolve_branch(&config, branch, None, "main");
|
||||
let output = client
|
||||
.mutate(
|
||||
&branch,
|
||||
&query_source,
|
||||
query_name.as_deref(),
|
||||
params_json.as_ref(),
|
||||
)
|
||||
.mutate(&branch, &query_source, name.as_deref(), params_json.as_ref())
|
||||
.await?;
|
||||
if json {
|
||||
print_json(&output)?;
|
||||
|
|
@ -761,6 +639,37 @@ async fn main() -> Result<()> {
|
|||
print_change_human(&output);
|
||||
}
|
||||
}
|
||||
Command::Alias {
|
||||
name,
|
||||
args,
|
||||
config,
|
||||
params,
|
||||
format,
|
||||
json,
|
||||
} => {
|
||||
let config = load_cli_config(config.as_ref())?;
|
||||
let operator_config = crate::operator::load_operator_config()?;
|
||||
let Some(operator_alias) = operator_config.aliases.get(&name) else {
|
||||
let defined: Vec<&str> =
|
||||
operator_config.aliases.keys().map(String::as_str).collect();
|
||||
bail!(
|
||||
"unknown alias '{name}'; defined aliases: [{}] \
|
||||
(add it under `aliases:` in ~/.omnigraph/config.yaml)",
|
||||
defined.join(", ")
|
||||
);
|
||||
};
|
||||
let output = execute_operator_alias(
|
||||
&http_client,
|
||||
&config,
|
||||
&name,
|
||||
operator_alias,
|
||||
&args,
|
||||
load_params_json(¶ms)?,
|
||||
)
|
||||
.await?;
|
||||
let format = resolve_read_format(&config, format, json, operator_alias.format);
|
||||
print_read_output(&output, format, &config)?;
|
||||
}
|
||||
Command::Policy { command } => match command {
|
||||
PolicyCommand::Validate { config } => {
|
||||
let config = load_cli_config(config.as_ref())?;
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ pub(crate) fn command_plane(cmd: &Command) -> Plane {
|
|||
match cmd {
|
||||
Command::Query { .. }
|
||||
| Command::Mutate { .. }
|
||||
| Command::Alias { .. }
|
||||
| Command::Load { .. }
|
||||
| Command::Ingest { .. }
|
||||
| Command::Branch { .. }
|
||||
|
|
@ -168,6 +169,7 @@ pub(crate) fn command_label(cmd: &Command) -> &'static str {
|
|||
Command::Commit { .. } => "commit",
|
||||
Command::Query { .. } => "query",
|
||||
Command::Mutate { .. } => "mutate",
|
||||
Command::Alias { .. } => "alias",
|
||||
Command::Policy { .. } => "policy",
|
||||
Command::Optimize { .. } => "optimize",
|
||||
Command::Repair { .. } => "repair",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue