mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-12 01:45:14 +02:00
Verbatim moves: the clap surface (every command/subcommand/arg struct) to cli.rs, resolution helpers (config/actor/graph/branch/query, remote HTTP, env/token, scaffolding) to helpers.rs, human/JSON formatting to output.rs, the in-source test mod to main_tests.rs via #[path]. main.rs (1,184 lines) keeps main() and the dispatch match. Visibility bumps only; 22 binary tests green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
830 lines
25 KiB
Rust
830 lines
25 KiB
Rust
//! Human/JSON output formatting for every command (moved verbatim from
|
|
//! main.rs in the modularization).
|
|
|
|
use super::*;
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub(crate) struct LoadOutput {
|
|
pub(crate) uri: String,
|
|
pub(crate) branch: String,
|
|
pub(crate) mode: &'static str,
|
|
/// Present only when `--from` was given; echoes the requested base.
|
|
#[serde(skip_serializing_if = "Option::is_none")]
|
|
pub(crate) base_branch: Option<String>,
|
|
pub(crate) branch_created: bool,
|
|
pub(crate) nodes_loaded: usize,
|
|
pub(crate) edges_loaded: usize,
|
|
pub(crate) node_types_loaded: usize,
|
|
pub(crate) edge_types_loaded: usize,
|
|
}
|
|
|
|
pub(crate) fn load_output_from_tables(
|
|
uri: &str,
|
|
branch: &str,
|
|
mode: CliLoadMode,
|
|
output: &IngestOutput,
|
|
) -> LoadOutput {
|
|
let mut nodes_loaded = 0;
|
|
let mut edges_loaded = 0;
|
|
let mut node_types_loaded = 0;
|
|
let mut edge_types_loaded = 0;
|
|
for table in &output.tables {
|
|
if table.table_key.starts_with("node:") {
|
|
nodes_loaded += table.rows_loaded;
|
|
node_types_loaded += 1;
|
|
} else if table.table_key.starts_with("edge:") {
|
|
edges_loaded += table.rows_loaded;
|
|
edge_types_loaded += 1;
|
|
}
|
|
}
|
|
LoadOutput {
|
|
uri: uri.to_string(),
|
|
branch: branch.to_string(),
|
|
mode: mode.as_str(),
|
|
base_branch: output.base_branch.clone(),
|
|
branch_created: output.branch_created,
|
|
nodes_loaded,
|
|
edges_loaded,
|
|
node_types_loaded,
|
|
edge_types_loaded,
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize)]
|
|
pub(crate) struct SchemaPlanOutput<'a> {
|
|
pub(crate) uri: &'a str,
|
|
pub(crate) supported: bool,
|
|
pub(crate) step_count: usize,
|
|
pub(crate) steps: &'a [SchemaMigrationStep],
|
|
}
|
|
|
|
pub(crate) fn print_schema_apply_human(output: &SchemaApplyOutput) {
|
|
println!("schema apply for {}", output.uri);
|
|
println!("supported: {}", if output.supported { "yes" } else { "no" });
|
|
println!("applied: {}", if output.applied { "yes" } else { "no" });
|
|
println!("manifest_version: {}", output.manifest_version);
|
|
if output.steps.is_empty() {
|
|
println!("no schema changes");
|
|
return;
|
|
}
|
|
for step in &output.steps {
|
|
println!("- {}", render_schema_plan_step(step));
|
|
}
|
|
}
|
|
|
|
pub(crate) fn query_kind_label(kind: QueryLintQueryKind) -> &'static str {
|
|
match kind {
|
|
QueryLintQueryKind::Read => "read",
|
|
QueryLintQueryKind::Mutation => "mutation",
|
|
}
|
|
}
|
|
|
|
pub(crate) fn severity_label(severity: QueryLintSeverity) -> &'static str {
|
|
match severity {
|
|
QueryLintSeverity::Error => "ERROR",
|
|
QueryLintSeverity::Warning => "WARN ",
|
|
QueryLintSeverity::Info => "INFO ",
|
|
}
|
|
}
|
|
|
|
pub(crate) fn print_query_lint_human(output: &QueryLintOutput) {
|
|
for result in &output.results {
|
|
match result.status {
|
|
QueryLintStatus::Ok => {
|
|
println!(
|
|
"OK query `{}` ({})",
|
|
result.name,
|
|
query_kind_label(result.kind)
|
|
);
|
|
}
|
|
QueryLintStatus::Error => {
|
|
println!(
|
|
"ERROR query `{}`: {}",
|
|
result.name,
|
|
result.error.as_deref().unwrap_or("unknown error")
|
|
);
|
|
}
|
|
}
|
|
|
|
for warning in &result.warnings {
|
|
println!("WARN query `{}`: {}", result.name, warning);
|
|
}
|
|
}
|
|
|
|
for finding in &output.findings {
|
|
println!("{} {}", severity_label(finding.severity), finding.message);
|
|
}
|
|
|
|
println!(
|
|
"INFO Lint complete: {} queries processed ({} error(s), {} warning(s), {} info item(s))",
|
|
output.queries_processed, output.errors, output.warnings, output.infos
|
|
);
|
|
}
|
|
|
|
pub(crate) fn finish_query_lint(output: &QueryLintOutput, json: bool) -> Result<()> {
|
|
if json {
|
|
print_json(output)?;
|
|
} else {
|
|
print_query_lint_human(output);
|
|
}
|
|
|
|
if output.status == QueryLintStatus::Error {
|
|
io::stdout().flush()?;
|
|
std::process::exit(1);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn print_json<T: Serialize>(value: &T) -> Result<()> {
|
|
println!("{}", serde_json::to_string_pretty(value)?);
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn print_cluster_validate_human(output: &ValidateOutput) {
|
|
if output.ok {
|
|
println!(
|
|
"cluster config valid: {} resource(s), {} dependency edge(s)",
|
|
output.resources.len(),
|
|
output.dependencies.len()
|
|
);
|
|
} else {
|
|
println!("cluster config invalid");
|
|
}
|
|
print_cluster_diagnostics(&output.diagnostics);
|
|
}
|
|
|
|
pub(crate) fn print_cluster_plan_human(output: &PlanOutput) {
|
|
if output.ok {
|
|
println!(
|
|
"cluster plan: {} change(s), {} approval gate(s)",
|
|
output.changes.len(),
|
|
output.approvals_required.len()
|
|
);
|
|
for change in &output.changes {
|
|
let bindings = if change.binding_change { " [bindings]" } else { "" };
|
|
println!(" {:?} {}{bindings}", change.operation, change.resource);
|
|
if let Some(migration) = &change.migration {
|
|
if !migration.supported {
|
|
println!(" migration UNSUPPORTED:");
|
|
}
|
|
for step in &migration.steps {
|
|
println!(
|
|
" {}",
|
|
serde_json::to_string(step).unwrap_or_else(|_| format!("{step:?}"))
|
|
);
|
|
}
|
|
}
|
|
}
|
|
if output.changes.is_empty() {
|
|
println!(" no changes");
|
|
}
|
|
} else {
|
|
println!("cluster plan failed");
|
|
}
|
|
print_cluster_diagnostics(&output.diagnostics);
|
|
}
|
|
|
|
pub(crate) fn print_cluster_apply_human(output: &ApplyOutput) {
|
|
if output.ok {
|
|
println!(
|
|
"cluster apply: {} applied, {} deferred/blocked",
|
|
output.applied_count, output.deferred_count
|
|
);
|
|
} else {
|
|
println!("cluster apply failed");
|
|
}
|
|
// The change list prints on failure too: an operator debugging a partial
|
|
// apply (payload or state-write error) needs to see what was attempted.
|
|
print_cluster_apply_changes(&output.changes);
|
|
if output.ok {
|
|
let state = &output.state_observations;
|
|
println!(
|
|
" state: revision {}, converged: {}, written: {}",
|
|
state.state_revision, output.converged, output.state_written
|
|
);
|
|
println!(" note: cluster-booted servers (--cluster) serve this on their next restart; omnigraph.yaml deployments are unaffected");
|
|
}
|
|
print_cluster_diagnostics(&output.diagnostics);
|
|
}
|
|
|
|
pub(crate) fn print_cluster_apply_changes(changes: &[omnigraph_cluster::PlanChange]) {
|
|
for change in changes {
|
|
let bindings = if change.binding_change { " [bindings]" } else { "" };
|
|
match (&change.disposition, change.reason.as_deref()) {
|
|
(Some(disposition), Some(reason)) => println!(
|
|
" {:?} {}{bindings} [{disposition:?}: {reason}]",
|
|
change.operation, change.resource
|
|
),
|
|
(Some(disposition), None) => println!(
|
|
" {:?} {}{bindings} [{disposition:?}]",
|
|
change.operation, change.resource
|
|
),
|
|
_ => println!(" {:?} {}{bindings}", change.operation, change.resource),
|
|
}
|
|
}
|
|
if changes.is_empty() {
|
|
println!(" no changes");
|
|
}
|
|
}
|
|
|
|
pub(crate) fn print_cluster_status_human(output: &StatusOutput) {
|
|
if output.ok {
|
|
let state = &output.state_observations;
|
|
if state.state_found {
|
|
println!(
|
|
"cluster state: revision {}, {} resource(s)",
|
|
state.state_revision, state.resource_count
|
|
);
|
|
if let Some(digest) = state.applied_config_digest.as_deref() {
|
|
println!(" applied config: {digest}");
|
|
}
|
|
if state.locked {
|
|
println!(" lock: held{}", cluster_lock_summary(state));
|
|
} else {
|
|
println!(" lock: not held");
|
|
}
|
|
} else {
|
|
println!("cluster state missing");
|
|
}
|
|
} else {
|
|
println!("cluster status failed");
|
|
}
|
|
print_cluster_diagnostics(&output.diagnostics);
|
|
}
|
|
|
|
pub(crate) fn print_cluster_state_sync_human(output: &StateSyncOutput) {
|
|
let operation = match output.operation {
|
|
omnigraph_cluster::StateSyncOperation::Refresh => "refresh",
|
|
omnigraph_cluster::StateSyncOperation::Import => "import",
|
|
};
|
|
if output.ok {
|
|
let state = &output.state_observations;
|
|
println!(
|
|
"cluster {operation}: revision {}, {} resource(s)",
|
|
state.state_revision, state.resource_count
|
|
);
|
|
if let Some(cas) = state.state_cas.as_deref() {
|
|
println!(" state_cas: {cas}");
|
|
}
|
|
if state.locked {
|
|
println!(" lock: acquired{}", cluster_lock_summary(state));
|
|
} else {
|
|
println!(" lock: not acquired");
|
|
}
|
|
} else {
|
|
println!("cluster {operation} failed");
|
|
}
|
|
print_cluster_diagnostics(&output.diagnostics);
|
|
}
|
|
|
|
pub(crate) fn print_cluster_force_unlock_human(output: &ForceUnlockOutput) {
|
|
if output.ok {
|
|
if output.lock_removed {
|
|
println!(
|
|
"cluster force-unlock: removed lock{}",
|
|
cluster_lock_summary(&output.state_observations)
|
|
);
|
|
} else {
|
|
println!("cluster force-unlock: no lock removed");
|
|
}
|
|
} else {
|
|
println!("cluster force-unlock failed");
|
|
if output.state_observations.locked {
|
|
println!(
|
|
" lock: held{}",
|
|
cluster_lock_summary(&output.state_observations)
|
|
);
|
|
}
|
|
}
|
|
print_cluster_diagnostics(&output.diagnostics);
|
|
}
|
|
|
|
pub(crate) fn cluster_lock_summary(state: &omnigraph_cluster::StateObservations) -> String {
|
|
let Some(lock_id) = state.lock_id.as_deref() else {
|
|
return String::new();
|
|
};
|
|
let mut parts = vec![format!("id={lock_id}")];
|
|
if let Some(operation) = state.lock_operation.as_deref() {
|
|
parts.push(format!("operation={operation}"));
|
|
}
|
|
if let Some(pid) = state.lock_pid {
|
|
parts.push(format!("pid={pid}"));
|
|
}
|
|
if let Some(created_at) = state.lock_created_at.as_deref() {
|
|
parts.push(format!("created_at={created_at}"));
|
|
}
|
|
if let Some(age_seconds) = state.lock_age_seconds {
|
|
parts.push(format!("age_seconds={age_seconds}"));
|
|
}
|
|
format!(" ({})", parts.join(", "))
|
|
}
|
|
|
|
pub(crate) fn print_cluster_diagnostics(diagnostics: &[omnigraph_cluster::Diagnostic]) {
|
|
for diagnostic in diagnostics {
|
|
let label = match diagnostic.severity {
|
|
DiagnosticSeverity::Error => "ERROR",
|
|
DiagnosticSeverity::Warning => "WARN ",
|
|
};
|
|
println!(
|
|
"{label} {} {}: {}",
|
|
diagnostic.code, diagnostic.path, diagnostic.message
|
|
);
|
|
}
|
|
}
|
|
|
|
pub(crate) fn finish_cluster_validate(output: &ValidateOutput, json: bool) -> Result<()> {
|
|
if json {
|
|
print_json(output)?;
|
|
} else {
|
|
print_cluster_validate_human(output);
|
|
}
|
|
if !output.ok {
|
|
io::stdout().flush()?;
|
|
std::process::exit(1);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn finish_cluster_plan(output: &PlanOutput, json: bool) -> Result<()> {
|
|
if json {
|
|
print_json(output)?;
|
|
} else {
|
|
print_cluster_plan_human(output);
|
|
}
|
|
if !output.ok {
|
|
io::stdout().flush()?;
|
|
std::process::exit(1);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn finish_cluster_apply(output: &ApplyOutput, json: bool) -> Result<()> {
|
|
if json {
|
|
print_json(output)?;
|
|
} else {
|
|
print_cluster_apply_human(output);
|
|
}
|
|
if !output.ok {
|
|
io::stdout().flush()?;
|
|
std::process::exit(1);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn finish_cluster_approve(output: &ApproveOutput, json: bool) -> Result<()> {
|
|
if json {
|
|
print_json(output)?;
|
|
} else if output.ok {
|
|
println!(
|
|
"cluster approve: {} {} approved by {} (approval {})",
|
|
output
|
|
.operation
|
|
.as_ref()
|
|
.map(|operation| format!("{operation:?}").to_lowercase())
|
|
.unwrap_or_default(),
|
|
output.resource.as_deref().unwrap_or("?"),
|
|
output.approved_by.as_deref().unwrap_or("?"),
|
|
output.approval_id.as_deref().unwrap_or("?"),
|
|
);
|
|
print_cluster_diagnostics(&output.diagnostics);
|
|
} else {
|
|
println!("cluster approve failed");
|
|
print_cluster_diagnostics(&output.diagnostics);
|
|
}
|
|
if !output.ok {
|
|
io::stdout().flush()?;
|
|
std::process::exit(1);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn finish_cluster_status(output: &StatusOutput, json: bool) -> Result<()> {
|
|
if json {
|
|
print_json(output)?;
|
|
} else {
|
|
print_cluster_status_human(output);
|
|
}
|
|
if !output.ok {
|
|
io::stdout().flush()?;
|
|
std::process::exit(1);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn finish_cluster_state_sync(output: &StateSyncOutput, json: bool) -> Result<()> {
|
|
if json {
|
|
print_json(output)?;
|
|
} else {
|
|
print_cluster_state_sync_human(output);
|
|
}
|
|
if !output.ok {
|
|
io::stdout().flush()?;
|
|
std::process::exit(1);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn finish_cluster_force_unlock(output: &ForceUnlockOutput, json: bool) -> Result<()> {
|
|
if json {
|
|
print_json(output)?;
|
|
} else {
|
|
print_cluster_force_unlock_human(output);
|
|
}
|
|
if !output.ok {
|
|
io::stdout().flush()?;
|
|
std::process::exit(1);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn print_load_human(payload: &LoadOutput) {
|
|
println!(
|
|
"loaded {} on branch {} with {}: {} nodes across {} node types, {} edges across {} edge types",
|
|
payload.uri,
|
|
payload.branch,
|
|
payload.mode,
|
|
payload.nodes_loaded,
|
|
payload.node_types_loaded,
|
|
payload.edges_loaded,
|
|
payload.edge_types_loaded
|
|
);
|
|
if payload.branch_created {
|
|
if let Some(base) = &payload.base_branch {
|
|
println!("branch {} created from {}", payload.branch, base);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn print_ingest_human(output: &IngestOutput) {
|
|
println!(
|
|
"ingested {} into branch {} from {} with {} ({})",
|
|
output.uri,
|
|
output.branch,
|
|
output.base_branch.as_deref().unwrap_or("main"),
|
|
output.mode.as_str(),
|
|
if output.branch_created {
|
|
"branch created"
|
|
} else {
|
|
"branch exists"
|
|
}
|
|
);
|
|
for table in &output.tables {
|
|
println!("{} rows_loaded={}", table.table_key, table.rows_loaded);
|
|
}
|
|
if let Some(actor_id) = &output.actor_id {
|
|
println!("actor_id: {}", actor_id);
|
|
}
|
|
}
|
|
|
|
pub(crate) fn print_schema_plan_human(uri: &str, plan: &SchemaMigrationPlan) {
|
|
println!("schema plan for {}", uri);
|
|
println!("supported: {}", if plan.supported { "yes" } else { "no" });
|
|
if plan.steps.is_empty() {
|
|
println!("no schema changes");
|
|
return;
|
|
}
|
|
for step in &plan.steps {
|
|
println!("- {}", render_schema_plan_step(step));
|
|
}
|
|
}
|
|
|
|
pub(crate) fn render_schema_plan_step(step: &SchemaMigrationStep) -> String {
|
|
match step {
|
|
SchemaMigrationStep::AddType { type_kind, name } => {
|
|
format!("add {} type '{}'", schema_type_kind_label(*type_kind), name)
|
|
}
|
|
SchemaMigrationStep::RenameType {
|
|
type_kind,
|
|
from,
|
|
to,
|
|
} => format!(
|
|
"rename {} type '{}' -> '{}'",
|
|
schema_type_kind_label(*type_kind),
|
|
from,
|
|
to
|
|
),
|
|
SchemaMigrationStep::AddProperty {
|
|
type_kind,
|
|
type_name,
|
|
property_name,
|
|
property_type,
|
|
} => format!(
|
|
"add property '{}.{}' ({}) on {} '{}'",
|
|
type_name,
|
|
property_name,
|
|
render_prop_type(property_type),
|
|
schema_type_kind_label(*type_kind),
|
|
type_name
|
|
),
|
|
SchemaMigrationStep::RenameProperty {
|
|
type_kind,
|
|
type_name,
|
|
from,
|
|
to,
|
|
} => format!(
|
|
"rename property '{}.{}' -> '{}.{}' on {} '{}'",
|
|
type_name,
|
|
from,
|
|
type_name,
|
|
to,
|
|
schema_type_kind_label(*type_kind),
|
|
type_name
|
|
),
|
|
SchemaMigrationStep::AddConstraint {
|
|
type_kind,
|
|
type_name,
|
|
constraint,
|
|
} => format!(
|
|
"add constraint {} on {} '{}'",
|
|
render_constraint(constraint),
|
|
schema_type_kind_label(*type_kind),
|
|
type_name
|
|
),
|
|
SchemaMigrationStep::UpdateTypeMetadata {
|
|
type_kind,
|
|
name,
|
|
annotations,
|
|
} => format!(
|
|
"update metadata on {} '{}' ({})",
|
|
schema_type_kind_label(*type_kind),
|
|
name,
|
|
render_annotations(annotations)
|
|
),
|
|
SchemaMigrationStep::UpdatePropertyMetadata {
|
|
type_kind,
|
|
type_name,
|
|
property_name,
|
|
annotations,
|
|
} => format!(
|
|
"update metadata on property '{}.{}' of {} '{}' ({})",
|
|
type_name,
|
|
property_name,
|
|
schema_type_kind_label(*type_kind),
|
|
type_name,
|
|
render_annotations(annotations)
|
|
),
|
|
SchemaMigrationStep::DropType {
|
|
type_kind,
|
|
name,
|
|
mode,
|
|
} => format!(
|
|
"drop {} type '{}' ({} mode)",
|
|
schema_type_kind_label(*type_kind),
|
|
name,
|
|
drop_mode_label(*mode),
|
|
),
|
|
SchemaMigrationStep::DropProperty {
|
|
type_kind,
|
|
type_name,
|
|
property_name,
|
|
mode,
|
|
} => format!(
|
|
"drop property '{}.{}' of {} '{}' ({} mode)",
|
|
type_name,
|
|
property_name,
|
|
schema_type_kind_label(*type_kind),
|
|
type_name,
|
|
drop_mode_label(*mode),
|
|
),
|
|
SchemaMigrationStep::UnsupportedChange { entity, reason, .. } => {
|
|
// When a schema-lint code is attached, render code + tier
|
|
// so operators see at-a-glance the kind of risk (destructive
|
|
// / validated / safe) — not just the rule identifier.
|
|
// Reach the diagnostic via the `diagnostic()` helper so the
|
|
// CLI doesn't need to know how the lookup works.
|
|
match step.diagnostic() {
|
|
Some(diag) => format!(
|
|
"unsupported change on {} [{}, {}]: {}",
|
|
entity,
|
|
diag.code,
|
|
schema_lint_tier_label(diag.tier),
|
|
reason,
|
|
),
|
|
None => format!("unsupported change on {}: {}", entity, reason),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn schema_type_kind_label(kind: omnigraph_compiler::SchemaTypeKind) -> &'static str {
|
|
match kind {
|
|
omnigraph_compiler::SchemaTypeKind::Interface => "interface",
|
|
omnigraph_compiler::SchemaTypeKind::Node => "node",
|
|
omnigraph_compiler::SchemaTypeKind::Edge => "edge",
|
|
}
|
|
}
|
|
|
|
pub(crate) fn schema_lint_tier_label(tier: omnigraph_compiler::SafetyTier) -> &'static str {
|
|
match tier {
|
|
omnigraph_compiler::SafetyTier::Safe => "safe",
|
|
omnigraph_compiler::SafetyTier::Validated => "validated",
|
|
omnigraph_compiler::SafetyTier::Destructive => "destructive",
|
|
}
|
|
}
|
|
|
|
pub(crate) fn drop_mode_label(mode: omnigraph_compiler::DropMode) -> &'static str {
|
|
match mode {
|
|
omnigraph_compiler::DropMode::Soft => "soft",
|
|
omnigraph_compiler::DropMode::Hard => "hard",
|
|
}
|
|
}
|
|
|
|
pub(crate) fn render_prop_type(prop_type: &omnigraph_compiler::PropType) -> String {
|
|
let base = if let Some(values) = &prop_type.enum_values {
|
|
format!("Enum({})", values.join("|"))
|
|
} else {
|
|
prop_type.scalar.to_string()
|
|
};
|
|
let base = if prop_type.list {
|
|
format!("[{}]", base)
|
|
} else {
|
|
base
|
|
};
|
|
if prop_type.nullable {
|
|
format!("{}?", base)
|
|
} else {
|
|
base
|
|
}
|
|
}
|
|
|
|
pub(crate) fn render_constraint(constraint: &omnigraph_compiler::schema::ast::Constraint) -> String {
|
|
match constraint {
|
|
omnigraph_compiler::schema::ast::Constraint::Key(columns) => {
|
|
format!("@key({})", columns.join(", "))
|
|
}
|
|
omnigraph_compiler::schema::ast::Constraint::Unique(columns) => {
|
|
format!("@unique({})", columns.join(", "))
|
|
}
|
|
omnigraph_compiler::schema::ast::Constraint::Index(columns) => {
|
|
format!("@index({})", columns.join(", "))
|
|
}
|
|
omnigraph_compiler::schema::ast::Constraint::Range { property, min, max } => {
|
|
format!("@range({}, {:?}, {:?})", property, min, max)
|
|
}
|
|
omnigraph_compiler::schema::ast::Constraint::Check { property, pattern } => {
|
|
format!("@check({}, {:?})", property, pattern)
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn render_annotations(annotations: &[omnigraph_compiler::schema::ast::Annotation]) -> String {
|
|
annotations
|
|
.iter()
|
|
.map(|annotation| match &annotation.value {
|
|
Some(value) => format!("@{}({})", annotation.name, value),
|
|
None => format!("@{}", annotation.name),
|
|
})
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
}
|
|
|
|
pub(crate) fn print_embed_human(output: &EmbedOutput) {
|
|
println!(
|
|
"embedded {} rows (selected {}, cleaned {}) from {} -> {} [{} {}d]",
|
|
output.embedded_rows,
|
|
output.selected_rows,
|
|
output.cleaned_rows,
|
|
output.input,
|
|
output.output,
|
|
output.mode,
|
|
output.dimension
|
|
);
|
|
}
|
|
|
|
pub(crate) fn print_snapshot_human(branch: &str, manifest_version: u64, entries: &[SnapshotTableOutput]) {
|
|
println!("branch: {}", branch);
|
|
println!("manifest_version: {}", manifest_version);
|
|
for entry in entries {
|
|
println!(
|
|
"{} v{} branch={} rows={}",
|
|
entry.table_key,
|
|
entry.table_version,
|
|
entry.table_branch.as_deref().unwrap_or("main"),
|
|
entry.row_count
|
|
);
|
|
}
|
|
}
|
|
|
|
pub(crate) fn print_read_output(
|
|
output: &ReadOutput,
|
|
format: ReadOutputFormat,
|
|
config: &OmnigraphConfig,
|
|
) -> Result<()> {
|
|
println!(
|
|
"{}",
|
|
render_read(
|
|
output,
|
|
format,
|
|
&ReadRenderOptions {
|
|
max_column_width: config.table_max_column_width(),
|
|
cell_layout: config.table_cell_layout(),
|
|
},
|
|
)?
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn print_change_human(output: &ChangeOutput) {
|
|
println!(
|
|
"changed {} via {}: {} nodes, {} edges",
|
|
output.branch, output.query_name, output.affected_nodes, output.affected_edges
|
|
);
|
|
if let Some(actor_id) = &output.actor_id {
|
|
println!("actor_id: {}", actor_id);
|
|
}
|
|
}
|
|
|
|
pub(crate) fn print_commit_list_human(commits: &[CommitOutput]) {
|
|
for commit in commits {
|
|
let branch = commit.manifest_branch.as_deref().unwrap_or("main");
|
|
println!(
|
|
"{} branch={} version={}{}",
|
|
commit.graph_commit_id,
|
|
branch,
|
|
commit.manifest_version,
|
|
commit
|
|
.actor_id
|
|
.as_deref()
|
|
.map(|actor| format!(" actor={}", actor))
|
|
.unwrap_or_default()
|
|
);
|
|
}
|
|
}
|
|
|
|
pub(crate) fn print_commit_human(commit: &CommitOutput) {
|
|
println!("graph_commit_id: {}", commit.graph_commit_id);
|
|
println!(
|
|
"manifest_branch: {}",
|
|
commit.manifest_branch.as_deref().unwrap_or("main")
|
|
);
|
|
println!("manifest_version: {}", commit.manifest_version);
|
|
if let Some(parent_commit_id) = &commit.parent_commit_id {
|
|
println!("parent_commit_id: {}", parent_commit_id);
|
|
}
|
|
if let Some(merged_parent_commit_id) = &commit.merged_parent_commit_id {
|
|
println!("merged_parent_commit_id: {}", merged_parent_commit_id);
|
|
}
|
|
if let Some(actor_id) = &commit.actor_id {
|
|
println!("actor_id: {}", actor_id);
|
|
}
|
|
println!("created_at: {}", commit.created_at);
|
|
}
|
|
|
|
pub(crate) fn print_policy_explain(decision: &PolicyDecision, actor_id: &str, request: &PolicyRequest) {
|
|
println!(
|
|
"decision: {}",
|
|
if decision.allowed { "allow" } else { "deny" }
|
|
);
|
|
println!("actor: {}", actor_id);
|
|
println!("action: {}", request.action);
|
|
if let Some(branch) = &request.branch {
|
|
println!("branch: {}", branch);
|
|
}
|
|
if let Some(target_branch) = &request.target_branch {
|
|
println!("target_branch: {}", target_branch);
|
|
}
|
|
if let Some(rule_id) = &decision.matched_rule_id {
|
|
println!("matched_rule: {}", rule_id);
|
|
}
|
|
println!("message: {}", decision.message);
|
|
}
|
|
|
|
pub(crate) fn yaml_string(value: &str) -> String {
|
|
format!("'{}'", value.replace('\'', "''"))
|
|
}
|
|
|
|
#[derive(serde::Serialize)]
|
|
pub(crate) struct QueriesIssue {
|
|
pub(crate) query: String,
|
|
pub(crate) message: String,
|
|
}
|
|
|
|
#[derive(serde::Serialize)]
|
|
pub(crate) struct QueriesValidateOutput {
|
|
pub(crate) ok: bool,
|
|
pub(crate) breakages: Vec<QueriesIssue>,
|
|
pub(crate) warnings: Vec<QueriesIssue>,
|
|
}
|
|
|
|
#[derive(serde::Serialize)]
|
|
pub(crate) struct QueriesParam {
|
|
pub(crate) name: String,
|
|
#[serde(rename = "type")]
|
|
pub(crate) type_name: String,
|
|
pub(crate) nullable: bool,
|
|
}
|
|
|
|
#[derive(serde::Serialize)]
|
|
pub(crate) struct QueriesListItem {
|
|
pub(crate) name: String,
|
|
pub(crate) mcp_expose: bool,
|
|
pub(crate) tool_name: Option<String>,
|
|
pub(crate) mutation: bool,
|
|
pub(crate) params: Vec<QueriesParam>,
|
|
}
|
|
|
|
#[derive(serde::Serialize)]
|
|
pub(crate) struct QueriesListOutput {
|
|
pub(crate) queries: Vec<QueriesListItem>,
|
|
}
|