From c51b9e1e20b332b4c97d4a5b8130e7ed7433ac7f Mon Sep 17 00:00:00 2001 From: Ragnor Comerford Date: Wed, 3 Jun 2026 11:14:19 +0200 Subject: [PATCH] refactor: extract omnigraph-api-types crate from omnigraph-server Move api.rs (HTTP request/response DTOs + OpenAPI schemas) into omnigraph-api-types (deps: engine, compiler, queries, serde, serde_json, utoipa). Repoint crate::queries::StoredQuery -> omnigraph_queries. Relocate the two catalog-projection tests here, where query_catalog_entry lives, eliminating the queries<->api dev-dep cycle (no cycle, tests at their boundary). Server aliases via 'pub use omnigraph_api_types as api;'. openapi.json byte-identical (drift test passes; no regen). 2 api-types tests pass; server+cli compile. No behavior change. --- Cargo.lock | 13 +++ Cargo.toml | 1 + crates/omnigraph-api-types/Cargo.toml | 17 ++++ .../api.rs => omnigraph-api-types/src/lib.rs} | 86 ++++++++++++++++++- crates/omnigraph-server/Cargo.toml | 1 + crates/omnigraph-server/src/lib.rs | 2 +- 6 files changed, 118 insertions(+), 2 deletions(-) create mode 100644 crates/omnigraph-api-types/Cargo.toml rename crates/{omnigraph-server/src/api.rs => omnigraph-api-types/src/lib.rs} (87%) diff --git a/Cargo.lock b/Cargo.lock index 83baf29..a107c12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4541,6 +4541,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "omnigraph-api-types" +version = "0.6.1" +dependencies = [ + "omnigraph-compiler", + "omnigraph-engine", + "omnigraph-queries", + "serde", + "serde_json", + "utoipa", +] + [[package]] name = "omnigraph-cli" version = "0.6.1" @@ -4673,6 +4685,7 @@ dependencies = [ "futures", "lance", "lance-index", + "omnigraph-api-types", "omnigraph-compiler", "omnigraph-config", "omnigraph-engine", diff --git a/Cargo.toml b/Cargo.toml index b60d6d8..0008296 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "crates/omnigraph-server", "crates/omnigraph-config", "crates/omnigraph-queries", + "crates/omnigraph-api-types", ] default-members = [ "crates/omnigraph", diff --git a/crates/omnigraph-api-types/Cargo.toml b/crates/omnigraph-api-types/Cargo.toml new file mode 100644 index 0000000..d3f965c --- /dev/null +++ b/crates/omnigraph-api-types/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "omnigraph-api-types" +version = "0.6.1" +edition = "2024" +description = "HTTP request/response types (OpenAPI schemas) for the Omnigraph graph database." +license = "MIT" +repository = "https://github.com/ModernRelay/omnigraph" +homepage = "https://github.com/ModernRelay/omnigraph" +documentation = "https://docs.rs/omnigraph-api-types" + +[dependencies] +omnigraph = { package = "omnigraph-engine", path = "../omnigraph", version = "0.6.1" } +omnigraph-compiler = { path = "../omnigraph-compiler", version = "0.6.1" } +omnigraph-queries = { path = "../omnigraph-queries", version = "0.6.1" } +serde = { workspace = true } +serde_json = { workspace = true } +utoipa = { workspace = true } diff --git a/crates/omnigraph-server/src/api.rs b/crates/omnigraph-api-types/src/lib.rs similarity index 87% rename from crates/omnigraph-server/src/api.rs rename to crates/omnigraph-api-types/src/lib.rs index 4a6024f..85c0b5a 100644 --- a/crates/omnigraph-server/src/api.rs +++ b/crates/omnigraph-api-types/src/lib.rs @@ -1,7 +1,7 @@ use omnigraph::db::{GraphCommit, MergeOutcome, ReadTarget, SchemaApplyResult, Snapshot}; use omnigraph::error::{MergeConflict, MergeConflictKind}; use omnigraph::loader::{IngestResult, LoadMode}; -use crate::queries::StoredQuery; +use omnigraph_queries::StoredQuery; use omnigraph_compiler::SchemaMigrationStep; use omnigraph_compiler::query::ast::Param; use omnigraph_compiler::result::QueryResult; @@ -693,3 +693,87 @@ pub struct GraphInfo { pub struct GraphListResponse { pub graphs: Vec, } + +#[cfg(test)] +mod tests { + // Catalog-projection tests for `query_catalog_entry`: relocated here from + // `omnigraph-queries` because the function under test lives in this crate + // (keeps the test at its boundary and keeps `omnigraph-queries` free of a + // dev-dependency cycle on `omnigraph-api-types`). + use super::{ParamKind, query_catalog_entry}; + use omnigraph_queries::{QueryRegistry, RegistrySpec}; + + fn spec(name: &str, source: &str, expose: bool) -> RegistrySpec { + RegistrySpec { + name: name.to_string(), + source: source.to_string(), + expose, + tool_name: None, + } + } + + fn spec_tool(name: &str, source: &str, expose: bool, tool_name: &str) -> RegistrySpec { + RegistrySpec { + name: name.to_string(), + source: source.to_string(), + expose, + tool_name: Some(tool_name.to_string()), + } + } + + #[test] + fn catalog_entry_projects_every_param_kind() { + let reg = QueryRegistry::from_specs(vec![spec_tool( + "all_types", + "query all_types($s: String, $i: I32, $big: I64, $u: U64, $f: F64, $b: Bool, \ + $d: Date, $dt: DateTime, $blob: Blob, $opt: String?, $list: [I32], $vec: Vector(4)) \ + { match { $x: User } return { $x.name } }", + true, + "all", + )]) + .unwrap(); + let entry = query_catalog_entry(reg.lookup("all_types").unwrap()); + assert_eq!(entry.name, "all_types"); + assert_eq!(entry.tool_name, "all"); + assert!(!entry.mutation); + + let by: std::collections::HashMap<_, _> = + entry.params.iter().map(|p| (p.name.as_str(), p)).collect(); + assert_eq!(by["s"].kind, ParamKind::String); + assert_eq!(by["i"].kind, ParamKind::Int); + assert_eq!(by["big"].kind, ParamKind::BigInt, "I64 → bigint (string on the wire)"); + assert_eq!(by["u"].kind, ParamKind::BigInt, "U64 → bigint"); + assert_eq!(by["f"].kind, ParamKind::Float); + assert_eq!(by["b"].kind, ParamKind::Bool); + assert_eq!(by["d"].kind, ParamKind::Date); + assert_eq!(by["dt"].kind, ParamKind::DateTime); + assert_eq!(by["blob"].kind, ParamKind::Blob); + assert!(!by["s"].nullable); + assert!(by["opt"].nullable, "String? → nullable"); + assert_eq!(by["list"].kind, ParamKind::List); + assert_eq!(by["list"].item_kind, Some(ParamKind::Int), "[I32] → list of int"); + assert_eq!(by["vec"].kind, ParamKind::Vector); + assert_eq!(by["vec"].vector_dim, Some(4)); + } + + #[test] + fn catalog_entry_flags_mutation_and_empty_params() { + let reg = QueryRegistry::from_specs(vec![spec( + "add_user", + "query add_user($name: String) { insert User { name: $name } }", + true, + )]) + .unwrap(); + let entry = query_catalog_entry(reg.lookup("add_user").unwrap()); + assert!(entry.mutation, "insert body → mutation flag"); + + let reg2 = QueryRegistry::from_specs(vec![spec( + "no_params", + "query no_params() { match { $u: User } return { $u.name } }", + true, + )]) + .unwrap(); + let entry2 = query_catalog_entry(reg2.lookup("no_params").unwrap()); + assert!(entry2.params.is_empty(), "no declared params → empty list"); + } +} diff --git a/crates/omnigraph-server/Cargo.toml b/crates/omnigraph-server/Cargo.toml index 8d4778a..5a66552 100644 --- a/crates/omnigraph-server/Cargo.toml +++ b/crates/omnigraph-server/Cargo.toml @@ -24,6 +24,7 @@ omnigraph-compiler = { path = "../omnigraph-compiler", version = "0.6.1" } omnigraph-policy = { path = "../omnigraph-policy", version = "0.6.1" } omnigraph-config = { path = "../omnigraph-config", version = "0.6.1" } omnigraph-queries = { path = "../omnigraph-queries", version = "0.6.1" } +omnigraph-api-types = { path = "../omnigraph-api-types", version = "0.6.1" } axum = { workspace = true } clap = { workspace = true } color-eyre = { workspace = true } diff --git a/crates/omnigraph-server/src/lib.rs b/crates/omnigraph-server/src/lib.rs index bd60d96..72b9189 100644 --- a/crates/omnigraph-server/src/lib.rs +++ b/crates/omnigraph-server/src/lib.rs @@ -1,4 +1,4 @@ -pub mod api; +pub use omnigraph_api_types as api; pub mod auth; pub use omnigraph_config as config; pub mod graph_id;