mirror of
https://github.com/larksuite/cli.git
synced 2026-09-14 18:42:53 +08:00
7690ba4446
+create, +add-slide, +update-slide and +replace-slide are the four shortcuts that change slide content, so they are the four that can ask the backend to check the page before accepting the write. All four now send lint_xml=true by default and lint_xml=false when --no-lint is passed. The subject of the check is the page the write produces, not the payload it was handed: +replace-slide submits fragments, and a fragment that is correct on its own can still push a neighbour off the canvas. The switch travels in the request body rather than the query string. A query parameter has to be declared in the gateway's own api meta before it is bound to a field, and the published definition of these endpoints does not list one — so an undeclared parameter is dropped, the field arrives unset, and the server reads it as "not requested". Verified against a live backend: pages that asked to be linted were written unlinted, with nothing anywhere to say so. Body fields ride along with the JSON already being sent and need no registration. The value is sent explicitly in both directions rather than omitted when on. The parameter is newer than the registry, so the server-side default is not something this CLI can read anywhere, and a request that states the value keeps meaning the same thing if that default ever moves. A refusal is passed through verbatim. The message field carries the lint report itself — the same document the lint tool writes when it is run by hand — and the same refusal reaches callers through `lark-cli api` as well, where nothing rewrites it. Rendering it to prose here would give one refusal two formats depending on which command produced it. Each finding carries the numbers behind its own rule, and which numbers those are differs per rule, so nothing is decoded that is not used: the report is what the caller reads. The refusal is recognised by its error code, 4000153, which the engine raises for nothing else and which reaches the CLI unchanged. Matching on the shape of the message instead would mean claiming any JSON that resembles a report, and a false positive there rewrites the hint of an error this code does not understand. What the backend cannot say goes in the hint instead: how many findings refused the write, that the page did not land, and --no-lint, which is a CLI flag the server has never heard of. The count is summary.error_count rather than the number of findings, because errors are what refuse a page — the same line the lint tool draws when it is run by hand, exiting non-zero on error_count alone. A report can arrive with warnings beside its errors, and counting those too would send the caller hunting for blockers that are not there. A message that does not parse still gets the hint: the escape hatch is the half of it they cannot get anywhere else, and withholding it over a missing number helps nobody. The hint names no page. Every write path submits exactly one page, so a finding's slide_number is its position inside that submission and is always 1 — which is not the page the caller is looking for. On +create it is actively wrong: it would read "on slide 1" next to a progress line saying "adding slide 2/3 failed". The page number has one source, and it is that line. Findings that did not refuse the write come back the other way. The backend returns them in an issues field on a response that succeeded, and all four shortcuts now pass that field through untouched rather than dropping it. It only ever arrives on a page that was written: anything serious enough to refuse the write left as the error above, carrying the same report. Dropping it would leave the caller believing the deck says exactly what they wrote, with no way to learn otherwise short of looking at the rendered page. It is passed through rather than reformatted so that one field reads the same however the page was written. +create keeps adding its pages one at a time, so a refusal there can arrive with the presentation and some of its pages already written. It is reported as such: the error carries the lint report and, next to it, which page was refused and how many landed before it, so the retry adds the rest instead of building a second deck. +replace-pages does the same for the items in its plan. A batch that was told to keep going reports its failures only through the per-item records, so those carry the report, the code and the flag hint as well; a record built from the error's message alone would have named neither the refusal nor the way past it. The position stays on the returning path, where it is the only thing that says how far the batch got — beside a per-item record it would describe a batch that did not stop. Tests assert on the wire — the body the stub actually received — rather than on the builder's return value, so a command that stops calling its own builder still fails.
67 lines
3.1 KiB
Go
67 lines
3.1 KiB
Go
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package slides
|
|
|
|
import "github.com/larksuite/cli/shortcuts/common"
|
|
|
|
// The server-side XML lint switch, carried by every shortcut that writes slide
|
|
// content: +create, +add-slide, +update-slide and +replace-slide.
|
|
//
|
|
// The backend lints the page the write produces and refuses it when that page
|
|
// fails. Note "produces", not "is handed": +replace-slide submits fragments, and
|
|
// the subject of the lint is the assembled page, so a fragment that is valid on
|
|
// its own is still refused when it pushes a neighbour off the canvas. The CLI
|
|
// asks for the check on every call, so a caller who never touches the flag gets
|
|
// the checked path; --no-lint is the escape hatch for the case the lint is wrong
|
|
// about a page that must still ship.
|
|
//
|
|
// Sent explicitly in both directions rather than omitted when on: the parameter
|
|
// is newer than internal/registry/meta_data.json, so the server-side default is
|
|
// not something this CLI can read anywhere, and a request that states the value
|
|
// means the same thing before and after that default ever changes.
|
|
const noLintFlagName = "no-lint"
|
|
|
|
// lintXMLBodyKey is the wire name the switch binds to, and it travels in the
|
|
// request body rather than the query string.
|
|
//
|
|
// That is not a style choice. A query parameter has to be declared in the
|
|
// gateway's own api meta before it is bound to a field, and the published
|
|
// definition of these endpoints lists only xml_presentation_id, revision_id
|
|
// and idempotency_key. Until that publish happens the gateway drops an
|
|
// undeclared parameter on the floor: the request succeeds, the field arrives
|
|
// unset, and the server reads it as "lint not requested". Verified against a
|
|
// live backend — pages that asked to be linted were written unlinted, with no
|
|
// error anywhere to say so. Body fields ride along with the JSON already being
|
|
// sent and need no separate registration.
|
|
//
|
|
// snake_case matches every other body key these endpoints take: slide,
|
|
// before_slide_id, parts, comment.
|
|
//
|
|
// Worth knowing when this is next touched: a wrong name here still fails
|
|
// silently, for the same reason it did in the query. Tests and --dry-run only
|
|
// prove what the CLI sent, not what was read.
|
|
const lintXMLBodyKey = "lint_xml"
|
|
|
|
// noLintFlag is the shared flag definition, so the three commands cannot drift
|
|
// apart on the name or the wording.
|
|
func noLintFlag() common.Flag {
|
|
return common.Flag{
|
|
Name: noLintFlagName,
|
|
Type: "bool",
|
|
Desc: "submit the XML without the server-side lint; by default the server lints every page and rejects the write when it fails",
|
|
}
|
|
}
|
|
|
|
// withLintXML stamps the switch onto a request body and returns it, so the body
|
|
// builders stay one-liners and dry-run and execute cannot disagree about the
|
|
// value. A nil map is allocated rather than rejected, so a caller with nothing
|
|
// else to send still gets a well-formed body.
|
|
func withLintXML(body map[string]interface{}, runtime *common.RuntimeContext) map[string]interface{} {
|
|
if body == nil {
|
|
body = map[string]interface{}{}
|
|
}
|
|
body[lintXMLBodyKey] = !runtime.Bool(noLintFlagName)
|
|
return body
|
|
}
|