{"record":{"id":"39215cdad81ac5ce","repo":"siyuan-note/siyuan","slug":"fields-mode-and-modeos-must-be-provided-together","errorCode":null,"errorMessage":"Fields [mode] and [modeOS] must be provided together","messagePattern":"Fields \\[mode\\] and \\[modeOS\\] must be provided together","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/apicontract/bazaar.go","lineNumber":916,"sourceCode":"\t\tif request.Keyword, err = bazaarString(fields, \"keyword\", false, false); err != nil {\n\t\t\treturn request, err\n\t\t}\n\t\tif request.RepoURL, err = bazaarString(fields, \"repoURL\", true, true); err != nil {\n\t\t\treturn request, err\n\t\t}\n\t\tif request.RepoHash, err = bazaarString(fields, \"repoHash\", true, true); err != nil {\n\t\t\treturn request, err\n\t\t}\n\t\tif request.RepoRef, err = bazaarString(fields, \"repoRef\", false, false); err != nil {\n\t\t\treturn request, err\n\t\t}\n\t\tif request.PackageName, err = bazaarString(fields, \"packageName\", true, true); err != nil {\n\t\t\treturn request, err\n\t\t}\n\t\t_, hasMode := fields[\"mode\"]\n\t\t_, hasModeOS := fields[\"modeOS\"]\n\t\tif hasMode != hasModeOS {\n\t\t\treturn request, errors.New(\"Fields [mode] and [modeOS] must be provided together\")\n\t\t}\n\t\tif hasMode {\n\t\t\tmode, parseErr := legacyField[float64](fields, \"mode\", \"Number\", true)\n\t\t\tif parseErr != nil {\n\t\t\t\treturn request, parseErr\n\t\t\t}\n\t\t\tmodeOS, parseErr := legacyField[bool](fields, \"modeOS\", \"Boolean\", true)\n\t\t\tif parseErr != nil {\n\t\t\t\treturn request, parseErr\n\t\t\t}\n\t\t\tif mode != 0 && mode != 1 {\n\t\t\t\treturn request, errors.New(\"Field [mode] must be 0 or 1\")\n\t\t\t}\n\t\t\trequest.Mode = &mode\n\t\t\trequest.ModeOS = &modeOS\n\t\t}\n\t\treturn request, nil\n\t}","sourceCodeStart":898,"sourceCodeEnd":934,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/apicontract/bazaar.go#L898-L934","documentation":"The bazaar install endpoint accepts an optional legacy pairing of fields: mode (target type: 0 = app, 1 = frontend plugin context) and modeOS (OS-specific flag). These two must always be sent together; sending only one is ambiguous and rejected by the decoder. This preserves compatibility with older clients while enforcing a consistent contract.","triggerScenarios":"POSTing to the bazaar install endpoint with {\"packageName\":\"x\",\"mode\":0} (missing modeOS) or {\"packageName\":\"x\",\"modeOS\":false} (missing mode).","commonSituations":"Partial upgrades of client code where one field was added but its sibling wasn't, hand-written curl payloads copied from outdated docs, or plugin code that sets only modeOS to target a specific OS.","solutions":["Add the missing sibling field so both mode and modeOS are present in the same request.","Remove both fields entirely if you do not need the legacy mode selection.","Check the caller's payload-construction code to ensure it sets the pair atomically.","Consult the current API contract docs for the correct mode/modeOS value pair."],"exampleFix":"// before\n{\"packageName\": \"siyuan-plugin-x\", \"mode\": 1}\n// after\n{\"packageName\": \"siyuan-plugin-x\", \"mode\": 1, \"modeOS\": false}","handlingStrategy":"validation","validationCode":"const keys = Object.keys(payload);\nif (keys.includes(\"mode\") !== keys.includes(\"modeOS\")) {\n  throw new Error(\"mode and modeOS must be sent together\");\n}","typeGuard":"const hasBothOrNeither = (p) => ((\"mode\" in p) === (\"modeOS\" in p));","tryCatchPattern":"try {\n  await post(\"/api/bazaar\", payload);\n} catch (e) {\n  if (String(e.message).includes(\"must be provided together\")) {\n    payload.modeOS = payload.modeOS ?? false;\n    await post(\"/api/bazaar\", payload);\n  } else { throw e; }\n}","preventionTips":["Set mode and modeOS in the same code path / single object literal","Keep payload templates in sync with the current API contract","Add a shared payload schema test for the bazaar install endpoint","Remove both fields together when dropping legacy mode selection"],"tags":["validation","api","required-fields","pairing"],"backgroundTag":"mutually-exclusive-options","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}