diff --git a/management/content_type.go b/management/content_type.go index 7811d45..37705c6 100644 --- a/management/content_type.go +++ b/management/content_type.go @@ -37,13 +37,32 @@ type ContentTypeInput struct { } type ContentTypeOptions struct { - Title string `json:"title"` - Publishable bool `json:"bool"` - IsPage bool `json:"is_page"` - Singleton bool `json:"singleton"` - SubTitle []string `json:"sub_title"` - UrlPattern string `json:"url_pattern"` - UrlPrefix string `json:"url_prefix"` + Title string `json:"title"` + Publishable bool `json:"bool"` + IsPage bool `json:"is_page"` + Singleton bool `json:"singleton"` + SubTitle []string `json:"sub_title"` + UrlPattern FlexString `json:"url_pattern"` + UrlPrefix FlexString `json:"url_prefix"` +} + +// FlexString unmarshals a JSON value that is either a string or a boolean. +// The Contentstack API returns false (boolean) for string fields that are unset, +// rather than omitting them or returning an empty string. +type FlexString string + +func (f *FlexString) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err == nil { + *f = FlexString(s) + return nil + } + var b bool + if err := json.Unmarshal(data, &b); err == nil { + *f = "" + return nil + } + return fmt.Errorf("FlexString: cannot unmarshal %s into string or bool", data) } func (si *StackInstance) ContentTypeCreate(ctx context.Context, input ContentTypeInput) (*ContentType, error) { diff --git a/management/content_type_test.go b/management/content_type_test.go new file mode 100644 index 0000000..6a1b579 --- /dev/null +++ b/management/content_type_test.go @@ -0,0 +1,124 @@ +package management + +import ( + "encoding/json" + "testing" +) + +func TestFlexString_UnmarshalJSON(t *testing.T) { + tests := []struct { + name string + input string + want FlexString + wantErr bool + }{ + { + name: "string value", + input: `"/:title"`, + want: "/:title", + }, + { + name: "empty string", + input: `""`, + want: "", + }, + { + name: "boolean false treated as empty string", + input: `false`, + want: "", + }, + { + name: "boolean true treated as empty string", + input: `true`, + want: "", + }, + { + name: "number is invalid", + input: `42`, + wantErr: true, + }, + { + // JSON null unmarshals into bool as false in Go, so null is treated + // as empty string rather than an error. + name: "null treated as empty string", + input: `null`, + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var f FlexString + err := json.Unmarshal([]byte(tt.input), &f) + if (err != nil) != tt.wantErr { + t.Errorf("UnmarshalJSON() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && f != tt.want { + t.Errorf("UnmarshalJSON() = %q, want %q", f, tt.want) + } + }) + } +} + +func TestContentTypeOptions_UnmarshalJSON(t *testing.T) { + t.Run("url_pattern and url_prefix as strings", func(t *testing.T) { + input := `{ + "title": "My Type", + "is_page": true, + "singleton": false, + "url_pattern": "/:title", + "url_prefix": "/" + }` + var opts ContentTypeOptions + if err := json.Unmarshal([]byte(input), &opts); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if opts.UrlPattern != "/:title" { + t.Errorf("UrlPattern = %q, want %q", opts.UrlPattern, "/:title") + } + if opts.UrlPrefix != "/" { + t.Errorf("UrlPrefix = %q, want %q", opts.UrlPrefix, "/") + } + }) + + t.Run("url_pattern and url_prefix as boolean false (unset)", func(t *testing.T) { + input := `{ + "title": "My Type", + "is_page": false, + "singleton": false, + "url_pattern": false, + "url_prefix": false + }` + var opts ContentTypeOptions + if err := json.Unmarshal([]byte(input), &opts); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if opts.UrlPattern != "" { + t.Errorf("UrlPattern = %q, want empty string", opts.UrlPattern) + } + if opts.UrlPrefix != "" { + t.Errorf("UrlPrefix = %q, want empty string", opts.UrlPrefix) + } + }) + + t.Run("singleton content type without url fields", func(t *testing.T) { + input := `{ + "title": "Header", + "is_page": false, + "singleton": true, + "url_pattern": false, + "url_prefix": false + }` + var opts ContentTypeOptions + if err := json.Unmarshal([]byte(input), &opts); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !opts.Singleton { + t.Error("Singleton = false, want true") + } + if opts.UrlPattern != "" { + t.Errorf("UrlPattern = %q, want empty string", opts.UrlPattern) + } + }) +}