{"record":{"id":"9f81d8106304c0f9","repo":"d2lang/d2","slug":"can-only-set-one-edge-at-a-time","errorCode":null,"errorMessage":"can only set one edge at a time","messagePattern":"can only set one edge at a time","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"d2oracle/edit.go","lineNumber":340,"sourceCode":"\t}\n\treturn g2, nil\n}\n\n// TODO merge flat styles\nfunc _set(g *d2graph.Graph, baseAST *d2ast.Map, key string, tag, value *string) error {\n\tif tag != nil {\n\t\tif hasSpace(*tag) {\n\t\t\treturn fmt.Errorf(\"spaces are not allowed in blockstring tags\")\n\t\t}\n\t}\n\n\tmk, err := d2parser.ParseMapKey(key)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif len(mk.Edges) > 1 {\n\t\treturn errors.New(\"can only set one edge at a time\")\n\t}\n\n\tif value != nil {\n\t\tmk.Value = d2ast.MakeValueBox(d2ast.RawString(*value, false))\n\t} else {\n\t\tmk.Value = d2ast.ValueBox{}\n\t}\n\tif tag != nil && value != nil {\n\t\tmk.Value = d2ast.MakeValueBox(&d2ast.BlockString{\n\t\t\tTag:   *tag,\n\t\t\tValue: *value,\n\t\t})\n\t}\n\n\tscope := baseAST\n\tedgeTrimCommon(mk)\n\tobj := g.Root\n\ttoSkip := 1","sourceCodeStart":322,"sourceCodeEnd":358,"githubUrl":"https://github.com/d2lang/d2/blob/0d69dca6f532ceaeacd615d35d1eaa41a238ffdb/d2oracle/edit.go#L322-L358","documentation":"d2oracle's Set/Create path (`_set`) supports addressing at most one edge per key. The parsed key contained a multi-edge chain (e.g. `(a -> b -> c)` produces two edges), which is ambiguous for setting a single value, so it is rejected.","triggerScenarios":"Calling d2oracle.Set or d2oracle.Create with a key containing a chain of two or more edges, e.g. `x.(a -> b -> c).style.opacity` or `(a <-> b <-> c)`.","commonSituations":"Users typing chained edges in a UI that forwards the raw key to Set; code generators that compose chains from path data; copying a valid D2 document key into the oracle API without splitting it.","solutions":["Split the chain and address a single edge with an index: `(a -> b -> c)[0].style.opacity` instead of `(a -> b -> c).style.opacity`.","Perform one Set call per edge segment.","If the goal is to set an attribute of an object rather than an edge, drop the edge portion from the key.","Validate len(mk.Edges) <= 1 client-side before calling Set/Create."],"exampleFix":"// before\ng, err = d2oracle.Set(g, nil, \"(a -> b -> c).style.opacity\", &val)\n// after\ng, err = d2oracle.Set(g, nil, \"(a -> b -> c)[0].style.opacity\", &val)","handlingStrategy":"validation","validationCode":"mk, err := d2parser.ParseMapKey(key)\nif err != nil { return err }\nif len(mk.Edges) > 1 {\n    return fmt.Errorf(\"key %q contains %d edges; split and address one with an [index]\", key, len(mk.Edges))\n}","typeGuard":"func isSingleEdgeKey(key string) bool {\n    mk, err := d2parser.ParseMapKey(key)\n    return err == nil && len(mk.Edges) <= 1\n}","tryCatchPattern":"err := d2oracle.Set(g, nil, key, &val)\nif err != nil && err.Error() == \"can only set one edge at a time\" {\n    return fmt.Errorf(\"split chain key %q into indexed single-edge keys\", key)\n}","preventionTips":["Never pass chain keys like (a -> b -> c) to Set/Create; always add an [index]","Sanitize user-provided keys in editors before forwarding to d2oracle","Add a unit test asserting all generated keys parse to a single edge","Remember <-> also composes chains when repeated"],"tags":["edge","api-misuse","d2"],"backgroundTag":"multiple-edges-not-allowed","analyzedSha":"0d69dca6f532ceaeacd615d35d1eaa41a238ffdb","analyzedAt":"2026-08-31T12:19:21.182Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}