mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-09 01:35:18 +02:00
Merge pull request #27 from ModernRelay/fix/schema-show-polish
Add schema show command (supersedes #23)
This commit is contained in:
commit
e926c925d6
5 changed files with 176 additions and 3 deletions
|
|
@ -18,7 +18,7 @@ use omnigraph_server::api::{
|
|||
BranchCreateOutput, BranchCreateRequest, BranchDeleteOutput, BranchListOutput,
|
||||
BranchMergeOutput, BranchMergeRequest, ChangeOutput, ChangeRequest, CommitListOutput,
|
||||
CommitOutput, ErrorOutput, ExportRequest, IngestOutput, IngestRequest, ReadOutput, ReadRequest,
|
||||
RunListOutput, RunOutput, SchemaApplyOutput, SchemaApplyRequest, SnapshotOutput,
|
||||
RunListOutput, RunOutput, SchemaApplyOutput, SchemaApplyRequest, SchemaOutput, SnapshotOutput,
|
||||
SnapshotTableOutput, commit_output, ingest_output, read_output, run_output,
|
||||
schema_apply_output, snapshot_payload,
|
||||
};
|
||||
|
|
@ -303,6 +303,18 @@ enum SchemaCommand {
|
|||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
/// Show the current accepted schema source
|
||||
#[command(alias = "get")]
|
||||
Show {
|
||||
/// Repo URI
|
||||
uri: Option<String>,
|
||||
#[arg(long)]
|
||||
target: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
|
|
@ -2003,6 +2015,37 @@ async fn main() -> Result<()> {
|
|||
print_schema_apply_human(&output);
|
||||
}
|
||||
}
|
||||
SchemaCommand::Show {
|
||||
uri,
|
||||
target,
|
||||
config,
|
||||
json,
|
||||
} => {
|
||||
let config = load_cli_config(config.as_ref())?;
|
||||
let bearer_token =
|
||||
resolve_remote_bearer_token(&config, uri.as_deref(), target.as_deref())?;
|
||||
let uri = resolve_uri(&config, uri, target.as_deref())?;
|
||||
let output = if is_remote_uri(&uri) {
|
||||
remote_json::<SchemaOutput>(
|
||||
&http_client,
|
||||
Method::GET,
|
||||
remote_url(&uri, "/schema"),
|
||||
None,
|
||||
bearer_token.as_deref(),
|
||||
)
|
||||
.await?
|
||||
} else {
|
||||
let db = Omnigraph::open(&uri).await?;
|
||||
SchemaOutput {
|
||||
schema_source: db.schema_source().to_string(),
|
||||
}
|
||||
};
|
||||
if json {
|
||||
print_json(&output)?;
|
||||
} else {
|
||||
println!("{}", output.schema_source);
|
||||
}
|
||||
}
|
||||
},
|
||||
Command::Query { command } => match command {
|
||||
QueryCommand::Lint {
|
||||
|
|
|
|||
|
|
@ -280,6 +280,11 @@ pub struct SchemaApplyOutput {
|
|||
pub steps: Vec<SchemaMigrationStep>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct SchemaOutput {
|
||||
pub schema_source: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
||||
pub struct IngestRequest {
|
||||
pub branch: Option<String>,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ use api::{
|
|||
BranchMergeOutput, BranchMergeRequest, ChangeOutput, ChangeRequest, CommitListOutput,
|
||||
CommitListQuery, ErrorCode, ErrorOutput, ExportRequest, HealthOutput, IngestOutput,
|
||||
IngestRequest, ReadOutput, ReadRequest, RunListOutput, SchemaApplyOutput, SchemaApplyRequest,
|
||||
SnapshotQuery, ingest_output, schema_apply_output, snapshot_payload,
|
||||
SchemaOutput, SnapshotQuery, ingest_output, schema_apply_output, snapshot_payload,
|
||||
};
|
||||
use axum::body::{Body, Bytes};
|
||||
use axum::extract::DefaultBodyLimit;
|
||||
|
|
@ -63,6 +63,7 @@ use utoipa::openapi::security::{Http, HttpAuthScheme, SecurityScheme};
|
|||
server_export,
|
||||
server_change,
|
||||
server_schema_apply,
|
||||
server_schema_get,
|
||||
server_ingest,
|
||||
server_branch_list,
|
||||
server_branch_create,
|
||||
|
|
@ -407,6 +408,7 @@ pub fn build_app(state: AppState) -> Router {
|
|||
.route("/export", post(server_export))
|
||||
.route("/read", post(server_read))
|
||||
.route("/change", post(server_change))
|
||||
.route("/schema", get(server_schema_get))
|
||||
.route("/schema/apply", post(server_schema_apply))
|
||||
.route(
|
||||
"/ingest",
|
||||
|
|
@ -796,6 +798,41 @@ async fn server_change(
|
|||
}))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/schema",
|
||||
tag = "schema",
|
||||
responses(
|
||||
(status = 200, description = "Current schema source", body = SchemaOutput),
|
||||
(status = 401, description = "Unauthorized", body = ErrorOutput),
|
||||
(status = 403, description = "Forbidden", body = ErrorOutput),
|
||||
),
|
||||
security(("bearer_token" = [])),
|
||||
)]
|
||||
async fn server_schema_get(
|
||||
State(state): State<AppState>,
|
||||
actor: Option<Extension<AuthenticatedActor>>,
|
||||
) -> std::result::Result<Json<SchemaOutput>, ApiError> {
|
||||
authorize_request(
|
||||
&state,
|
||||
actor.as_ref().map(|Extension(actor)| actor),
|
||||
PolicyRequest {
|
||||
actor_id: actor
|
||||
.as_ref()
|
||||
.map(|Extension(actor)| actor.as_str().to_string())
|
||||
.unwrap_or_default(),
|
||||
action: PolicyAction::Read,
|
||||
branch: None,
|
||||
target_branch: None,
|
||||
},
|
||||
)?;
|
||||
let schema_source = {
|
||||
let db = Arc::clone(&state.db).read_owned().await;
|
||||
db.schema_source().to_string()
|
||||
};
|
||||
Ok(Json(SchemaOutput { schema_source }))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/schema/apply",
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@ const EXPECTED_PATHS: &[&str] = &[
|
|||
"/read",
|
||||
"/export",
|
||||
"/change",
|
||||
"/schema",
|
||||
"/schema/apply",
|
||||
"/ingest",
|
||||
"/branches",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use omnigraph::db::{Omnigraph, ReadTarget};
|
|||
use omnigraph::loader::{LoadMode, load_jsonl};
|
||||
use omnigraph_server::api::{
|
||||
BranchCreateRequest, BranchMergeRequest, ChangeRequest, ErrorOutput, ExportRequest,
|
||||
IngestRequest, ReadRequest, SchemaApplyRequest,
|
||||
IngestRequest, ReadRequest, SchemaApplyRequest, SchemaOutput,
|
||||
};
|
||||
use omnigraph_server::{AppState, build_app};
|
||||
use serde_json::{Value, json};
|
||||
|
|
@ -1042,6 +1042,93 @@ async fn snapshot_route_returns_manifest_dataset_version() {
|
|||
assert!(snapshot_body["tables"].is_array());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn schema_route_returns_current_source() {
|
||||
let (_temp, app) = app_for_loaded_repo().await;
|
||||
let (status, body) = json_response(
|
||||
&app,
|
||||
Request::builder()
|
||||
.uri("/schema")
|
||||
.method(Method::GET)
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await;
|
||||
|
||||
assert_eq!(status, StatusCode::OK);
|
||||
let output: SchemaOutput = serde_json::from_value(body).unwrap();
|
||||
assert!(output.schema_source.contains("node Person"));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn schema_route_requires_bearer_token_when_auth_configured() {
|
||||
let (_temp, app) = app_for_loaded_repo_with_auth("demo-token").await;
|
||||
|
||||
let (missing_status, missing_body) = json_response(
|
||||
&app,
|
||||
Request::builder()
|
||||
.uri("/schema")
|
||||
.method(Method::GET)
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await;
|
||||
let missing_error: ErrorOutput = serde_json::from_value(missing_body).unwrap();
|
||||
assert_eq!(missing_status, StatusCode::UNAUTHORIZED);
|
||||
assert_eq!(
|
||||
missing_error.code,
|
||||
Some(omnigraph_server::api::ErrorCode::Unauthorized)
|
||||
);
|
||||
|
||||
let (ok_status, ok_body) = json_response(
|
||||
&app,
|
||||
Request::builder()
|
||||
.uri("/schema")
|
||||
.method(Method::GET)
|
||||
.header("authorization", "Bearer demo-token")
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(ok_status, StatusCode::OK);
|
||||
let output: SchemaOutput = serde_json::from_value(ok_body).unwrap();
|
||||
assert!(!output.schema_source.is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn schema_route_denied_when_actor_lacks_read_permission() {
|
||||
let temp = init_loaded_repo().await;
|
||||
let repo = repo_path(temp.path());
|
||||
let policy_path = temp.path().join("policy.yaml");
|
||||
// Policy grants branch_create only — no read action for act-bruno.
|
||||
fs::write(&policy_path, INGEST_CREATE_ONLY_POLICY_YAML).unwrap();
|
||||
let state = AppState::open_with_bearer_tokens_and_policy(
|
||||
repo.to_string_lossy().to_string(),
|
||||
vec![("act-bruno".to_string(), "team-token".to_string())],
|
||||
Some(&policy_path),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let app = build_app(state);
|
||||
|
||||
let (status, body) = json_response(
|
||||
&app,
|
||||
Request::builder()
|
||||
.uri("/schema")
|
||||
.method(Method::GET)
|
||||
.header("authorization", "Bearer team-token")
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await;
|
||||
let error: ErrorOutput = serde_json::from_value(body).unwrap();
|
||||
assert_eq!(status, StatusCode::FORBIDDEN);
|
||||
assert_eq!(
|
||||
error.code,
|
||||
Some(omnigraph_server::api::ErrorCode::Forbidden)
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn policy_blocks_change_on_protected_main_but_allows_unprotected_branch() {
|
||||
let temp = init_loaded_repo().await;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue