registry.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. package workflow
  2. import (
  3. "fmt"
  4. "regexp"
  5. "strconv"
  6. "strings"
  7. )
  8. // APIDefinition represents a third-party API endpoint configuration
  9. type APIDefinition struct {
  10. ID string `json:"id"` // Unique API identifier
  11. Method string `json:"method"` // HTTP method (GET, POST, PUT, PATCH, DELETE)
  12. URL string `json:"url"` // API endpoint URL (may contain {pathParam} placeholders)
  13. Auth string `json:"auth"` // Authentication credential reference (e.g., "SYSVAR.apiKey")
  14. Headers map[string]string `json:"headers"` // Static request headers
  15. Desc string `json:"desc"` // Human-readable description
  16. }
  17. // Registry defines external resources and global boundaries for workflow execution
  18. type Registry struct {
  19. Services []string `json:"services"` // Service signatures (VL format)
  20. APIs []APIDefinition `json:"apis"` // Third-party API endpoint definitions
  21. Components []string `json:"components"` // Component IDs
  22. Params []string `json:"params"` // Input parameters (VL format, read-only during execution)
  23. Vars []string `json:"vars"` // Global variables (VL format)
  24. Files FilesRegistry `json:"files"` // File access boundaries
  25. Docs map[string]string `json:"docs"` // Semantic document references (docId -> description)
  26. Schemas map[string]map[string]interface{} `json:"schemas"` // JSON Schema definitions for reuse (v3.9+, schemaId -> schema object)
  27. }
  28. // FilesRegistry defines file input/output boundaries
  29. type FilesRegistry struct {
  30. Inputs []string `json:"inputs"` // Read-only input files (path patterns)
  31. Artifacts []string `json:"artifacts"` // Temporary writable files (path patterns)
  32. }
  33. // ServiceSignature represents a parsed service signature
  34. type ServiceSignature struct {
  35. Name string // Service name
  36. Parameters []ParameterDef // Input parameters
  37. Returns []ParameterDef // Output fields
  38. }
  39. // ParameterDef represents a parameter definition
  40. type ParameterDef struct {
  41. Name string // Parameter name
  42. Type string // Parameter type (STRING, INT, BOOL, OBJECT, ARRAY, FILE_REF, etc.)
  43. }
  44. // VariableDeclaration represents a parsed variable declaration
  45. type VariableDeclaration struct {
  46. Name string // Variable name (with $ prefix)
  47. Type string // Variable type
  48. }
  49. // ParamDeclaration represents a parsed parameter declaration
  50. type ParamDeclaration struct {
  51. Name string // Parameter name (without $ prefix)
  52. Type string // Parameter type
  53. Default *string // Optional default value literal (spec §3.2, e.g. "3" for INT=3)
  54. }
  55. // ParseServiceSignature parses a VL-format service signature
  56. // Format: "ServiceName(param1(TYPE1), param2(TYPE2)) RETURN result1(TYPE1), result2(TYPE2)"
  57. func ParseServiceSignature(signature string) (*ServiceSignature, error) {
  58. // Example: "PlannerService(prd(STRING), rulesFile(FILE_REF)) RETURN plan(OBJECT)"
  59. // Match service name and parameters
  60. re := regexp.MustCompile(`^(\w+)\((.*?)\)\s+RETURN\s+(.*)$`)
  61. matches := re.FindStringSubmatch(strings.TrimSpace(signature))
  62. if len(matches) != 4 {
  63. return nil, fmt.Errorf("invalid service signature format: %s", signature)
  64. }
  65. serviceName := matches[1]
  66. paramsStr := matches[2]
  67. returnsStr := matches[3]
  68. sig := &ServiceSignature{
  69. Name: serviceName,
  70. }
  71. // Parse parameters
  72. if paramsStr != "" {
  73. params, err := parseParameters(paramsStr)
  74. if err != nil {
  75. return nil, fmt.Errorf("failed to parse parameters: %w", err)
  76. }
  77. sig.Parameters = params
  78. }
  79. // Parse return values
  80. if returnsStr != "" {
  81. returns, err := parseParameters(returnsStr)
  82. if err != nil {
  83. return nil, fmt.Errorf("failed to parse return values: %w", err)
  84. }
  85. sig.Returns = returns
  86. }
  87. return sig, nil
  88. }
  89. // parseParameters parses parameter list in format: "param1(TYPE1), param2(TYPE2)"
  90. func parseParameters(paramsStr string) ([]ParameterDef, error) {
  91. var params []ParameterDef
  92. // Split by comma, but need to handle nested parentheses
  93. parts := strings.Split(paramsStr, ",")
  94. for _, part := range parts {
  95. part = strings.TrimSpace(part)
  96. if part == "" {
  97. continue
  98. }
  99. // Match paramName(TYPE)
  100. re := regexp.MustCompile(`^(\w+)\(([^)]+)\)$`)
  101. matches := re.FindStringSubmatch(part)
  102. if len(matches) != 3 {
  103. return nil, fmt.Errorf("invalid parameter format: %s", part)
  104. }
  105. params = append(params, ParameterDef{
  106. Name: matches[1],
  107. Type: matches[2],
  108. })
  109. }
  110. return params, nil
  111. }
  112. // ParseVariableDeclaration parses a VL-format variable declaration
  113. // Format: "$varName(TYPE)" or "$varName([TYPE])" for arrays
  114. func ParseVariableDeclaration(declaration string) (*VariableDeclaration, error) {
  115. // Example: "$keyword(STRING)", "$items([OBJECT])", "$result(OBJECT)"
  116. re := regexp.MustCompile(`^(\$\w+)\((.+)\)$`)
  117. matches := re.FindStringSubmatch(strings.TrimSpace(declaration))
  118. if len(matches) != 3 {
  119. return nil, fmt.Errorf("invalid variable declaration format: %s", declaration)
  120. }
  121. return &VariableDeclaration{
  122. Name: matches[1],
  123. Type: matches[2],
  124. }, nil
  125. }
  126. // ParseParamDeclaration parses a VL-format parameter declaration.
  127. // Supported formats:
  128. // - "paramName(TYPE)" — no default
  129. // - "paramName([TYPE])" — array type, no default
  130. // - "paramName(TYPE) = value" — with default value (spec §3.2)
  131. //
  132. // Examples: "userId(STRING)", "filters([OBJECT])", "maxRetries(INT) = 3"
  133. func ParseParamDeclaration(declaration string) (*ParamDeclaration, error) {
  134. // Optional trailing "= <defaultValue>" after the closing paren.
  135. re := regexp.MustCompile(`^([a-zA-Z]\w*)\((.+)\)(?:\s*=\s*(.+))?$`)
  136. matches := re.FindStringSubmatch(strings.TrimSpace(declaration))
  137. if len(matches) < 3 {
  138. return nil, fmt.Errorf("invalid parameter declaration format: %s", declaration)
  139. }
  140. decl := &ParamDeclaration{
  141. Name: matches[1],
  142. Type: matches[2],
  143. }
  144. // matches[3] is the optional default value (empty string if not present)
  145. if len(matches) == 4 && matches[3] != "" {
  146. defaultVal := strings.TrimSpace(matches[3])
  147. // Strip surrounding quotes from string literals, e.g. "\"hello\"" → "hello"
  148. if len(defaultVal) >= 2 && defaultVal[0] == '"' && defaultVal[len(defaultVal)-1] == '"' {
  149. defaultVal = defaultVal[1 : len(defaultVal)-1]
  150. }
  151. decl.Default = &defaultVal
  152. }
  153. return decl, nil
  154. }
  155. // ValidateRegistry validates the registry structure and constraints
  156. func (r *Registry) ValidateRegistry() error {
  157. // Check for duplicate service names
  158. serviceNames := make(map[string]bool)
  159. for _, sig := range r.Services {
  160. parsed, err := ParseServiceSignature(sig)
  161. if err != nil {
  162. return fmt.Errorf("invalid service signature: %w", err)
  163. }
  164. if serviceNames[parsed.Name] {
  165. return fmt.Errorf("duplicate service name: %s", parsed.Name)
  166. }
  167. serviceNames[parsed.Name] = true
  168. }
  169. // Check for duplicate API IDs
  170. apiIDs := make(map[string]bool)
  171. for _, api := range r.APIs {
  172. if apiIDs[api.ID] {
  173. return fmt.Errorf("duplicate API ID: %s", api.ID)
  174. }
  175. apiIDs[api.ID] = true
  176. // Validate required fields
  177. if api.Method == "" {
  178. return fmt.Errorf("API %s: method is required", api.ID)
  179. }
  180. if api.URL == "" {
  181. return fmt.Errorf("API %s: URL is required", api.ID)
  182. }
  183. // Validate HTTP method
  184. validMethods := map[string]bool{
  185. "GET": true, "POST": true, "PUT": true,
  186. "PATCH": true, "DELETE": true,
  187. }
  188. if !validMethods[strings.ToUpper(api.Method)] {
  189. return fmt.Errorf("API %s: invalid HTTP method: %s", api.ID, api.Method)
  190. }
  191. }
  192. // Ensure APIs is initialized
  193. if r.APIs == nil {
  194. r.APIs = []APIDefinition{}
  195. }
  196. // Check for duplicate component IDs
  197. componentIDs := make(map[string]bool)
  198. for _, comp := range r.Components {
  199. if componentIDs[comp] {
  200. return fmt.Errorf("duplicate component ID: %s", comp)
  201. }
  202. componentIDs[comp] = true
  203. }
  204. // Check for duplicate parameter names
  205. paramNames := make(map[string]bool)
  206. for _, paramDecl := range r.Params {
  207. parsed, err := ParseParamDeclaration(paramDecl)
  208. if err != nil {
  209. return fmt.Errorf("invalid parameter declaration: %w", err)
  210. }
  211. if paramNames[parsed.Name] {
  212. return fmt.Errorf("duplicate parameter name: %s", parsed.Name)
  213. }
  214. paramNames[parsed.Name] = true
  215. }
  216. // Check for duplicate variable names
  217. varNames := make(map[string]bool)
  218. for _, varDecl := range r.Vars {
  219. parsed, err := ParseVariableDeclaration(varDecl)
  220. if err != nil {
  221. return fmt.Errorf("invalid variable declaration: %w", err)
  222. }
  223. if varNames[parsed.Name] {
  224. return fmt.Errorf("duplicate variable name: %s", parsed.Name)
  225. }
  226. varNames[parsed.Name] = true
  227. }
  228. // Ensure files section has both inputs and artifacts
  229. if r.Files.Inputs == nil {
  230. r.Files.Inputs = []string{}
  231. }
  232. if r.Files.Artifacts == nil {
  233. r.Files.Artifacts = []string{}
  234. }
  235. // Ensure docs is initialized
  236. if r.Docs == nil {
  237. r.Docs = make(map[string]string)
  238. }
  239. // Ensure schemas is initialized
  240. if r.Schemas == nil {
  241. r.Schemas = make(map[string]map[string]interface{})
  242. }
  243. // Validate schema IDs are not empty
  244. for schemaID := range r.Schemas {
  245. if schemaID == "" {
  246. return fmt.Errorf("schema ID cannot be empty")
  247. }
  248. }
  249. return nil
  250. }
  251. // HasDoc checks if a document ID is registered
  252. func (r *Registry) HasDoc(docID string) bool {
  253. if r.Docs == nil {
  254. return false
  255. }
  256. _, ok := r.Docs[docID]
  257. return ok
  258. }
  259. // GetDocDescription retrieves the description for a document ID
  260. func (r *Registry) GetDocDescription(docID string) (string, bool) {
  261. if r.Docs == nil {
  262. return "", false
  263. }
  264. desc, ok := r.Docs[docID]
  265. return desc, ok
  266. }
  267. // GetServiceSignature retrieves a parsed service signature by name
  268. func (r *Registry) GetServiceSignature(serviceName string) (*ServiceSignature, error) {
  269. for _, sig := range r.Services {
  270. parsed, err := ParseServiceSignature(sig)
  271. if err != nil {
  272. return nil, err
  273. }
  274. if parsed.Name == serviceName {
  275. return parsed, nil
  276. }
  277. }
  278. return nil, fmt.Errorf("service not found: %s", serviceName)
  279. }
  280. // HasComponent checks if a component is registered
  281. func (r *Registry) HasComponent(componentID string) bool {
  282. for _, comp := range r.Components {
  283. if comp == componentID {
  284. return true
  285. }
  286. }
  287. return false
  288. }
  289. // GetAPIDefinition retrieves an API definition by ID
  290. func (r *Registry) GetAPIDefinition(apiID string) (*APIDefinition, error) {
  291. for i := range r.APIs {
  292. if r.APIs[i].ID == apiID {
  293. return &r.APIs[i], nil
  294. }
  295. }
  296. return nil, fmt.Errorf("API not found: %s", apiID)
  297. }
  298. // HasAPI checks if an API is registered
  299. func (r *Registry) HasAPI(apiID string) bool {
  300. for _, api := range r.APIs {
  301. if api.ID == apiID {
  302. return true
  303. }
  304. }
  305. return false
  306. }
  307. // GetVariableDeclarations returns all parsed variable declarations
  308. func (r *Registry) GetVariableDeclarations() (map[string]*VariableDeclaration, error) {
  309. result := make(map[string]*VariableDeclaration)
  310. for _, varDecl := range r.Vars {
  311. parsed, err := ParseVariableDeclaration(varDecl)
  312. if err != nil {
  313. return nil, err
  314. }
  315. result[parsed.Name] = parsed
  316. }
  317. return result, nil
  318. }
  319. // GetParamDeclarations returns all parsed parameter declarations
  320. func (r *Registry) GetParamDeclarations() (map[string]*ParamDeclaration, error) {
  321. result := make(map[string]*ParamDeclaration)
  322. for _, paramDecl := range r.Params {
  323. parsed, err := ParseParamDeclaration(paramDecl)
  324. if err != nil {
  325. return nil, err
  326. }
  327. result[parsed.Name] = parsed
  328. }
  329. return result, nil
  330. }
  331. // IsInputPathAllowed checks if a read path is within allowed inputs
  332. func (r *Registry) IsInputPathAllowed(path string) bool {
  333. return matchesAnyPattern(path, r.Files.Inputs)
  334. }
  335. // IsArtifactPathAllowed checks if a write path is within allowed artifacts
  336. func (r *Registry) IsArtifactPathAllowed(path string) bool {
  337. return matchesAnyPattern(path, r.Files.Artifacts)
  338. }
  339. // matchesAnyPattern checks if a path matches any of the given patterns
  340. // Supports glob-like patterns with * wildcard
  341. func matchesAnyPattern(path string, patterns []string) bool {
  342. for _, pattern := range patterns {
  343. if matchPattern(path, pattern) {
  344. return true
  345. }
  346. }
  347. return false
  348. }
  349. // matchPattern performs simple glob-style pattern matching
  350. func matchPattern(path, pattern string) bool {
  351. // Simple implementation: * matches any characters
  352. // For production use, consider using filepath.Match or a more robust library
  353. if !strings.Contains(pattern, "*") {
  354. return path == pattern
  355. }
  356. // Convert glob pattern to regex
  357. regexPattern := strings.ReplaceAll(regexp.QuoteMeta(pattern), `\*`, `.*`)
  358. matched, err := regexp.MatchString("^"+regexPattern+"$", path)
  359. if err != nil {
  360. return false
  361. }
  362. return matched
  363. }
  364. // HasSchema checks if a schema ID is registered (v3.9+)
  365. func (r *Registry) HasSchema(schemaID string) bool {
  366. if r.Schemas == nil {
  367. return false
  368. }
  369. _, ok := r.Schemas[schemaID]
  370. return ok
  371. }
  372. // GetSchema retrieves a schema by ID (v3.9+)
  373. func (r *Registry) GetSchema(schemaID string) (map[string]interface{}, error) {
  374. if r.Schemas == nil {
  375. return nil, fmt.Errorf("no schemas registered")
  376. }
  377. schema, ok := r.Schemas[schemaID]
  378. if !ok {
  379. return nil, fmt.Errorf("schema not found: %s", schemaID)
  380. }
  381. return schema, nil
  382. }
  383. // CoerceParamDefault converts the string default value of a ParamDeclaration to
  384. // the appropriate Go type based on the declared type (spec §3.2).
  385. //
  386. // Supported type coercions:
  387. // - INT → int64
  388. // - FLOAT → float64
  389. // - BOOL → bool
  390. // - all others (STRING, OBJECT, ARRAY, etc.) → string (already stripped of quotes)
  391. //
  392. // Returns nil, nil if the declaration has no default.
  393. func CoerceParamDefault(decl *ParamDeclaration) (interface{}, error) {
  394. if decl.Default == nil {
  395. return nil, nil
  396. }
  397. raw := *decl.Default
  398. switch strings.ToUpper(decl.Type) {
  399. case "INT":
  400. v, err := strconv.ParseInt(raw, 10, 64)
  401. if err != nil {
  402. return nil, fmt.Errorf("param %s: cannot parse default %q as INT: %w", decl.Name, raw, err)
  403. }
  404. return v, nil
  405. case "FLOAT":
  406. v, err := strconv.ParseFloat(raw, 64)
  407. if err != nil {
  408. return nil, fmt.Errorf("param %s: cannot parse default %q as FLOAT: %w", decl.Name, raw, err)
  409. }
  410. return v, nil
  411. case "BOOL":
  412. v, err := strconv.ParseBool(raw)
  413. if err != nil {
  414. return nil, fmt.Errorf("param %s: cannot parse default %q as BOOL: %w", decl.Name, raw, err)
  415. }
  416. return v, nil
  417. default:
  418. // STRING and all other types: return as-is (quotes already stripped by ParseParamDeclaration)
  419. return raw, nil
  420. }
  421. }