176 lines
4.4 KiB
Go
176 lines
4.4 KiB
Go
package platform
|
|
|
|
import (
|
|
"encoding/json"
|
|
"math"
|
|
"mime"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
func canvasSchema(raw json.RawMessage) int {
|
|
var d struct {
|
|
Version int `json:"schema_version"`
|
|
}
|
|
_ = json.Unmarshal(raw, &d)
|
|
return d.Version
|
|
}
|
|
|
|
func acceptsCanvasV2(r *http.Request) bool {
|
|
for _, value := range strings.Split(r.Header.Get("Accept"), ",") {
|
|
typ, params, err := mime.ParseMediaType(strings.TrimSpace(value))
|
|
if err == nil && typ == "application/vnd.foundry.canvas+json" && params["version"] == "2" {
|
|
if value, exists := params["q"]; exists {
|
|
q, e := strconv.ParseFloat(value, 64)
|
|
if e != nil || !(q > 0 && q <= 1) {
|
|
continue
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func canvasPoint(v any) bool {
|
|
p, ok := v.(map[string]any)
|
|
return ok && canvasFields(p, "x", "y") && canvasNumber(p["x"], -1e6, 1e6) && canvasNumber(p["y"], -1e6, 1e6)
|
|
}
|
|
|
|
func validCanvasEdgeData(v any) bool {
|
|
d, ok := v.(map[string]any)
|
|
if !ok || !canvasFields(d, "appearance", "animation", "path", "execution") {
|
|
return false
|
|
}
|
|
for key, value := range d {
|
|
m, ok := value.(map[string]any)
|
|
if !ok {
|
|
return false
|
|
}
|
|
switch key {
|
|
case "appearance":
|
|
if !canvasFields(m, "color", "width", "opacity", "line", "head", "tail") {
|
|
return false
|
|
}
|
|
for k, v := range m {
|
|
switch k {
|
|
case "color":
|
|
s, ok := v.(string)
|
|
if !ok || !canvasColor.MatchString(s) {
|
|
return false
|
|
}
|
|
case "width":
|
|
if !canvasNumber(v, .5, 20) {
|
|
return false
|
|
}
|
|
case "opacity":
|
|
if !canvasNumber(v, .05, 1) {
|
|
return false
|
|
}
|
|
case "line":
|
|
if !canvasChoice(v, "solid", "dashed", "dotted") {
|
|
return false
|
|
}
|
|
case "head", "tail":
|
|
marker, ok := v.(map[string]any)
|
|
if !ok || !canvasFields(marker, "type", "size", "filled") || !canvasChoice(marker["type"], "none", "open-arrow", "triangle", "circle", "diamond", "square") {
|
|
return false
|
|
}
|
|
if v, exists := marker["size"]; exists && !canvasNumber(v, 4, 32) {
|
|
return false
|
|
}
|
|
if v, exists := marker["filled"]; exists {
|
|
if _, ok := v.(bool); !ok {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case "animation":
|
|
if !canvasFields(m, "mode", "effect", "direction", "durationMs", "color") {
|
|
return false
|
|
}
|
|
for k, v := range m {
|
|
switch k {
|
|
case "mode":
|
|
if !canvasChoice(v, "off", "always", "executing") {
|
|
return false
|
|
}
|
|
case "effect":
|
|
if !canvasChoice(v, "flow", "dash", "pulse") {
|
|
return false
|
|
}
|
|
case "direction":
|
|
if !canvasChoice(v, "forward", "reverse") {
|
|
return false
|
|
}
|
|
case "durationMs":
|
|
if !canvasNumber(v, 250, 30000) {
|
|
return false
|
|
}
|
|
case "color":
|
|
s, ok := v.(string)
|
|
if !ok || !canvasColor.MatchString(s) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
case "execution":
|
|
if !canvasFields(m, "enabled") {
|
|
return false
|
|
}
|
|
if v, exists := m["enabled"]; exists {
|
|
if _, ok := v.(bool); !ok {
|
|
return false
|
|
}
|
|
}
|
|
case "path":
|
|
if !canvasFields(m, "type", "startControl", "endControl", "knots") || m["type"] != "bezier-chain" {
|
|
return false
|
|
}
|
|
for _, k := range []string{"startControl", "endControl"} {
|
|
if v, exists := m[k]; exists && !canvasPoint(v) {
|
|
return false
|
|
}
|
|
}
|
|
knots, ok := m["knots"].([]any)
|
|
if !ok || len(knots) > 64 {
|
|
return false
|
|
}
|
|
seen := map[string]bool{}
|
|
for _, v := range knots {
|
|
knot, ok := v.(map[string]any)
|
|
if !ok || !canvasFields(knot, "id", "position", "in", "out", "mode") {
|
|
return false
|
|
}
|
|
id, ok := knot["id"].(string)
|
|
if !ok || !canvasID.MatchString(id) || seen[id] {
|
|
return false
|
|
}
|
|
seen[id] = true
|
|
for _, k := range []string{"position", "in", "out"} {
|
|
if !canvasPoint(knot[k]) {
|
|
return false
|
|
}
|
|
}
|
|
if !canvasChoice(knot["mode"], "corner", "smooth", "symmetric") {
|
|
return false
|
|
}
|
|
if knot["mode"] != "corner" {
|
|
a, b := knot["in"].(map[string]any), knot["out"].(map[string]any)
|
|
ax, ay, bx, by := a["x"].(float64), a["y"].(float64), b["x"].(float64), b["y"].(float64)
|
|
la, lb := math.Hypot(ax, ay), math.Hypot(bx, by)
|
|
if la > 0 && lb > 0 && (math.Abs((ax/la)*(by/lb)-(ay/la)*(bx/lb)) > 1e-6 || (ax/la)*(bx/lb)+(ay/la)*(by/lb) > 0) {
|
|
return false
|
|
}
|
|
if knot["mode"] == "symmetric" && math.Abs(la-lb) > 1e-6*math.Max(la, lb) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return true
|
|
}
|