Skip to content

Commit d71e06d

Browse files
committed
Add public serialization API for alternative transport protocols
This commit adds serializer_export.go which exports wrapper functions around internal GraphBinary serialization logic, enabling developers to implement alternative transport protocols (gRPC, HTTP/2, etc.) while maintaining full Gremlin API compatibility. New exported functions: - SerializeRequest(): Serialize bytecode with traversal source - SerializeBytecode(): Convenience wrapper using default source 'g' - SerializeStringQuery(): Serialize string queries with options - DeserializeResult(): Deserialize response bytes into Result objects - NewResultSet(): Create ResultSet from collected results These functions are thin wrappers that delegate to existing internal serialization code, requiring zero modifications to existing APIs and maintaining full backward compatibility. Use case: Enables building gRPC-based Gremlin clients that need to serialize traversals and deserialize responses outside the WebSocket transport layer, benefiting the broader TinkerPop ecosystem.
1 parent 1f998c3 commit d71e06d

File tree

2 files changed

+179
-0
lines changed

2 files changed

+179
-0
lines changed

CHANGELOG.asciidoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ image::https://raw.githubusercontent.com/apache/tinkerpop/master/docs/static/ima
2525
2626
This release also includes changes from <<release-3-7-6, 3.7.6>>.
2727
28+
==== Improvements
29+
30+
* Added public serialization API in `gremlin-go` to enable custom transport implementations (gRPC, HTTP/2, etc.).
2831
2932
[[release-3-8-0]]
3033
=== TinkerPop 3.8.0 (Release Date: November 12, 2025)
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*
2+
Licensed to the Apache Software Foundation (ASF) under one
3+
or more contributor license agreements. See the NOTICE file
4+
distributed with this work for additional information
5+
regarding copyright ownership. The ASF licenses this file
6+
to you under the Apache License, Version 2.0 (the
7+
"License"); you may not use this file except in compliance
8+
with the License. You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing,
13+
software distributed under the License is distributed on an
14+
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
KIND, either express or implied. See the License for the
16+
specific language governing permissions and limitations
17+
under the License.
18+
*/
19+
20+
package gremlingo
21+
22+
import "sync"
23+
24+
// SerializeRequest serializes a complete request with bytecode into GraphBinary format.
25+
// This function enables developers to implement alternative transport protocols (e.g., gRPC, HTTP/2)
26+
// while maintaining full compatibility with the Gremlin traversal API.
27+
//
28+
// The function creates a proper Gremlin request that includes the bytecode, serializes it using
29+
// the GraphBinary serializer, and returns the raw bytes that can be transmitted over any transport.
30+
//
31+
// Parameters:
32+
// - bytecode: The Gremlin bytecode to serialize
33+
// - traversalSource: The name of the traversal source (typically "g")
34+
// - sessionId: The session ID for stateful sessions (empty string for stateless)
35+
//
36+
// Returns:
37+
// - []byte: The serialized request in GraphBinary format
38+
// - error: Any error encountered during serialization
39+
//
40+
// Example usage:
41+
//
42+
// bytecode := &Bytecode{...}
43+
// bytes, err := SerializeRequest(bytecode, "g", "")
44+
// if err != nil {
45+
// return err
46+
// }
47+
// // Send bytes over custom transport (gRPC, HTTP/2, etc.)
48+
func SerializeRequest(bytecode *Bytecode, traversalSource, sessionId string) ([]byte, error) {
49+
// Use the existing makeBytecodeRequest function from request.go
50+
request := makeBytecodeRequest(bytecode, traversalSource, sessionId)
51+
52+
// Serialize the request using GraphBinary serializer
53+
serializer := graphBinarySerializer{}
54+
return serializer.serializeMessage(&request)
55+
}
56+
57+
// SerializeBytecode serializes bytecode into GraphBinary format using the default traversal source "g".
58+
// This is a convenience wrapper around SerializeRequest for backward compatibility.
59+
//
60+
// Parameters:
61+
// - bytecode: The Gremlin bytecode to serialize
62+
//
63+
// Returns:
64+
// - []byte: The serialized request in GraphBinary format
65+
// - error: Any error encountered during serialization
66+
func SerializeBytecode(bytecode *Bytecode) ([]byte, error) {
67+
return SerializeRequest(bytecode, "g", "")
68+
}
69+
70+
// SerializeStringQuery serializes a string-based Gremlin query into GraphBinary format.
71+
// This function enables sending raw Gremlin query strings over custom transport protocols.
72+
//
73+
// The function creates a proper Gremlin request that includes the query string, serializes it
74+
// using the GraphBinary serializer, and returns the raw bytes.
75+
//
76+
// Parameters:
77+
// - query: The Gremlin query string to serialize
78+
// - traversalSource: The name of the traversal source (typically "g")
79+
// - sessionId: The session ID for stateful sessions (empty string for stateless)
80+
// - requestOptions: Options for the request (bindings, timeout, etc.)
81+
//
82+
// Returns:
83+
// - []byte: The serialized request in GraphBinary format
84+
// - error: Any error encountered during serialization
85+
//
86+
// Example usage:
87+
//
88+
// bytes, err := SerializeStringQuery("g.V().count()", "g", "", RequestOptions{})
89+
// if err != nil {
90+
// return err
91+
// }
92+
// // Send bytes over custom transport
93+
func SerializeStringQuery(query string, traversalSource string, sessionId string, requestOptions RequestOptions) ([]byte, error) {
94+
request := makeStringRequest(query, traversalSource, sessionId, requestOptions)
95+
serializer := graphBinarySerializer{}
96+
return serializer.serializeMessage(&request)
97+
}
98+
99+
// DeserializeResult deserializes a response message from GraphBinary format into a Result.
100+
// This function enables receiving and processing responses from custom transport protocols.
101+
//
102+
// The function takes raw bytes received from a transport, deserializes them using the GraphBinary
103+
// deserializer, and returns a Result object containing the response data.
104+
//
105+
// Parameters:
106+
// - data: The response bytes in GraphBinary format
107+
//
108+
// Returns:
109+
// - *Result: The deserialized result containing response data
110+
// - error: Any error encountered during deserialization
111+
//
112+
// Example usage:
113+
//
114+
// // Receive bytes from custom transport
115+
// result, err := DeserializeResult(responseBytes)
116+
// if err != nil {
117+
// return err
118+
// }
119+
// value := result.Data
120+
func DeserializeResult(data []byte) (*Result, error) {
121+
serializer := graphBinarySerializer{}
122+
resp, err := serializer.deserializeMessage(data)
123+
if err != nil {
124+
return nil, err
125+
}
126+
127+
result := &Result{
128+
Data: resp.responseResult.data,
129+
}
130+
return result, nil
131+
}
132+
133+
// NewResultSet creates a new ResultSet from a slice of Result objects.
134+
// This function enables custom transport implementations to create ResultSets from
135+
// results collected via alternative protocols.
136+
//
137+
// The function creates a channel-based ResultSet, pre-populates it with the provided results,
138+
// and closes the channel to indicate completion.
139+
//
140+
// Parameters:
141+
// - results: A slice of Result objects to include in the ResultSet
142+
//
143+
// Returns:
144+
// - ResultSet: A ResultSet containing all the provided results
145+
//
146+
// Example usage:
147+
//
148+
// var results []*Result
149+
// // Collect results from custom transport
150+
// for _, responseBytes := range responses {
151+
// result, _ := DeserializeResult(responseBytes)
152+
// results = append(results, result)
153+
// }
154+
// resultSet := NewResultSet(results)
155+
// allResults, _ := resultSet.All()
156+
func NewResultSet(results []*Result) ResultSet {
157+
// Create a channel-based result set with capacity for all results
158+
channelSize := len(results)
159+
if channelSize == 0 {
160+
channelSize = 1 // Ensure at least size 1
161+
}
162+
rs := newChannelResultSetCapacity("", &synchronizedMap{make(map[string]ResultSet), sync.Mutex{}}, channelSize).(*channelResultSet)
163+
164+
// Add all results to the channel
165+
for _, result := range results {
166+
rs.channel <- result
167+
}
168+
169+
// Close the channel to indicate no more results
170+
rs.channelMutex.Lock()
171+
rs.closed = true
172+
close(rs.channel)
173+
rs.channelMutex.Unlock()
174+
175+
return rs
176+
}

0 commit comments

Comments
 (0)