This is an automated email from the ASF dual-hosted git repository.

yangxk1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-graphar.git


The following commit(s) were added to refs/heads/main by this push:
     new bc4407b9 feat(Rust): add `builder::Vertex` and 
`builder::VerticesBuilder` support (#911)
bc4407b9 is described below

commit bc4407b9ce47e3eeca9cab518e029d0a31731f62
Author: Jinye Wu <[email protected]>
AuthorDate: Mon Jun 29 19:47:12 2026 +0800

    feat(Rust): add `builder::Vertex` and `builder::VerticesBuilder` support 
(#911)
    
    * update
    
    * add `builder::Vertex`
    
    * update
    
    * update
    
    * remove Vertex id
    
    * update
    
    * update dependency
---
 rust/Cargo.lock                                |   7 +
 rust/Cargo.toml                                |   1 +
 rust/build.rs                                  |  18 +-
 rust/include/graphar_rs.h                      |  77 +++-
 rust/src/builder/mod.rs                        |  11 +
 rust/src/{lib.rs => builder/property_value.rs} |  33 +-
 rust/src/builder/vertex.rs                     | 519 +++++++++++++++++++++++++
 rust/src/error.rs                              |  22 ++
 rust/src/ffi.rs                                | 115 ++++++
 rust/src/graphar_rs.cc                         |  35 ++
 rust/src/info/adjacent_list.rs                 |   2 +
 rust/src/info/edge_info.rs                     |   3 +
 rust/src/info/vertex_info.rs                   |   3 +
 rust/src/lib.rs                                |   2 +
 rust/src/property.rs                           |   2 +
 15 files changed, 821 insertions(+), 29 deletions(-)

diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index 052f39a9..4bb944f3 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -191,6 +191,7 @@ dependencies = [
  "cmake",
  "cxx",
  "cxx-build",
+ "pastey",
  "pkg-config",
  "tempfile",
 ]
@@ -238,6 +239,12 @@ version = "1.21.3"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
 
+[[package]]
+name = "pastey"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec"
+
 [[package]]
 name = "pkg-config"
 version = "0.3.32"
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 7a66444f..df758a10 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -24,6 +24,7 @@ license = "Apache-2.0"
 [dependencies]
 # anyhow = "1.0.100"
 cxx = "1.0.190"
+pastey = "0.2.1"
 
 [build-dependencies]
 cxx-build = "1.0.190"
diff --git a/rust/build.rs b/rust/build.rs
index 40fce462..d4ecba2e 100644
--- a/rust/build.rs
+++ b/rust/build.rs
@@ -23,11 +23,17 @@ use std::path::{Path, PathBuf};
 fn link_libraries() {
     println!("cargo:rerun-if-env-changed=PKG_CONFIG_PATH");
 
-    let _arrow = pkg_config::Config::new()
-        .statik(false)
-        .cargo_metadata(true)
-        .probe("arrow")
-        .expect("Arrow development files not found via pkg-config. Set 
PKG_CONFIG_PATH if needed.");
+    for package in ["arrow", "parquet", "arrow-dataset", "arrow-acero"] {
+        pkg_config::Config::new()
+            .statik(false)
+            .cargo_metadata(true)
+            .probe(package)
+            .unwrap_or_else(|_| {
+                panic!(
+                    "{package} development files not found via pkg-config. Set 
PKG_CONFIG_PATH if needed."
+                )
+            });
+    }
 
     println!("cargo:rustc-link-lib=graphar");
     println!("cargo:rustc-link-lib=graphar_thirdparty");
@@ -39,7 +45,7 @@ fn build_ffi(bridge_file: &str, out_name: &str, source_file: 
&str, include_paths
 
     build.includes(include_paths);
     // TODO support MSVC
-    build.flag("-std=c++17");
+    build.flag("-std=c++20");
     build.flag("-fdiagnostics-color=always");
 
     build.compile(out_name);
diff --git a/rust/include/graphar_rs.h b/rust/include/graphar_rs.h
index 3d096e5a..527fae30 100644
--- a/rust/include/graphar_rs.h
+++ b/rust/include/graphar_rs.h
@@ -25,12 +25,15 @@
 #include <string>
 #include <vector>
 
-#include "graphar/fwd.h"
-#include "graphar/graph_info.h"
-#include "graphar/types.h"
-#include "graphar/version_parser.h"
+#include "graphar/api/high_level_writer.h"
+#include "graphar/api/info.h"
 #include "rust/cxx.h"
 
+using i32 = int32_t;
+using i64 = int64_t;
+using f32 = float;
+using f64 = double;
+
 namespace graphar {
 struct MaybeIndex;
 
@@ -129,4 +132,70 @@ void graph_info_save(const graphar::GraphInfo& graph_info,
                      const std::string& path);
 std::unique_ptr<std::string> graph_info_dump(
     const graphar::GraphInfo& graph_info);
+
+// =========================== Builder ===========================
+// `Vertex`
+std::unique_ptr<graphar::builder::Vertex> new_vertex_builder();
+
+inline bool vertex_builder_is_empty(const graphar::builder::Vertex& v) {
+  return v.Empty();
+}
+
+inline bool vertex_builder_is_multi_property(const graphar::builder::Vertex& v,
+                                             const std::string& name) {
+  return v.IsMultiProperty(name);
+}
+
+inline bool vertex_builder_contains_property(const graphar::builder::Vertex& v,
+                                             const std::string& name) {
+  const auto& props = v.GetProperties();
+  return props.find(name) != props.end();
+}
+
+#define DEF_VERTEX_BUILDER_ADD_PROPERTY_FUNC(type)                             
\
+  inline void vertex_builder_add_property_##type(                              
\
+      graphar::builder::Vertex& v, const std::string& name, type val) {        
\
+    v.AddProperty(name, val);                                                  
\
+  }
+
+DEF_VERTEX_BUILDER_ADD_PROPERTY_FUNC(bool)
+DEF_VERTEX_BUILDER_ADD_PROPERTY_FUNC(i32)
+DEF_VERTEX_BUILDER_ADD_PROPERTY_FUNC(i64)
+DEF_VERTEX_BUILDER_ADD_PROPERTY_FUNC(f32)
+DEF_VERTEX_BUILDER_ADD_PROPERTY_FUNC(f64)
+
+inline void vertex_builder_add_property_string(graphar::builder::Vertex& v,
+                                               const std::string& name,
+                                               const std::string& val) {
+  v.AddProperty(name, val);
+}
+
+#define DEF_VERTEX_BUILDER_ADD_PROPERTY_WITH_CARDINALITY_FUNC(type)            
\
+  inline void vertex_builder_add_property_##type##_with_cardinality(           
\
+      graphar::builder::Vertex& v, graphar::Cardinality cardinality,           
\
+      const std::string& name, type val) {                                     
\
+    v.AddProperty(cardinality, name, val);                                     
\
+  }
+
+DEF_VERTEX_BUILDER_ADD_PROPERTY_WITH_CARDINALITY_FUNC(bool)
+DEF_VERTEX_BUILDER_ADD_PROPERTY_WITH_CARDINALITY_FUNC(i32)
+DEF_VERTEX_BUILDER_ADD_PROPERTY_WITH_CARDINALITY_FUNC(i64)
+DEF_VERTEX_BUILDER_ADD_PROPERTY_WITH_CARDINALITY_FUNC(f32)
+DEF_VERTEX_BUILDER_ADD_PROPERTY_WITH_CARDINALITY_FUNC(f64)
+
+inline void vertex_builder_add_property_string_with_cardinality(
+    graphar::builder::Vertex& v, graphar::Cardinality cardinality,
+    const std::string& name, const std::string& val) {
+  v.AddProperty(cardinality, name, val);
+}
+
+void add_vertex(graphar::builder::VerticesBuilder& builder,
+                graphar::builder::Vertex& v);
+
+std::unique_ptr<graphar::builder::VerticesBuilder>
+new_vertices_builder(const std::shared_ptr<graphar::VertexInfo>& vertex_info,
+                     const std::string& path_prefix, i64 start_idx);
+
+void vertices_dump(graphar::builder::VerticesBuilder& builder);
+
 }  // namespace graphar_rs
diff --git a/rust/src/builder/mod.rs b/rust/src/builder/mod.rs
new file mode 100644
index 00000000..b35f32c1
--- /dev/null
+++ b/rust/src/builder/mod.rs
@@ -0,0 +1,11 @@
+//! High-level builders for writing GraphAr data.
+
+/// Vertex builders for writing vertex chunks.
+pub mod vertex;
+
+mod property_value;
+
+/// A value that can be written into a GraphAr property.
+pub use property_value::PropertyValue;
+
+pub use vertex::{Vertex, VerticesBuilder};
diff --git a/rust/src/lib.rs b/rust/src/builder/property_value.rs
similarity index 55%
copy from rust/src/lib.rs
copy to rust/src/builder/property_value.rs
index 9a44a40a..35dcbdd4 100644
--- a/rust/src/lib.rs
+++ b/rust/src/builder/property_value.rs
@@ -15,26 +15,21 @@
 // specific language governing permissions and limitations
 // under the License.
 
-//! Rust bindings for GraphAr.
+//! Shared property value abstractions for builders.
 
-#![deny(missing_docs)]
+use crate::types::Cardinality;
 
-use std::path::Path;
-
-mod ffi;
-
-/// Error types for this crate.
-pub mod error;
-/// GraphAr metadata information types.
-pub mod info;
-/// GraphAr property.
-pub mod property;
-/// GraphAr logical data types.
-pub mod types;
-
-pub use error::{Error, Result};
+#[doc(hidden)]
+pub(crate) mod sealed {
+    pub trait Sealed<Target> {}
+}
 
-fn path_to_utf8_str(path: &Path) -> Result<&str> {
-    path.to_str()
-        .ok_or_else(|| Error::NonUtf8Path(path.to_owned()))
+/// A value that can be written into a GraphAr property.
+///
+/// This trait is sealed and cannot be implemented outside this crate.
+pub trait PropertyValue<Target>: sealed::Sealed<Target> {
+    /// Add this value as a property on the given target.
+    fn add_to(self, target: &mut Target, name: &str);
+    /// Add this value as a property on the given target with the specified 
cardinality.
+    fn add_to_with_cardinality(self, target: &mut Target, name: &str, 
cardinality: Cardinality);
 }
diff --git a/rust/src/builder/vertex.rs b/rust/src/builder/vertex.rs
new file mode 100644
index 00000000..51f26fc1
--- /dev/null
+++ b/rust/src/builder/vertex.rs
@@ -0,0 +1,519 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+//! GraphAr writer builders.
+
+use std::pin::Pin;
+
+use cxx::{UniquePtr, let_cxx_string};
+
+use crate::builder::property_value::sealed;
+use crate::types::Cardinality;
+use crate::{builder::PropertyValue, ffi, info::VertexInfo};
+
+/// A vertex record being constructed for use with [`VerticesBuilder`].
+///
+/// This is a thin wrapper around GraphAr's `graphar::builder::Vertex`.
+pub struct Vertex(pub(crate) UniquePtr<ffi::graphar::BuilderVertex>);
+
+macro_rules! impl_vertex_property_value {
+    ($($ty:tt),+ $(,)?) => {
+        $(
+            impl sealed::Sealed<Vertex> for $ty {}
+
+            impl PropertyValue<Vertex> for $ty {
+                fn add_to(self, vertex: &mut Vertex, name: &str) {
+                    let_cxx_string!(name = name);
+                    pastey::paste! {
+                        ffi::graphar::[<vertex_builder_add_property_ 
$ty>](vertex.pin_mut(), &name, self);
+                    }
+                }
+
+                fn add_to_with_cardinality(
+                    self,
+                    vertex: &mut Vertex,
+                    name: &str,
+                    cardinality: Cardinality,
+                ) {
+                    let_cxx_string!(name = name);
+                    pastey::paste! {
+                        ffi::graphar::[<vertex_builder_add_property_ $ty 
_with_cardinality>](
+                            vertex.pin_mut(),
+                            cardinality,
+                            &name,
+                            self,
+                        );
+                    }
+                }
+            }
+        )+
+    };
+}
+
+impl_vertex_property_value!(bool, i32, i64, f32, f64);
+
+impl sealed::Sealed<Vertex> for String {}
+impl PropertyValue<Vertex> for String {
+    fn add_to(self, vertex: &mut Vertex, name: &str) {
+        let_cxx_string!(name = name);
+        let_cxx_string!(val = self.as_str());
+        ffi::graphar::vertex_builder_add_property_string(vertex.pin_mut(), 
&name, &val);
+    }
+
+    fn add_to_with_cardinality(self, vertex: &mut Vertex, name: &str, 
cardinality: Cardinality) {
+        let_cxx_string!(name = name);
+        let_cxx_string!(val = self.as_str());
+        ffi::graphar::vertex_builder_add_property_string_with_cardinality(
+            vertex.pin_mut(),
+            cardinality,
+            &name,
+            &val,
+        );
+    }
+}
+
+impl sealed::Sealed<Vertex> for &str {}
+impl PropertyValue<Vertex> for &str {
+    fn add_to(self, vertex: &mut Vertex, name: &str) {
+        let_cxx_string!(name = name);
+        let_cxx_string!(val = self);
+        ffi::graphar::vertex_builder_add_property_string(vertex.pin_mut(), 
&name, &val);
+    }
+
+    fn add_to_with_cardinality(self, vertex: &mut Vertex, name: &str, 
cardinality: Cardinality) {
+        let_cxx_string!(name = name);
+        let_cxx_string!(val = self);
+        ffi::graphar::vertex_builder_add_property_string_with_cardinality(
+            vertex.pin_mut(),
+            cardinality,
+            &name,
+            &val,
+        );
+    }
+}
+
+impl Vertex {
+    pub(crate) fn as_ref(&self) -> &ffi::graphar::BuilderVertex {
+        self.0.as_ref().expect("vertex should be valid")
+    }
+
+    /// Create a new vertex record.
+    pub fn new() -> Self {
+        Self(ffi::graphar::new_vertex_builder())
+    }
+
+    /// Returns true if this vertex is empty.
+    pub fn is_empty(&self) -> bool {
+        ffi::graphar::vertex_builder_is_empty(self.as_ref())
+    }
+
+    /// Add a property to this vertex.
+    ///
+    /// Supported types are: `bool`, `i32`, `i64`, `f32`, `f64`, `String`, 
`&str`.
+    pub fn add_property<S, V>(&mut self, name: S, val: V)
+    where
+        S: AsRef<str>,
+        V: PropertyValue<Vertex>,
+    {
+        val.add_to(self, name.as_ref());
+    }
+
+    /// Add a boolean property.
+    pub fn add_property_bool<S: AsRef<str>>(&mut self, name: S, val: bool) {
+        self.add_property(name, val);
+    }
+
+    /// Add an `i32` property.
+    pub fn add_property_i32<S: AsRef<str>>(&mut self, name: S, val: i32) {
+        self.add_property(name, val);
+    }
+
+    /// Add an `i64` property.
+    pub fn add_property_i64<S: AsRef<str>>(&mut self, name: S, val: i64) {
+        self.add_property(name, val);
+    }
+
+    /// Add an `f32` property.
+    pub fn add_property_f32<S: AsRef<str>>(&mut self, name: S, val: f32) {
+        self.add_property(name, val);
+    }
+
+    /// Add an `f64` property.
+    pub fn add_property_f64<S: AsRef<str>>(&mut self, name: S, val: f64) {
+        self.add_property(name, val);
+    }
+
+    /// Add a string property.
+    pub fn add_property_string<S: AsRef<str>, V: AsRef<str>>(&mut self, name: 
S, val: V) {
+        self.add_property(name, val.as_ref());
+    }
+
+    /// Add a property to this vertex with the specified cardinality.
+    ///
+    /// Supported types are: `bool`, `i32`, `i64`, `f32`, `f64`, `String`, 
`&str`.
+    pub fn add_property_with_cardinality<S, V>(&mut self, cardinality: 
Cardinality, name: S, val: V)
+    where
+        S: AsRef<str>,
+        V: PropertyValue<Vertex>,
+    {
+        val.add_to_with_cardinality(self, name.as_ref(), cardinality);
+    }
+
+    /// Returns true if the property is stored as a multi-value (LIST/SET).
+    pub fn is_multi_property<S: AsRef<str>>(&self, name: S) -> bool {
+        let_cxx_string!(name = name.as_ref());
+        ffi::graphar::vertex_builder_is_multi_property(self.as_ref(), &name)
+    }
+
+    /// Returns true if this vertex contains a property with the given name.
+    pub fn contains_property<S: AsRef<str>>(&self, name: S) -> bool {
+        let_cxx_string!(name = name.as_ref());
+        ffi::graphar::vertex_builder_contains_property(self.as_ref(), &name)
+    }
+
+    /// Append a value into a LIST property.
+    pub fn add_property_list_item<S, V>(&mut self, name: S, val: V)
+    where
+        S: AsRef<str>,
+        V: PropertyValue<Vertex>,
+    {
+        self.add_property_with_cardinality(Cardinality::List, name, val);
+    }
+
+    /// Append multiple values into a LIST property.
+    pub fn add_property_list<I, V>(&mut self, name: &str, values: I)
+    where
+        I: IntoIterator<Item = V>,
+        V: PropertyValue<Vertex>,
+    {
+        for v in values {
+            self.add_property_list_item(name, v);
+        }
+    }
+
+    /// Append a value into a SET property.
+    pub fn add_property_set_item<S, V>(&mut self, name: S, val: V)
+    where
+        S: AsRef<str>,
+        V: PropertyValue<Vertex>,
+    {
+        self.add_property_with_cardinality(Cardinality::Set, name, val);
+    }
+
+    /// Append multiple values into a SET property.
+    pub fn add_property_set<I, V>(&mut self, name: &str, values: I)
+    where
+        I: IntoIterator<Item = V>,
+        V: PropertyValue<Vertex>,
+    {
+        for v in values {
+            self.add_property_set_item(name, v);
+        }
+    }
+
+    pub(crate) fn pin_mut(&mut self) -> Pin<&mut ffi::graphar::BuilderVertex> {
+        self.0.as_mut().expect("vertex should be valid")
+    }
+}
+
+impl Default for Vertex {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+/// A high-level builder for writing a collection of vertices.
+pub struct VerticesBuilder(pub(crate) 
UniquePtr<ffi::graphar::VerticesBuilder>);
+
+impl VerticesBuilder {
+    /// Create a new vertices builder.
+    ///
+    /// The `prefix` is a filesystem prefix string used by GraphAr (it is not 
a [`std::path::Path`]).
+    ///
+    /// GraphAr expects `prefix` to end with a trailing slash (`/`).
+    pub fn try_new<P: AsRef<str>>(
+        vertex_info: &VertexInfo,
+        prefix: P,
+        start_idx: i64,
+    ) -> crate::Result<Self> {
+        let prefix = prefix.as_ref();
+        if !prefix.ends_with('/') {
+            return Err(crate::Error::InvalidArgument {
+                name: "prefix",
+                reason: "prefix must end with '/'".to_string(),
+            });
+        }
+        let_cxx_string!(prefix = prefix);
+        let inner = ffi::graphar::new_vertices_builder(&vertex_info.0, 
&prefix, start_idx)?;
+        Ok(Self(inner))
+    }
+
+    /// Create a new vertices builder.
+    ///
+    /// Panics if the inputs are rejected by GraphAr. Prefer 
[`VerticesBuilder::try_new`]
+    /// if you want to handle errors.
+    pub fn new<P: AsRef<str>>(vertex_info: &VertexInfo, prefix: P, start_idx: 
i64) -> Self {
+        Self::try_new(vertex_info, prefix, start_idx).unwrap()
+    }
+
+    /// Add a vertex into this builder.
+    pub fn add_vertex(&mut self, mut vertex: Vertex) -> crate::Result<()> {
+        ffi::graphar::add_vertex(self.pin_mut(), vertex.pin_mut())?;
+        Ok(())
+    }
+
+    /// Dump all currently buffered vertices into chunk files.
+    pub fn dump(&mut self) -> crate::Result<()> {
+        ffi::graphar::vertices_dump(self.pin_mut())?;
+        Ok(())
+    }
+
+    pub(crate) fn pin_mut(&mut self) -> Pin<&mut 
ffi::graphar::VerticesBuilder> {
+        self.0.as_mut().expect("vertices builder should be valid")
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    use crate::info::InfoVersion;
+    use crate::property::{Property, PropertyGroup, PropertyGroupVector, 
PropertyVec};
+    use crate::types::{Cardinality, DataType, FileType};
+    use tempfile::tempdir;
+
+    fn make_vertex_info() -> VertexInfo {
+        let mut props = PropertyVec::new();
+        props.push(Property::new(
+            "id_i64",
+            DataType::int64(),
+            true,
+            false,
+            Cardinality::Single,
+        ));
+        props.push(Property::new(
+            "active_bool",
+            DataType::bool(),
+            false,
+            false,
+            Cardinality::Single,
+        ));
+        props.push(Property::new(
+            "age_i32",
+            DataType::int32(),
+            false,
+            false,
+            Cardinality::Single,
+        ));
+        props.push(Property::new(
+            "score_f32",
+            DataType::float32(),
+            false,
+            false,
+            Cardinality::Single,
+        ));
+        props.push(Property::new(
+            "rating_f64",
+            DataType::float64(),
+            false,
+            false,
+            Cardinality::Single,
+        ));
+        props.push(Property::new(
+            "name_string",
+            DataType::string(),
+            false,
+            false,
+            Cardinality::Single,
+        ));
+
+        let mut groups = PropertyGroupVector::new();
+        groups.push(PropertyGroup::new(props, FileType::Csv, ""));
+
+        let ver = Some(InfoVersion::new(1).unwrap());
+        VertexInfo::new("person", 4, groups, vec![], "", ver)
+    }
+
+    #[test]
+    fn test_vertex_add_property_dispatch_primitives() {
+        let mut v = Vertex::default();
+        assert!(v.is_empty());
+        assert!(!v.contains_property("age_i32"));
+        v.add_property("id_i64", 1_i64);
+        v.add_property("active_bool", true);
+        v.add_property("age_i32", 42_i32);
+        v.add_property("score_f32", 0.5_f32);
+        v.add_property("rating_f64", 9.5_f64);
+        assert!(!v.is_empty());
+        assert!(!v.is_multi_property("age_i32"));
+        assert!(v.contains_property("age_i32"));
+    }
+
+    #[test]
+    fn test_vertex_add_property_dispatch_string() {
+        let mut v = Vertex::default();
+        assert!(v.is_empty());
+        v.add_property("name_string", "alice");
+        v.add_property("name_string", "bob");
+        v.add_property_string("name_string", "carol");
+        v.add_property_string("name_string", "dave");
+        assert!(!v.is_empty());
+        assert!(!v.is_multi_property("name_string"));
+    }
+
+    #[test]
+    fn test_vertex_add_property_wrapper_methods() {
+        let mut v = Vertex::new();
+        v.add_property_bool("active_bool", true);
+        v.add_property_i32("age_i32", 1);
+        v.add_property_i64("id_i64", 2);
+        v.add_property_f32("score_f32", 1.0);
+        v.add_property_f64("rating_f64", 2.0);
+        v.add_property_string("name_string", "alice");
+        assert!(!v.is_empty());
+    }
+
+    #[test]
+    fn test_vertex_add_property_with_cardinality() {
+        let mut v = Vertex::new();
+        assert!(!v.is_multi_property("tags"));
+        assert!(!v.contains_property("tags"));
+
+        v.add_property_with_cardinality(Cardinality::List, "tags", "t0");
+        assert!(v.is_multi_property("tags"));
+        assert!(v.contains_property("tags"));
+
+        v.add_property_list("nums", [1_i64, 2_i64, 3_i64]);
+        assert!(v.is_multi_property("nums"));
+        assert!(v.contains_property("nums"));
+
+        v.add_property_set("uniq", ["a", "a", "b"]);
+        assert!(v.is_multi_property("uniq"));
+        assert!(v.contains_property("uniq"));
+    }
+
+    #[test]
+    fn test_vertex_add_property_with_cardinality_dispatch() {
+        let mut v = Vertex::new();
+
+        v.add_property_with_cardinality(Cardinality::Single, "b_single", true);
+        assert!(v.contains_property("b_single"));
+        assert!(!v.is_multi_property("b_single"));
+
+        v.add_property_with_cardinality(Cardinality::List, "b_list", true);
+        assert!(v.contains_property("b_list"));
+        assert!(v.is_multi_property("b_list"));
+
+        v.add_property_with_cardinality(Cardinality::Single, "i32_single", 
1_i32);
+        assert!(v.contains_property("i32_single"));
+        assert!(!v.is_multi_property("i32_single"));
+
+        v.add_property_with_cardinality(Cardinality::List, "i32_list", 1_i32);
+        assert!(v.contains_property("i32_list"));
+        assert!(v.is_multi_property("i32_list"));
+
+        v.add_property_with_cardinality(Cardinality::Single, "i64_single", 
1_i64);
+        assert!(v.contains_property("i64_single"));
+        assert!(!v.is_multi_property("i64_single"));
+
+        v.add_property_with_cardinality(Cardinality::List, "i64_list", 1_i64);
+        assert!(v.contains_property("i64_list"));
+        assert!(v.is_multi_property("i64_list"));
+
+        v.add_property_with_cardinality(Cardinality::Single, "f32_single", 
1.0_f32);
+        assert!(v.contains_property("f32_single"));
+        assert!(!v.is_multi_property("f32_single"));
+
+        v.add_property_with_cardinality(Cardinality::List, "f32_list", 
1.0_f32);
+        assert!(v.contains_property("f32_list"));
+        assert!(v.is_multi_property("f32_list"));
+
+        v.add_property_with_cardinality(Cardinality::Single, "f64_single", 
1.0_f64);
+        assert!(v.contains_property("f64_single"));
+        assert!(!v.is_multi_property("f64_single"));
+
+        v.add_property_with_cardinality(Cardinality::List, "f64_list", 
1.0_f64);
+        assert!(v.contains_property("f64_list"));
+        assert!(v.is_multi_property("f64_list"));
+
+        v.add_property_with_cardinality(Cardinality::Single, "s_single", 
"alice");
+        assert!(v.contains_property("s_single"));
+        assert!(!v.is_multi_property("s_single"));
+
+        v.add_property_with_cardinality(Cardinality::List, "s_list", "alice");
+        assert!(v.contains_property("s_list"));
+        assert!(v.is_multi_property("s_list"));
+
+        v.add_property_with_cardinality(Cardinality::Single, "string_single", 
"bob".to_string());
+        assert!(v.contains_property("string_single"));
+        assert!(!v.is_multi_property("string_single"));
+
+        v.add_property_with_cardinality(Cardinality::Set, "string_set", 
"bob".to_string());
+        assert!(v.contains_property("string_set"));
+        assert!(v.is_multi_property("string_set"));
+    }
+
+    #[test]
+    fn test_vertices_builder_add_and_dump() {
+        let info = make_vertex_info();
+        let tmp = tempdir().unwrap();
+        let prefix = tmp.path().join("vertices");
+        std::fs::create_dir_all(&prefix).unwrap();
+
+        let prefix = format!("{}/", prefix.display());
+        let mut b = VerticesBuilder::new(&info, prefix.as_str(), 0);
+
+        let mut v = Vertex::new();
+        v.add_property("id_i64", 1_i64);
+        v.add_property("active_bool", true);
+        v.add_property("age_i32", 42_i32);
+        v.add_property("score_f32", 0.5_f32);
+        v.add_property("rating_f64", 9.5_f64);
+        v.add_property("name_string", "alice");
+        b.add_vertex(v).unwrap();
+
+        b.dump().unwrap();
+        let dir = tmp.path().join("vertices");
+        assert!(std::fs::read_dir(&dir).unwrap().next().is_some());
+    }
+
+    #[test]
+    fn test_vertices_builder_try_new_rejects_prefix_without_trailing_slash() {
+        let info = make_vertex_info();
+        let tmp = tempdir().unwrap();
+        let prefix = format!("{}", tmp.path().display());
+        match VerticesBuilder::try_new(&info, prefix.as_str(), 0) {
+            Ok(_) => panic!("VerticesBuilder::try_new should reject prefixes 
without trailing '/'"),
+            Err(err) => assert!(matches!(
+                err,
+                crate::Error::InvalidArgument { name: "prefix", .. }
+            )),
+        }
+    }
+
+    #[test]
+    fn test_vertices_builder_try_new_rejects_negative_start_idx() {
+        let info = make_vertex_info();
+        let tmp = tempdir().unwrap();
+
+        let prefix = format!("{}/", tmp.path().display());
+        match VerticesBuilder::try_new(&info, prefix.as_str(), -1) {
+            Ok(_) => panic!("VerticesBuilder::try_new should reject negative 
start_idx"),
+            Err(err) => assert!(matches!(err, crate::Error::Cxx(_))),
+        }
+    }
+}
diff --git a/rust/src/error.rs b/rust/src/error.rs
index fcc873ce..f727f41c 100644
--- a/rust/src/error.rs
+++ b/rust/src/error.rs
@@ -28,6 +28,13 @@ pub type Result<T> = std::result::Result<T, Error>;
 pub enum Error {
     /// An exception raised from the C++ GraphAr implementation and propagated 
via `cxx`.
     Cxx(cxx::Exception),
+    /// An input argument is rejected by the Rust binding before calling into 
GraphAr.
+    InvalidArgument {
+        /// The argument name.
+        name: &'static str,
+        /// The reason why this argument is invalid.
+        reason: String,
+    },
     /// A filesystem path is not valid UTF-8.
     NonUtf8Path(PathBuf),
 }
@@ -36,6 +43,9 @@ impl fmt::Display for Error {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
             Self::Cxx(e) => write!(f, "C++ exception: {e}"),
+            Self::InvalidArgument { name, reason } => {
+                write!(f, "invalid argument {name}: {reason}")
+            }
             Self::NonUtf8Path(path) => write!(f, "path is not valid UTF-8: 
{}", path.display()),
         }
     }
@@ -45,6 +55,7 @@ impl std::error::Error for Error {
     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
         match self {
             Self::Cxx(e) => Some(e),
+            Self::InvalidArgument { .. } => None,
             Self::NonUtf8Path(_) => None,
         }
     }
@@ -89,4 +100,15 @@ mod tests {
         assert!(msg.contains("C++ exception:"), "msg={msg:?}");
         assert!(StdError::source(&err).is_some());
     }
+
+    #[test]
+    fn test_invalid_argument_display_and_source() {
+        let err = Error::InvalidArgument {
+            name: "prefix",
+            reason: "prefix must end with '/'".to_string(),
+        };
+        let msg = err.to_string();
+        assert!(msg.contains("invalid argument prefix"), "msg={msg:?}");
+        assert!(StdError::source(&err).is_none());
+    }
 }
diff --git a/rust/src/ffi.rs b/rust/src/ffi.rs
index 15c20584..d388d2fc 100644
--- a/rust/src/ffi.rs
+++ b/rust/src/ffi.rs
@@ -452,6 +452,121 @@ pub(crate) mod graphar {
         type SharedAdjacentList = crate::ffi::SharedAdjacentList;
     }
     impl CxxVector<SharedAdjacentList> {}
+
+    // =========================== Builder ===========================
+    // `builder::Vertex`
+    #[namespace = "graphar::builder"]
+    unsafe extern "C++" {
+        #[rust_name = "BuilderVertex"]
+        type Vertex;
+
+        #[namespace = "graphar_rs"]
+        fn new_vertex_builder() -> UniquePtr<BuilderVertex>;
+        #[namespace = "graphar_rs"]
+        fn vertex_builder_add_property_bool(
+            vertex: Pin<&mut BuilderVertex>,
+            name: &CxxString,
+            val: bool,
+        );
+        #[namespace = "graphar_rs"]
+        fn vertex_builder_add_property_i32(
+            vertex: Pin<&mut BuilderVertex>,
+            name: &CxxString,
+            val: i32,
+        );
+        #[namespace = "graphar_rs"]
+        fn vertex_builder_add_property_i64(
+            vertex: Pin<&mut BuilderVertex>,
+            name: &CxxString,
+            val: i64,
+        );
+        #[namespace = "graphar_rs"]
+        fn vertex_builder_add_property_f32(
+            vertex: Pin<&mut BuilderVertex>,
+            name: &CxxString,
+            val: f32,
+        );
+        #[namespace = "graphar_rs"]
+        fn vertex_builder_add_property_f64(
+            vertex: Pin<&mut BuilderVertex>,
+            name: &CxxString,
+            val: f64,
+        );
+        #[namespace = "graphar_rs"]
+        fn vertex_builder_add_property_string(
+            vertex: Pin<&mut BuilderVertex>,
+            name: &CxxString,
+            val: &CxxString,
+        );
+
+        #[namespace = "graphar_rs"]
+        fn vertex_builder_is_empty(vertex: &BuilderVertex) -> bool;
+        #[namespace = "graphar_rs"]
+        fn vertex_builder_is_multi_property(vertex: &BuilderVertex, name: 
&CxxString) -> bool;
+        #[namespace = "graphar_rs"]
+        fn vertex_builder_contains_property(vertex: &BuilderVertex, name: 
&CxxString) -> bool;
+
+        #[namespace = "graphar_rs"]
+        fn vertex_builder_add_property_bool_with_cardinality(
+            vertex: Pin<&mut BuilderVertex>,
+            cardinality: Cardinality,
+            name: &CxxString,
+            val: bool,
+        );
+        #[namespace = "graphar_rs"]
+        fn vertex_builder_add_property_i32_with_cardinality(
+            vertex: Pin<&mut BuilderVertex>,
+            cardinality: Cardinality,
+            name: &CxxString,
+            val: i32,
+        );
+        #[namespace = "graphar_rs"]
+        fn vertex_builder_add_property_i64_with_cardinality(
+            vertex: Pin<&mut BuilderVertex>,
+            cardinality: Cardinality,
+            name: &CxxString,
+            val: i64,
+        );
+        #[namespace = "graphar_rs"]
+        fn vertex_builder_add_property_f32_with_cardinality(
+            vertex: Pin<&mut BuilderVertex>,
+            cardinality: Cardinality,
+            name: &CxxString,
+            val: f32,
+        );
+        #[namespace = "graphar_rs"]
+        fn vertex_builder_add_property_f64_with_cardinality(
+            vertex: Pin<&mut BuilderVertex>,
+            cardinality: Cardinality,
+            name: &CxxString,
+            val: f64,
+        );
+        #[namespace = "graphar_rs"]
+        fn vertex_builder_add_property_string_with_cardinality(
+            vertex: Pin<&mut BuilderVertex>,
+            cardinality: Cardinality,
+            name: &CxxString,
+            val: &CxxString,
+        );
+    }
+
+    // `builder::VerticesBuilder`
+    #[namespace = "graphar::builder"]
+    unsafe extern "C++" {
+        type VerticesBuilder;
+
+        #[namespace = "graphar_rs"]
+        fn add_vertex(builder: Pin<&mut VerticesBuilder>, v: Pin<&mut 
BuilderVertex>)
+        -> Result<()>;
+        #[namespace = "graphar_rs"]
+        fn new_vertices_builder(
+            vertex_info: &SharedPtr<VertexInfo>,
+            path_prefix: &CxxString,
+            start_idx: i64,
+        ) -> Result<UniquePtr<VerticesBuilder>>;
+        #[namespace = "graphar_rs"]
+        fn vertices_dump(builder: Pin<&mut VerticesBuilder>) -> Result<()>;
+    }
 }
 
 impl From<MaybeIndex> for Option<usize> {
diff --git a/rust/src/graphar_rs.cc b/rust/src/graphar_rs.cc
index 0d9f68a9..022d1c1f 100644
--- a/rust/src/graphar_rs.cc
+++ b/rust/src/graphar_rs.cc
@@ -285,4 +285,39 @@ std::unique_ptr<std::string> graph_info_dump(
   }
   return std::make_unique<std::string>(std::move(dumped).value());
 }
+
+// =========================== Builder ===========================
+// `Vertex`
+std::unique_ptr<graphar::builder::Vertex> new_vertex_builder() {
+  return std::make_unique<graphar::builder::Vertex>();
+}
+
+void add_vertex(graphar::builder::VerticesBuilder& builder,
+                graphar::builder::Vertex& v) {
+  auto status = builder.AddVertex(v);
+  if (!status.ok()) {
+    throw std::runtime_error(status.message());
+  }
+}
+
+std::unique_ptr<graphar::builder::VerticesBuilder>
+new_vertices_builder(const std::shared_ptr<graphar::VertexInfo>& vertex_info,
+                     const std::string& path_prefix, i64 start_idx) {
+  if (vertex_info == nullptr) {
+    throw std::runtime_error("VerticesBuilder: vertex_info must not be null");
+  }
+  if (start_idx < 0) {
+    throw std::runtime_error("VerticesBuilder: start_idx must be >= 0");
+  }
+
+  return std::make_unique<graphar::builder::VerticesBuilder>(
+      vertex_info, path_prefix, static_cast<graphar::IdType>(start_idx));
+}
+
+void vertices_dump(graphar::builder::VerticesBuilder &builder) {
+  auto status = builder.Dump();
+  if (!status.ok()) {
+    throw std::runtime_error(status.message());
+  }
+}
 }  // namespace graphar_rs
diff --git a/rust/src/info/adjacent_list.rs b/rust/src/info/adjacent_list.rs
index 8ef28e99..d1efe4ea 100644
--- a/rust/src/info/adjacent_list.rs
+++ b/rust/src/info/adjacent_list.rs
@@ -38,6 +38,8 @@ impl AdjacentList {
     ///
     /// If `path_prefix` is `None`, GraphAr will use a default prefix derived
     /// from `ty` (e.g. `ordered_by_source/`).
+    ///
+    /// GraphAr conventions typically use a trailing slash (`/`) for prefixes.
     pub fn new<P: AsRef<str>>(
         ty: AdjListType,
         file_type: FileType,
diff --git a/rust/src/info/edge_info.rs b/rust/src/info/edge_info.rs
index af693c46..839a473b 100644
--- a/rust/src/info/edge_info.rs
+++ b/rust/src/info/edge_info.rs
@@ -56,6 +56,8 @@ impl EdgeInfo {
     ///
     /// The `prefix` is a logical prefix string used by GraphAr (it is not a 
filesystem path).
     ///
+    /// GraphAr conventions typically use a trailing slash (`/`) for prefixes.
+    ///
     /// Panics if GraphAr rejects the inputs (including, but not limited to,
     /// empty type names, non-positive chunk sizes, or empty adjacency list
     /// vector). Prefer [`EdgeInfo::try_new`] if you want to handle errors.
@@ -338,6 +340,7 @@ impl EdgeInfoBuilder {
     /// Set the logical prefix.
     ///
     /// This is a logical prefix string used by GraphAr (it is not a 
filesystem path).
+    /// GraphAr conventions typically use a trailing slash (`/`) for prefixes.
     pub fn prefix<P: AsRef<str>>(mut self, prefix: P) -> Self {
         self.prefix = prefix.as_ref().to_owned();
         self
diff --git a/rust/src/info/vertex_info.rs b/rust/src/info/vertex_info.rs
index df098fb1..f78185cb 100644
--- a/rust/src/info/vertex_info.rs
+++ b/rust/src/info/vertex_info.rs
@@ -40,6 +40,8 @@ impl VertexInfo {
     /// The `prefix` is a logical prefix string used by GraphAr (it is not a
     /// filesystem path).
     ///
+    /// GraphAr conventions typically use a trailing slash (`/`) for prefixes.
+    ///
     /// Panics if GraphAr rejects the inputs (including, but not limited to,
     /// `type` being empty or `chunk_size <= 0`). Prefer 
[`VertexInfo::try_new`]
     /// if you want to handle errors.
@@ -254,6 +256,7 @@ impl VertexInfoBuilder {
     /// Set the logical prefix.
     ///
     /// This is a logical prefix string used by GraphAr (it is not a 
filesystem path).
+    /// GraphAr conventions typically use a trailing slash (`/`) for prefixes.
     pub fn prefix<P: AsRef<str>>(mut self, prefix: P) -> Self {
         self.prefix = prefix.as_ref().to_owned();
         self
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index 9a44a40a..225ab43c 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -23,6 +23,8 @@ use std::path::Path;
 
 mod ffi;
 
+/// GraphAr data builders for writing chunks.
+pub mod builder;
 /// Error types for this crate.
 pub mod error;
 /// GraphAr metadata information types.
diff --git a/rust/src/property.rs b/rust/src/property.rs
index ef13fd52..a7bddea3 100644
--- a/rust/src/property.rs
+++ b/rust/src/property.rs
@@ -245,6 +245,8 @@ impl PropertyGroup {
     ///
     /// The `prefix` is a logical prefix string used by GraphAr (it is not a
     /// filesystem path).
+    ///
+    /// GraphAr conventions typically use a trailing slash (`/`) for prefixes.
     pub fn new<S: AsRef<str>>(properties: PropertyVec, file_type: FileType, 
prefix: S) -> Self {
         let_cxx_string!(prefix = prefix.as_ref());
         let inner = ffi::graphar::CreatePropertyGroup(properties.as_ref(), 
file_type, &prefix);


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to