#![forbid(unsafe_code)]
pub mod algorithms;
pub mod canonical;
pub mod embedding_options;
pub mod manifest;
pub mod uuid;
use std::{fmt, sync::Arc};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub struct Span {
pub start: usize,
pub end: usize,
}
impl Span {
#[must_use]
pub const fn new(start: usize, end: usize) -> Self {
Self { start, end }
}
}
impl fmt::Display for Span {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}..{}", self.start, self.end)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct TypeId(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
pub struct PropId(pub u32);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OntologyFormat {
Yaml,
Json,
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, Hash, Default, serde::Serialize, serde::Deserialize,
)]
#[serde(rename_all = "lowercase")]
pub enum OntologyMode {
#[default]
Exploratory,
Advisory,
Strict,
}
#[derive(thiserror::Error, Debug)]
pub enum GfError {
#[error("not implemented: {0}")]
NotImplemented(&'static str),
#[error("parse error at {span}: {msg}")]
Parse {
msg: String,
span: Span,
},
#[error("bind error at {span}: {msg}")]
Bind {
msg: String,
span: Span,
},
#[error("plan error: {0}")]
Plan(String),
#[error("execution error: {0}")]
Execution(String),
#[error("provider error: class={class} provider={provider} model={model}")]
Provider {
class: String,
provider: String,
model: String,
},
#[error("storage error: {0}")]
Storage(String),
#[error("{code}: {message}")]
Project {
code: ProjectErrorCode,
message: String,
},
#[error("{code}: {message}")]
Api {
code: ApiErrorCode,
message: String,
},
#[error("lifecycle error: {0}")]
Lifecycle(String),
#[error("validation error: {0}")]
Validation(String),
#[error("ontology error: {0}")]
Ontology(String),
}
impl GfError {
#[must_use]
pub const fn code(&self) -> &'static str {
match self {
Self::NotImplemented(_) => "GF_NOT_IMPLEMENTED",
Self::Parse { .. } => "GF_PARSE",
Self::Bind { .. } | Self::Plan(_) => "GF_PLAN",
Self::Execution(_) | Self::Provider { .. } => "GF_EXECUTION",
Self::Storage(_) => "GF_IO",
Self::Project { code, .. } => code.as_str(),
Self::Api { code, .. } => code.as_str(),
Self::Lifecycle(_) => "GF_LIFECYCLE",
Self::Validation(_) => "GF_VALIDATION",
Self::Ontology(_) => "GF_ONTOLOGY",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ApiErrorCode {
NotFound,
Cancelled,
ResourceLimit,
PageInvalid,
PageSnapshotGone,
SchemaMismatch,
UnknownArgument,
AmbiguousProjection,
IdentityConflict,
FingerprintCollision,
ResultNotRetained,
}
impl ApiErrorCode {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::NotFound => "GF_NOT_FOUND",
Self::Cancelled => "GF_CANCELLED",
Self::ResourceLimit => "GF_RESOURCE_LIMIT",
Self::PageInvalid => "GF_PAGE_INVALID",
Self::PageSnapshotGone => "GF_PAGE_SNAPSHOT_GONE",
Self::SchemaMismatch => "GF_SCHEMA_MISMATCH",
Self::UnknownArgument => "GF_UNKNOWN_ARGUMENT",
Self::AmbiguousProjection => "GF_AMBIGUOUS_PROJECTION",
Self::IdentityConflict => "GF_IDENTITY_CONFLICT",
Self::FingerprintCollision => "GF_FINGERPRINT_COLLISION",
Self::ResultNotRetained => "GF_RESULT_NOT_RETAINED",
}
}
}
impl fmt::Display for ApiErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProjectErrorCode {
UnsupportedProjectFormat,
ProjectUninitialized,
ProjectCorrupt,
UnsupportedFilesystem,
WriterBusy,
WriteConflict,
RebaseExhausted,
TransactionConflict,
PublicationFailed,
UnsupportedCapabilityVersion,
CapabilityDisabled,
TransactionFailed,
CheckpointExists,
CheckpointNotFound,
CheckpointRegistryCorrupt,
ReadOnlyView,
ResourceLimit,
}
impl ProjectErrorCode {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::UnsupportedProjectFormat => "GF_UNSUPPORTED_PROJECT_FORMAT",
Self::ProjectUninitialized => "GF_PROJECT_UNINITIALIZED",
Self::ProjectCorrupt => "GF_PROJECT_CORRUPT",
Self::UnsupportedFilesystem => "GF_UNSUPPORTED_FILESYSTEM",
Self::WriterBusy => "GF_WRITER_BUSY",
Self::WriteConflict => "GF_WRITE_CONFLICT",
Self::RebaseExhausted => "GF_REBASE_EXHAUSTED",
Self::TransactionConflict => "GF_IDEMPOTENCY_CONFLICT",
Self::PublicationFailed => "GF_PUBLICATION_FAILED",
Self::UnsupportedCapabilityVersion => "GF_UNSUPPORTED_CAPABILITY_VERSION",
Self::CapabilityDisabled => "GF_CAPABILITY_DISABLED",
Self::TransactionFailed => "GF_TRANSACTION_FAILED",
Self::CheckpointExists => "GF_CHECKPOINT_EXISTS",
Self::CheckpointNotFound => "GF_CHECKPOINT_NOT_FOUND",
Self::CheckpointRegistryCorrupt => "GF_CHECKPOINT_REGISTRY_CORRUPT",
Self::ReadOnlyView => "GF_READ_ONLY_VIEW",
Self::ResourceLimit => "GF_RESOURCE_LIMIT",
}
}
}
impl fmt::Display for ProjectErrorCode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[non_exhaustive]
pub enum PropValue {
Null,
Bool(bool),
Int(i64),
Float(f64),
Str(String),
List(Vec<PropValue>),
}
impl fmt::Display for PropValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Null => write!(f, "null"),
Self::Bool(b) => write!(f, "{b}"),
Self::Int(i) => write!(f, "{i}"),
Self::Float(fl) => write!(f, "{fl}"),
Self::Str(s) => write!(f, "{s}"),
Self::List(l) => {
write!(f, "[")?;
for (i, v) in l.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{v}")?;
}
write!(f, "]")
}
}
}
}
#[doc(hidden)]
#[derive(Clone, Default)]
pub struct GraphIdentity(Arc<()>);
impl GraphIdentity {
#[must_use]
pub fn new() -> Self {
Self::default()
}
}
impl fmt::Debug for GraphIdentity {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("GraphIdentity(..)")
}
}
#[derive(Debug, Clone)]
pub struct NodeHandle {
pub uuid: ::uuid::Uuid,
pub label: String,
owner: GraphIdentity,
}
impl NodeHandle {
#[doc(hidden)]
#[must_use]
pub fn new(uuid: ::uuid::Uuid, label: impl Into<String>, owner: GraphIdentity) -> Self {
Self {
uuid,
label: label.into(),
owner,
}
}
#[doc(hidden)]
#[must_use]
pub fn belongs_to(&self, owner: &GraphIdentity) -> bool {
Arc::ptr_eq(&self.owner.0, &owner.0)
}
}
impl PartialEq for NodeHandle {
fn eq(&self, other: &Self) -> bool {
self.uuid == other.uuid
}
}
impl Eq for NodeHandle {}
impl fmt::Display for NodeHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}(uuid={})", self.label, self.uuid)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum NodeSelector {
Uuid(::uuid::Uuid),
Handle(NodeHandle),
Match {
label: String,
property: String,
value: PropValue,
},
}
impl NodeSelector {
pub fn uuid(value: &str) -> Result<Self, GfError> {
::uuid::Uuid::parse_str(value)
.map(Self::Uuid)
.map_err(|_| GfError::Validation(format!("invalid node UUID {value:?}")))
}
}
#[derive(Debug, Clone)]
pub struct EdgeHandle {
pub uuid: ::uuid::Uuid,
pub rel_type: String,
}
impl EdgeHandle {
#[doc(hidden)]
#[must_use]
pub fn new(uuid: ::uuid::Uuid, rel_type: impl Into<String>) -> Self {
Self {
uuid,
rel_type: rel_type.into(),
}
}
}
impl PartialEq for EdgeHandle {
fn eq(&self, other: &Self) -> bool {
self.uuid == other.uuid
}
}
impl Eq for EdgeHandle {}
impl fmt::Display for EdgeHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}(uuid={})", self.rel_type, self.uuid)
}
}
#[derive(Debug, Clone)]
pub struct RankOptions {
pub by: algorithms::RankAlgorithm,
pub via: Option<String>,
pub directed: bool,
pub write_property: Option<String>,
}
impl Default for RankOptions {
fn default() -> Self {
Self {
by: algorithms::RankAlgorithm::default(),
via: None,
directed: true,
write_property: None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct ClusterOptions {
pub by: algorithms::ClusterAlgorithm,
pub vector_property: Option<String>,
pub via: Option<String>,
pub directed: bool,
pub write_property: Option<String>,
}
#[derive(Debug, Clone)]
pub struct FindOptions {
pub query: Option<String>,
pub label: Option<String>,
pub vector: Option<Vec<f32>>,
pub similar_to: Option<NodeSelector>,
pub semantic_query: Option<String>,
pub limit: usize,
pub space: Option<String>,
pub force_stale: bool,
}
impl Default for FindOptions {
fn default() -> Self {
Self {
query: None,
label: None,
vector: None,
similar_to: None,
semantic_query: None,
limit: 10,
space: None,
force_stale: false,
}
}
}
#[derive(Debug, Clone)]
pub struct PathsOptions {
pub by: algorithms::PathAlgorithm,
pub via: Option<String>,
pub directed: bool,
pub k: usize,
pub weight: Option<String>,
pub capacity_property: Option<String>,
pub cost_property: Option<String>,
pub heuristic: Option<String>,
pub walk_length: Option<usize>,
pub seed: Option<u64>,
pub terminal_uuids: Vec<[u8; 16]>,
pub prize_property: Option<String>,
}
impl Default for PathsOptions {
fn default() -> Self {
Self {
by: algorithms::PathAlgorithm::Bfs,
via: None,
directed: true,
k: 1,
weight: None,
capacity_property: None,
cost_property: None,
heuristic: None,
walk_length: None,
seed: None,
terminal_uuids: Vec::new(),
prize_property: None,
}
}
}
#[derive(Debug, Clone)]
pub struct AnalyzeOptions {
pub by: algorithms::AnalyzeAlgorithm,
pub via: Option<String>,
pub directed: bool,
pub weight: Option<String>,
pub k: Option<usize>,
pub partition_property: Option<String>,
}
impl Default for AnalyzeOptions {
fn default() -> Self {
Self {
by: algorithms::AnalyzeAlgorithm::IsDag,
via: None,
directed: true,
weight: None,
k: None,
partition_property: None,
}
}
}
#[derive(Debug, Clone)]
pub struct SimilarOptions {
pub by: algorithms::SimilarAlgorithm,
pub k: usize,
pub vector_property: Option<String>,
pub via: Option<String>,
}
impl Default for SimilarOptions {
fn default() -> Self {
Self {
by: algorithms::SimilarAlgorithm::default(),
k: 10,
vector_property: None,
via: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExplainStage {
Ast,
BoundAst,
GraphIr,
LogicalPlan,
PhysicalPlan,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn span_display() {
assert_eq!(Span::new(0, 5).to_string(), "0..5");
}
#[test]
fn gf_error_not_implemented() {
let e = GfError::NotImplemented("execute");
assert!(e.to_string().contains("execute"));
}
#[test]
fn node_handle_display() {
let owner = GraphIdentity::new();
let uuid = ::uuid::Uuid::from_bytes([1; 16]);
let h = NodeHandle::new(uuid, "Person", owner.clone());
assert!(h.to_string().contains("Person"));
assert!(h.to_string().contains(&uuid.to_string()));
assert!(h.belongs_to(&owner));
assert!(!h.belongs_to(&GraphIdentity::new()));
assert_eq!(h, NodeHandle::new(uuid, "Other", GraphIdentity::new()));
}
#[test]
fn edge_handle_identity_and_display_are_uuid_based() {
let uuid = ::uuid::Uuid::from_bytes([2; 16]);
let handle = EdgeHandle::new(uuid, "KNOWS");
assert_eq!(handle.uuid, uuid);
assert_eq!(handle.rel_type, "KNOWS");
assert_eq!(handle, EdgeHandle::new(uuid, "OTHER"));
assert_ne!(
handle,
EdgeHandle::new(::uuid::Uuid::from_bytes([3; 16]), "KNOWS"),
);
assert_eq!(handle.to_string(), format!("KNOWS(uuid={uuid})"));
assert!(!handle.to_string().starts_with("Edge(id="));
}
#[test]
fn paths_options_default_to_the_canonical_bfs_contract() {
let options = PathsOptions::default();
assert_eq!(options.by, algorithms::PathAlgorithm::Bfs);
assert_eq!(options.via, None);
assert!(options.directed);
assert_eq!(options.k, 1);
assert_eq!(options.weight, None);
assert!(options.terminal_uuids.is_empty());
assert_eq!(options.prize_property, None);
}
#[test]
fn analyze_options_default_to_the_canonical_is_dag_contract() {
let options = AnalyzeOptions::default();
assert_eq!(options.by, algorithms::AnalyzeAlgorithm::IsDag);
assert_eq!(options.via, None);
assert!(options.directed);
}
#[test]
fn find_options_default_to_no_query_or_stale_override() {
let options = FindOptions::default();
assert_eq!(options.query, None);
assert_eq!(options.label, None);
assert_eq!(options.vector, None);
assert_eq!(options.similar_to, None);
assert_eq!(options.semantic_query, None);
assert_eq!(options.limit, 10);
assert_eq!(options.space, None);
assert!(!options.force_stale);
}
}