Merge branch 'feat/schema-calendar-format-migration' into feat/calendar-event-all-day-online-meeting

This commit is contained in:
fengbai
2026-09-09 16:36:23 +08:00
4 changed files with 80 additions and 8 deletions
+6 -4
View File
@@ -1051,11 +1051,13 @@ func (p ParameterSpec) ToPayload() (map[string]any, error) {
if p.Format != "" || p.Type != "string" {
return nil, fmt.Errorf("parameter %q anyOf requires string type and no top-level format", p.Name)
}
value, err := typedJSONValue(p.AnyOf)
if err != nil {
return nil, err
// FormatAlternative contains only a string: project the JSON shape
// directly instead of introducing an impossible marshal-error path.
branches := make([]any, len(p.AnyOf))
for i, branch := range p.AnyOf {
branches[i] = map[string]any{"format": branch.Format}
}
payload["anyOf"] = value
payload["anyOf"] = branches
}
if len(p.Enum) > 0 {
payload["enum"] = append([]string(nil), p.Enum...)
@@ -3,10 +3,12 @@ package cli
import (
"encoding/json"
"reflect"
"strings"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contractfinal"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/runtimeannotate"
"github.com/spf13/cobra"
)
@@ -90,3 +92,56 @@ func TestCrossPlatformCoverageCatalogFormatAlternatives(t *testing.T) {
})
}
}
func TestCrossPlatformCoverageSchemaFormatAlternativesRejectInvalidAnnotations(t *testing.T) {
for _, tc := range []struct {
name string
values []string
format string
want string
}{
{"missing value", nil, "", "invalid or conflicting"},
{"multiple values", []string{"[]", "[]"}, "", "invalid or conflicting"},
{"explicit format", []string{`[{"format":"date"},{"format":"date-time"}]`}, "date-time", "invalid or conflicting"},
{"invalid json", []string{"["}, "", "anyOf:"},
{"unknown branch field", []string{`[{"pattern":".*"}]`}, "", "unknown field"},
} {
t.Run(tc.name, func(t *testing.T) {
cmd := &cobra.Command{Use: "sample"}
cmd.Flags().String("time", "", "time")
cmd.Flags().Lookup("time").Annotations = map[string][]string{runtimeannotate.AnnotationFlagAnyOf: tc.values}
if tc.format != "" {
runtimeannotate.AnnotateRuntimeFlagFormat(cmd, "time", tc.format)
}
_, err := runtimeCommandParameterSpecs(cmd, "sample.run", RuntimeSchemaConstraints{})
if err == nil || !strings.Contains(err.Error(), tc.want) {
t.Fatalf("error=%v, want %q", err, tc.want)
}
})
}
}
func TestCrossPlatformCoverageSchemaFormatAlternativesPayloadAndProvenance(t *testing.T) {
union := []contract.FormatAlternative{{Format: "date"}, {Format: "date-time"}}
for _, p := range []ParameterSpec{{Name: "time", Type: "integer", AnyOf: union}, {Name: "time", Type: "string", Format: "date-time", AnyOf: union}} {
if _, err := p.ToPayload(); err == nil || !strings.Contains(err.Error(), "no top-level format") {
t.Fatalf("invalid combination accepted: %v", err)
}
}
p := ParameterSpec{Name: "time", Type: "string", Property: "startDateTime", AnyOf: union}
value, ok := p.provenanceValue("anyOf")
if !ok || !reflect.DeepEqual(value, union) {
t.Fatalf("provenance value: %v, %v", value, ok)
}
p.FieldProvenance = schemaDeliveryTestProvenance(map[string]any{
"type": "string", "description": "", "property": "startDateTime", "required": false, "required_when": "", "anyOf": union,
})
registry := schemaDeliveryTestRegistry(schemaDeliveryTestTool{Canonical: "sample.run", CLIPath: "sample run", Parameters: []ParameterSpec{p}})
if err := validateFinalSchemaProvenanceCoverage(registry); err != nil {
t.Fatalf("valid provenance rejected: %v", err)
}
delete(registry.Products[0].Tools[0].Parameters[0].FieldProvenance, "anyOf")
if err := validateFinalSchemaProvenanceCoverage(registry); err == nil || !strings.Contains(err.Error(), "has no provenance for anyOf") {
t.Fatalf("missing provenance accepted: %v", err)
}
}
@@ -57,3 +57,19 @@ func TestCrossPlatformCoverageParamFormatAlternativesClone(t *testing.T) {
t.Fatal("read exposed stored slice")
}
}
func TestCrossPlatformCoverageParamFormatAlternativesAnnotation(t *testing.T) {
cmd := &cobra.Command{Use: "sample"}
cmd.Flags().String("time", "", "")
branches := []contract.FormatAlternative{{Format: "date-time"}, {Format: "date"}}
if err := ApplyParamDecls(cmd, []contract.ParamDecl{{Name: "time", AnyOf: branches}}); err != nil {
t.Fatal(err)
}
got := cmd.Flags().Lookup("time").Annotations[runtimeannotate.AnnotationFlagAnyOf]
if len(got) != 1 || got[0] != `[{"format":"date"},{"format":"date-time"}]` {
t.Fatalf("annotation=%v", got)
}
if branches[0].Format != "date-time" {
t.Fatal("annotation sorting mutated declaration")
}
}
+3 -4
View File
@@ -671,10 +671,9 @@ func normalizeParameter(raw json.RawMessage) (parameterSchema, error) {
if err := json.Unmarshal(raw, &schema); err != nil {
return parameterSchema{}, err
}
anyOf, err := canonicalRawJSON(parameter.AnyOf)
if err != nil {
return parameterSchema{}, err
}
// Both decodes above already validated this RawMessage as JSON.
// Canonicalizing that validated fragment cannot fail.
anyOf, _ := canonicalRawJSON(parameter.AnyOf)
parameterType := schemaType(schema)
if parameterType == "unspecified" {
return parameterSchema{}, fmt.Errorf("type is missing")