spmallette commented on code in PR #3238: URL: https://github.com/apache/tinkerpop/pull/3238#discussion_r2436966975
########## gremlin-mcp/src/main/javascript/src/gremlin/models/query-result.ts: ########## @@ -0,0 +1,143 @@ +/* + * 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. + */ + +/** + * @fileoverview Query result models for Gremlin database responses. + */ + +import { z } from 'zod'; + +/** + * Gremlin vertex with full structure. + */ +export const GremlinVertexSchema = z.object({ + id: z.union([z.string(), z.number(), z.object({})]), + label: z.string().min(1, 'Vertex label cannot be empty'), + properties: z.record(z.string(), z.array(z.unknown())).optional(), + type: z.literal('vertex'), +}); + +export type GremlinVertex = z.infer<typeof GremlinVertexSchema>; + +/** + * Gremlin edge with full structure. + */ +export const GremlinEdgeSchema = z.object({ + id: z.union([z.string(), z.number(), z.object({})]), + label: z.string().min(1, 'Edge label cannot be empty'), + inV: z.union([z.string(), z.number(), z.object({})]), + outV: z.union([z.string(), z.number(), z.object({})]), + properties: z.record(z.string(), z.array(z.unknown())).optional(), + type: z.literal('edge'), +}); + +export type GremlinEdge = z.infer<typeof GremlinEdgeSchema>; + +/** + * Gremlin property map (from valueMap() queries). + */ +export const GremlinPropertyMapSchema = z.record(z.array(z.unknown())); +export type GremlinPropertyMap = z.infer<typeof GremlinPropertyMapSchema>; + +/** + * Gremlin path result (from path() queries). + */ +export const GremlinPathSchema = z.object({ + labels: z.array(z.string()), + objects: z.array(z.unknown()), + type: z.literal('path'), +}); + +export type GremlinPath = z.infer<typeof GremlinPathSchema>; + +/** + * Gremlin property result. + */ +export const GremlinPropertySchema = z.object({ + key: z.string(), + value: z.unknown(), + type: z.literal('property'), +}); + +export type GremlinProperty = z.infer<typeof GremlinPropertySchema>; + +/** + * Structured Gremlin result types with discriminated union. + */ +export const GremlinStructuredResultSchema = z.discriminatedUnion('type', [ + GremlinVertexSchema, + GremlinEdgeSchema, + GremlinPathSchema, + GremlinPropertySchema, +]); + +/** + * Generic objects without 'type' field. + */ +const GenericObjectSchema = z + .record(z.unknown()) + .refine(obj => !('type' in obj) || typeof obj['type'] !== 'string', { + message: "Objects with 'type' field must use structured schemas", + }); + +/** + * Recursive array validation. + */ +const ValidatedArraySchema: z.ZodType<unknown[]> = z.lazy(() => z.array(GremlinResultItemSchema)); + +/** + * Union type for all possible Gremlin query results. + */ +export const GremlinResultItemSchema = z.union([ + GremlinStructuredResultSchema, + GremlinPropertyMapSchema, + z.string(), + z.number(), + z.boolean(), + z.null(), + ValidatedArraySchema, + GenericObjectSchema, +]); + +export type GremlinStructuredResult = z.infer<typeof GremlinStructuredResultSchema>; +export type GremlinResultItem = z.infer<typeof GremlinResultItemSchema>; + +/** + * Gremlin query result with typed results and status. + */ +export const GremlinQueryResultSchema = z.object({ + /** Query results array with typed items */ + results: z.array(GremlinResultItemSchema), + /** Status message about the query execution */ + message: z.string(), +}); + +export type GremlinQueryResult = z.infer<typeof GremlinQueryResultSchema>; + +/** + * Input schema for Gremlin query operations. + */ +export const GremlinQueryInputSchema = z.object({ + query: z + .string() + .min(1, 'Query cannot be empty') + .describe('The Gremlin query to execute against the graph database'), Review Comment: yeah - i thought about this. not sure i want to do regex validation here for that. i think the best protection from crazy stuff is what i documented: use `ReadOnlyStrategy` on your graph and protect it from mutation that way. I did add some basic validation here to at least check for "g." as a starting part to the string. i think we should also switch to doing a client-side conversion of the Gremlin generated by the LLM to native Gremlin. then we could easily inject a `ReadOnlyStrategy` client side as part of a configuration. Something for the future. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
