diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 65f558e..656a2ef 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "2.0.0" + ".": "2.1.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 96e30e1..333dfb4 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 43 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-c7ad6f552b38f2145781847f8b390fa1ec43068d64e45a33012a97a9299edc10.yml -openapi_spec_hash: 50f281e91210ad5018ac7e4eee216f56 -config_hash: 74a8263b80c732a2b016177e7d56bb9c +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/imagekit-inc%2Fimagekit-9d184cb502ab32a85db2889c796cdfebe812f2a55a604df79c85dd4b5e7e2add.yml +openapi_spec_hash: a9aa620376fce66532c84f9364209b0b +config_hash: 71cab8223bb5610c6c7ca6e9c4cc1f89 diff --git a/CHANGELOG.md b/CHANGELOG.md index 488241b..ca55236 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,36 @@ # Changelog +## 2.1.0 (2025-12-19) + +Full Changelog: [v2.0.0...v2.1.0](https://github.com/imagekit-developer/imagekit-go/compare/v2.0.0...v2.1.0) + +### Features + +* **api:** add GetImageAttributesOptions and ResponsiveImageAttributes schemas; update resource references in main.yaml; remove dummy endpoint ([41072da](https://github.com/imagekit-developer/imagekit-go/commit/41072da63cd2ba891a911d932af3bc8b70c90588)) +* **api:** fix go sdk breaking changes ([6cbddff](https://github.com/imagekit-developer/imagekit-go/commit/6cbddffab95c89b964fc29ce119ceb70d7ebded5)) +* **encoder:** support bracket encoding form-data object members ([cb3e557](https://github.com/imagekit-developer/imagekit-go/commit/cb3e5572b00fe978ee93d595cc4d8775edccbc89)) + + +### Bug Fixes + +* **client:** correctly specify Accept header with */* instead of empty ([21a30a4](https://github.com/imagekit-developer/imagekit-go/commit/21a30a4d60b4b2da84d2314ddf6bcf76759da64d)) +* **client:** properly marshal embedded structs ([e55e614](https://github.com/imagekit-developer/imagekit-go/commit/e55e614fab629dfab68d7249e53ef602dd1a36b3)) +* **docs:** update go get command to include version path in README.md ([d7d4c82](https://github.com/imagekit-developer/imagekit-go/commit/d7d4c829ebccafd1242d79a03651f1189c9f24d0)) +* **mcp:** correct code tool API endpoint ([b32395e](https://github.com/imagekit-developer/imagekit-go/commit/b32395e36a3fdd2f8e37313a303a68135f13400f)) +* rename param to avoid collision ([5067fd4](https://github.com/imagekit-developer/imagekit-go/commit/5067fd4adfe3a7108f3799f270c4211ade385882)) +* skip usage tests that don't work with Prism ([429ad75](https://github.com/imagekit-developer/imagekit-go/commit/429ad75eb8c267b44a9c8e4f2c542344189563e3)) + + +### Chores + +* add float64 to valid types for RegisterFieldValidator ([2dc3cae](https://github.com/imagekit-developer/imagekit-go/commit/2dc3cae63386dc6b8c7743af3fdb0a9c4ed93ef5)) +* bump gjson version ([87ad44d](https://github.com/imagekit-developer/imagekit-go/commit/87ad44d7016dcb641158e26cc4f0d08e89770dc5)) +* elide duplicate aliases ([2f9eee1](https://github.com/imagekit-developer/imagekit-go/commit/2f9eee11c82bd0b6641dec1f0c20042863ad169f)) +* **internal:** codegen related update ([8877b4f](https://github.com/imagekit-developer/imagekit-go/commit/8877b4fbdce39a56785613a1172dda44399c6fe7)) +* **internal:** codegen related update ([d83769d](https://github.com/imagekit-developer/imagekit-go/commit/d83769df0486737f479694acc9421415fb11c523)) +* **internal:** codegen related update ([63165ac](https://github.com/imagekit-developer/imagekit-go/commit/63165ac51ec10df842ed8f9496c82294b4f9e61e)) +* **internal:** grammar fix (it's -> its) ([e35e192](https://github.com/imagekit-developer/imagekit-go/commit/e35e1922f7ad2541ed116db557bcedd8c9c088de)) + ## 2.0.0 (2025-10-05) Full Changelog: [v0.0.1...v2.0.0](https://github.com/imagekit-developer/imagekit-go/compare/v0.0.1...v2.0.0) diff --git a/README.md b/README.md index 58286aa..9e5488d 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Or to pin the version: ```sh -go get -u 'github.com/imagekit-developer/imagekit-go@v2.0.0' +go get -u 'github.com/imagekit-developer/imagekit-go/v2@v2.1.0' ``` @@ -167,7 +167,7 @@ custom := param.Override[imagekit.FooParams](12) ### Request unions -Unions are represented as a struct with fields prefixed by "Of" for each of it's variants, +Unions are represented as a struct with fields prefixed by "Of" for each of its variants, only one field can be non-zero. The non-zero field will be serialized. Sub-properties of the union can be accessed via methods on the union struct. diff --git a/accountorigin.go b/accountorigin.go index 79a8d4a..52888b2 100644 --- a/accountorigin.go +++ b/accountorigin.go @@ -74,7 +74,7 @@ func (r *AccountOriginService) List(ctx context.Context, opts ...option.RequestO // any URL‑endpoints, the API will return an error. func (r *AccountOriginService) Delete(ctx context.Context, id string, opts ...option.RequestOption) (err error) { opts = slices.Concat(r.Options, opts) - opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) + opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...) if id == "" { err = errors.New("missing required id parameter") return diff --git a/accounturlendpoint.go b/accounturlendpoint.go index 7991a84..dbd0224 100644 --- a/accounturlendpoint.go +++ b/accounturlendpoint.go @@ -75,7 +75,7 @@ func (r *AccountURLEndpointService) List(ctx context.Context, opts ...option.Req // URL‑endpoint created by ImageKit during account creation. func (r *AccountURLEndpointService) Delete(ctx context.Context, id string, opts ...option.RequestOption) (err error) { opts = slices.Concat(r.Options, opts) - opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) + opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...) if id == "" { err = errors.New("missing required id parameter") return diff --git a/aliases.go b/aliases.go index be8fe53..33ed8d6 100644 --- a/aliases.go +++ b/aliases.go @@ -40,6 +40,13 @@ type ExtensionAutoTaggingParam = shared.ExtensionAutoTaggingParam // This is an alias to an internal type. type ExtensionAIAutoDescriptionParam = shared.ExtensionAIAutoDescriptionParam +// Options for generating responsive image attributes including `src`, `srcSet`, +// and `sizes` for HTML `` elements. This schema extends `SrcOptions` to add +// support for responsive image generation with breakpoints. +// +// This is an alias to an internal type. +type GetImageAttributesOptionsParam = shared.GetImageAttributesOptionsParam + // This is an alias to an internal type. type ImageOverlayParam = shared.ImageOverlayParam @@ -133,6 +140,12 @@ type OverlayTimingEndUnionParam = shared.OverlayTimingEndUnionParam // This is an alias to an internal type. type OverlayTimingStartUnionParam = shared.OverlayTimingStartUnionParam +// Resulting set of attributes suitable for an HTML `` element. Useful for +// enabling responsive image loading with `srcSet` and `sizes`. +// +// This is an alias to an internal type. +type ResponsiveImageAttributesParam = shared.ResponsiveImageAttributesParam + // This is an alias to an internal type. type SolidColorOverlayParam = shared.SolidColorOverlayParam diff --git a/api.md b/api.md index d1fe4eb..2589aa8 100644 --- a/api.md +++ b/api.md @@ -2,10 +2,12 @@ - shared.BaseOverlayParam - shared.ExtensionsParam +- shared.GetImageAttributesOptionsParam - shared.ImageOverlayParam - shared.OverlayUnionParam - shared.OverlayPositionParam - shared.OverlayTimingParam +- shared.ResponsiveImageAttributesParam - shared.SolidColorOverlayParam - shared.SolidColorOverlayTransformationParam - shared.SrcOptionsParam @@ -18,6 +20,12 @@ - shared.TransformationPosition - shared.VideoOverlayParam +# Dummy + +Methods: + +- client.Dummy.New(ctx context.Context, body imagekit.DummyNewParams) error + # CustomMetadataFields Response Types: diff --git a/dummy.go b/dummy.go index 9f26efb..80449e4 100644 --- a/dummy.go +++ b/dummy.go @@ -38,7 +38,7 @@ func NewDummyService(opts ...option.RequestOption) (r DummyService) { // and is not intended for public consumption. func (r *DummyService) New(ctx context.Context, body DummyNewParams, opts ...option.RequestOption) (err error) { opts = slices.Concat(r.Options, opts) - opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) + opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...) path := "v1/dummy/test" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, nil, opts...) return @@ -48,15 +48,22 @@ type DummyNewParams struct { BaseOverlay shared.BaseOverlayParam `json:"baseOverlay,omitzero"` // Array of extensions to be applied to the asset. Each extension can be configured // with specific parameters based on the extension type. - Extensions shared.ExtensionsParam `json:"extensions,omitzero"` - ImageOverlay shared.ImageOverlayParam `json:"imageOverlay,omitzero"` + Extensions shared.ExtensionsParam `json:"extensions,omitzero"` + // Options for generating responsive image attributes including `src`, `srcSet`, + // and `sizes` for HTML `` elements. This schema extends `SrcOptions` to add + // support for responsive image generation with breakpoints. + GetImageAttributesOptions shared.GetImageAttributesOptionsParam `json:"getImageAttributesOptions,omitzero"` + ImageOverlay shared.ImageOverlayParam `json:"imageOverlay,omitzero"` // Specifies an overlay to be applied on the parent image or video. ImageKit // supports overlays including images, text, videos, subtitles, and solid colors. // See // [Overlay using layers](https://imagekit.io/docs/transformations#overlay-using-layers). - Overlay shared.OverlayUnionParam `json:"overlay,omitzero"` - OverlayPosition shared.OverlayPositionParam `json:"overlayPosition,omitzero"` - OverlayTiming shared.OverlayTimingParam `json:"overlayTiming,omitzero"` + Overlay shared.OverlayUnionParam `json:"overlay,omitzero"` + OverlayPosition shared.OverlayPositionParam `json:"overlayPosition,omitzero"` + OverlayTiming shared.OverlayTimingParam `json:"overlayTiming,omitzero"` + // Resulting set of attributes suitable for an HTML `` element. Useful for + // enabling responsive image loading with `srcSet` and `sizes`. + ResponsiveImageAttributes shared.ResponsiveImageAttributesParam `json:"responsiveImageAttributes,omitzero"` SolidColorOverlay shared.SolidColorOverlayParam `json:"solidColorOverlay,omitzero"` SolidColorOverlayTransformation shared.SolidColorOverlayTransformationParam `json:"solidColorOverlayTransformation,omitzero"` // Options for generating ImageKit URLs with transformations. See the diff --git a/dummy_test.go b/dummy_test.go index 0548e6a..96a9a1c 100644 --- a/dummy_test.go +++ b/dummy_test.go @@ -69,6 +69,168 @@ func TestDummyNewWithOptionalParams(t *testing.T) { }, shared.ExtensionUnionParam{ OfAIAutoDescription: &shared.ExtensionAIAutoDescriptionParam{}, }}, + GetImageAttributesOptions: shared.GetImageAttributesOptionsParam{ + SrcOptionsParam: shared.SrcOptionsParam{ + Src: "/my-image.jpg", + URLEndpoint: "https://ik.imagekit.io/demo", + ExpiresIn: imagekit.Float(0), + QueryParameters: map[string]string{ + "foo": "string", + }, + Signed: imagekit.Bool(true), + Transformation: []shared.TransformationParam{{ + AIChangeBackground: imagekit.String("aiChangeBackground"), + AIDropShadow: shared.TransformationAIDropShadowUnionParam{ + OfTransformationAIDropShadowBoolean: imagekit.Bool(true), + }, + AIEdit: imagekit.String("aiEdit"), + AIRemoveBackground: true, + AIRemoveBackgroundExternal: true, + AIRetouch: true, + AIUpscale: true, + AIVariation: true, + AspectRatio: shared.TransformationAspectRatioUnionParam{ + OfString: imagekit.String("4:3"), + }, + AudioCodec: shared.TransformationAudioCodecAac, + Background: imagekit.String("red"), + Blur: imagekit.Float(10), + Border: imagekit.String("5_FF0000"), + ColorProfile: imagekit.Bool(true), + ContrastStretch: true, + Crop: shared.TransformationCropForce, + CropMode: shared.TransformationCropModePadResize, + DefaultImage: imagekit.String("defaultImage"), + Dpr: imagekit.Float(2), + Duration: shared.TransformationDurationUnionParam{ + OfFloat: imagekit.Float(0), + }, + EndOffset: shared.TransformationEndOffsetUnionParam{ + OfFloat: imagekit.Float(0), + }, + Flip: shared.TransformationFlipH, + Focus: imagekit.String("center"), + Format: shared.TransformationFormatAuto, + Gradient: shared.TransformationGradientUnionParam{ + OfTransformationGradientBoolean: imagekit.Bool(true), + }, + Grayscale: true, + Height: shared.TransformationHeightUnionParam{ + OfFloat: imagekit.Float(200), + }, + Lossless: imagekit.Bool(true), + Metadata: imagekit.Bool(true), + Named: imagekit.String("named"), + Opacity: imagekit.Float(0), + Original: imagekit.Bool(true), + Overlay: shared.OverlayUnionParam{ + OfText: &shared.TextOverlayParam{ + BaseOverlayParam: shared.BaseOverlayParam{ + Position: shared.OverlayPositionParam{ + Focus: shared.OverlayPositionFocusCenter, + X: shared.OverlayPositionXUnionParam{ + OfFloat: imagekit.Float(0), + }, + Y: shared.OverlayPositionYUnionParam{ + OfFloat: imagekit.Float(0), + }, + }, + Timing: shared.OverlayTimingParam{ + Duration: shared.OverlayTimingDurationUnionParam{ + OfFloat: imagekit.Float(0), + }, + End: shared.OverlayTimingEndUnionParam{ + OfFloat: imagekit.Float(0), + }, + Start: shared.OverlayTimingStartUnionParam{ + OfFloat: imagekit.Float(0), + }, + }, + }, + Text: "text", + Encoding: "auto", + Transformation: []shared.TextOverlayTransformationParam{{ + Alpha: imagekit.Float(1), + Background: imagekit.String("background"), + Flip: shared.TextOverlayTransformationFlipH, + FontColor: imagekit.String("fontColor"), + FontFamily: imagekit.String("fontFamily"), + FontSize: shared.TextOverlayTransformationFontSizeUnionParam{ + OfFloat: imagekit.Float(0), + }, + InnerAlignment: shared.TextOverlayTransformationInnerAlignmentLeft, + LineHeight: shared.TextOverlayTransformationLineHeightUnionParam{ + OfFloat: imagekit.Float(0), + }, + Padding: shared.TextOverlayTransformationPaddingUnionParam{ + OfFloat: imagekit.Float(0), + }, + Radius: shared.TextOverlayTransformationRadiusUnionParam{ + OfFloat: imagekit.Float(0), + }, + Rotation: shared.TextOverlayTransformationRotationUnionParam{ + OfFloat: imagekit.Float(0), + }, + Typography: imagekit.String("typography"), + Width: shared.TextOverlayTransformationWidthUnionParam{ + OfFloat: imagekit.Float(0), + }, + }}, + }, + }, + Page: shared.TransformationPageUnionParam{ + OfFloat: imagekit.Float(0), + }, + Progressive: imagekit.Bool(true), + Quality: imagekit.Float(80), + Radius: shared.TransformationRadiusUnionParam{ + OfFloat: imagekit.Float(20), + }, + Raw: imagekit.String("raw"), + Rotation: shared.TransformationRotationUnionParam{ + OfFloat: imagekit.Float(90), + }, + Shadow: shared.TransformationShadowUnionParam{ + OfTransformationShadowBoolean: imagekit.Bool(true), + }, + Sharpen: shared.TransformationSharpenUnionParam{ + OfTransformationSharpenBoolean: imagekit.Bool(true), + }, + StartOffset: shared.TransformationStartOffsetUnionParam{ + OfFloat: imagekit.Float(0), + }, + StreamingResolutions: []shared.StreamingResolution{shared.StreamingResolution240}, + Trim: shared.TransformationTrimUnionParam{ + OfTransformationTrimBoolean: imagekit.Bool(true), + }, + UnsharpMask: shared.TransformationUnsharpMaskUnionParam{ + OfTransformationUnsharpMaskBoolean: imagekit.Bool(true), + }, + VideoCodec: shared.TransformationVideoCodecH264, + Width: shared.TransformationWidthUnionParam{ + OfFloat: imagekit.Float(300), + }, + X: shared.TransformationXUnionParam{ + OfFloat: imagekit.Float(0), + }, + XCenter: shared.TransformationXCenterUnionParam{ + OfFloat: imagekit.Float(0), + }, + Y: shared.TransformationYUnionParam{ + OfFloat: imagekit.Float(0), + }, + YCenter: shared.TransformationYCenterUnionParam{ + OfFloat: imagekit.Float(0), + }, + Zoom: imagekit.Float(0), + }}, + TransformationPosition: shared.TransformationPositionPath, + }, + DeviceBreakpoints: []float64{640, 750, 828, 1080, 1200, 1920, 2048, 3840}, + ImageBreakpoints: []float64{16, 32, 48, 64, 96, 128, 256, 384}, + Sizes: imagekit.String("(min-width: 768px) 50vw, 100vw"), + Width: imagekit.Float(400), + }, ImageOverlay: shared.ImageOverlayParam{ BaseOverlayParam: shared.BaseOverlayParam{ Position: shared.OverlayPositionParam{ @@ -316,6 +478,12 @@ func TestDummyNewWithOptionalParams(t *testing.T) { OfFloat: imagekit.Float(0), }, }, + ResponsiveImageAttributes: shared.ResponsiveImageAttributesParam{ + Src: "https://ik.imagekit.io/demo/image.jpg?tr=w-3840", + Sizes: imagekit.String("100vw"), + SrcSet: imagekit.String("https://ik.imagekit.io/demo/image.jpg?tr=w-640 640w, https://ik.imagekit.io/demo/image.jpg?tr=w-1080 1080w, https://ik.imagekit.io/demo/image.jpg?tr=w-1920 1920w"), + Width: imagekit.Float(400), + }, SolidColorOverlay: shared.SolidColorOverlayParam{ BaseOverlayParam: shared.BaseOverlayParam{ Position: shared.OverlayPositionParam{ diff --git a/file.go b/file.go index 95a44c3..a35ca04 100644 --- a/file.go +++ b/file.go @@ -72,7 +72,7 @@ func (r *FileService) Update(ctx context.Context, fileID string, body FileUpdate // the cache using purge cache API. func (r *FileService) Delete(ctx context.Context, fileID string, opts ...option.RequestOption) (err error) { opts = slices.Concat(r.Options, opts) - opts = append([]option.RequestOption{option.WithHeader("Accept", "")}, opts...) + opts = append([]option.RequestOption{option.WithHeader("Accept", "*/*")}, opts...) if fileID == "" { err = errors.New("missing required fileId parameter") return diff --git a/go.mod b/go.mod index 11f9670..a0e1ba6 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/imagekit-developer/imagekit-go/v2 go 1.22 require ( - github.com/standard-webhooks/standard-webhooks/libraries v0.0.0-20250711233419-a173a6c0125c - github.com/tidwall/gjson v1.14.4 + github.com/standard-webhooks/standard-webhooks/libraries v0.0.0-20251210175704-b03a68fe8b19 + github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 ) diff --git a/go.sum b/go.sum index 318dc77..f1261e2 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,8 @@ -github.com/standard-webhooks/standard-webhooks/libraries v0.0.0-20250711233419-a173a6c0125c h1:Mm99t6GdFMtZOwyyvu3q8gXeZX0sqnjvimTC9QCJwQc= -github.com/standard-webhooks/standard-webhooks/libraries v0.0.0-20250711233419-a173a6c0125c/go.mod h1:L1MQhA6x4dn9r007T033lsaZMv9EmBAdXyU/+EF40fo= +github.com/standard-webhooks/standard-webhooks/libraries v0.0.0-20251210175704-b03a68fe8b19 h1:8rMUmsyom6y/10iTAgqkfv8zHVKxVQxFwlOb42V23cA= +github.com/standard-webhooks/standard-webhooks/libraries v0.0.0-20251210175704-b03a68fe8b19/go.mod h1:L1MQhA6x4dn9r007T033lsaZMv9EmBAdXyU/+EF40fo= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= -github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= diff --git a/internal/apiform/encoder.go b/internal/apiform/encoder.go index 1d6afb2..58f5621 100644 --- a/internal/apiform/encoder.go +++ b/internal/apiform/encoder.go @@ -60,6 +60,7 @@ type encoderField struct { type encoderEntry struct { reflect.Type dateFormat string + arrayFmt string root bool } @@ -77,6 +78,7 @@ func (e *encoder) typeEncoder(t reflect.Type) encoderFunc { entry := encoderEntry{ Type: t, dateFormat: e.dateFormat, + arrayFmt: e.arrayFmt, root: e.root, } @@ -178,34 +180,9 @@ func (e *encoder) newPrimitiveTypeEncoder(t reflect.Type) encoderFunc { } } -func arrayKeyEncoder(arrayFmt string) func(string, int) string { - var keyFn func(string, int) string - switch arrayFmt { - case "comma", "repeat": - keyFn = func(k string, _ int) string { return k } - case "brackets": - keyFn = func(key string, _ int) string { return key + "[]" } - case "indices:dots": - keyFn = func(k string, i int) string { - if k == "" { - return strconv.Itoa(i) - } - return k + "." + strconv.Itoa(i) - } - case "indices:brackets": - keyFn = func(k string, i int) string { - if k == "" { - return strconv.Itoa(i) - } - return k + "[" + strconv.Itoa(i) + "]" - } - } - return keyFn -} - func (e *encoder) newArrayTypeEncoder(t reflect.Type) encoderFunc { itemEncoder := e.typeEncoder(t.Elem()) - keyFn := arrayKeyEncoder(e.arrayFmt) + keyFn := e.arrayKeyEncoder() return func(key string, v reflect.Value, writer *multipart.Writer) error { if keyFn == nil { return fmt.Errorf("apiform: unsupported array format") @@ -303,13 +280,10 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { }) return func(key string, value reflect.Value, writer *multipart.Writer) error { - if key != "" { - key = key + "." - } - + keyFn := e.objKeyEncoder(key) for _, ef := range encoderFields { field := value.FieldByIndex(ef.idx) - err := ef.fn(key+ef.tag.name, field, writer) + err := ef.fn(keyFn(ef.tag.name), field, writer) if err != nil { return err } @@ -405,6 +379,43 @@ func (e *encoder) newReaderTypeEncoder() encoderFunc { } } +func (e encoder) arrayKeyEncoder() func(string, int) string { + var keyFn func(string, int) string + switch e.arrayFmt { + case "comma", "repeat": + keyFn = func(k string, _ int) string { return k } + case "brackets": + keyFn = func(key string, _ int) string { return key + "[]" } + case "indices:dots": + keyFn = func(k string, i int) string { + if k == "" { + return strconv.Itoa(i) + } + return k + "." + strconv.Itoa(i) + } + case "indices:brackets": + keyFn = func(k string, i int) string { + if k == "" { + return strconv.Itoa(i) + } + return k + "[" + strconv.Itoa(i) + "]" + } + } + return keyFn +} + +func (e encoder) objKeyEncoder(parent string) func(string) string { + if parent == "" { + return func(child string) string { return child } + } + switch e.arrayFmt { + case "brackets": + return func(child string) string { return parent + "[" + child + "]" } + default: + return func(child string) string { return parent + "." + child } + } +} + // Given a []byte of json (may either be an empty object or an object that already contains entries) // encode all of the entries in the map to the json byte array. func (e *encoder) encodeMapEntries(key string, v reflect.Value, writer *multipart.Writer) error { @@ -413,10 +424,6 @@ func (e *encoder) encodeMapEntries(key string, v reflect.Value, writer *multipar value reflect.Value } - if key != "" { - key = key + "." - } - pairs := []mapPair{} iter := v.MapRange() @@ -434,8 +441,9 @@ func (e *encoder) encodeMapEntries(key string, v reflect.Value, writer *multipar }) elementEncoder := e.typeEncoder(v.Type().Elem()) + keyFn := e.objKeyEncoder(key) for _, p := range pairs { - err := elementEncoder(key+string(p.key), p.value, writer) + err := elementEncoder(keyFn(p.key), p.value, writer) if err != nil { return err } diff --git a/internal/apiform/form_test.go b/internal/apiform/form_test.go index 3b95794..86cb43d 100644 --- a/internal/apiform/form_test.go +++ b/internal/apiform/form_test.go @@ -123,6 +123,18 @@ type StructUnion struct { param.APIUnion } +type MultipartMarshalerParent struct { + Middle MultipartMarshalerMiddleNext `form:"middle"` +} + +type MultipartMarshalerMiddleNext struct { + MiddleNext MultipartMarshalerMiddle `form:"middleNext"` +} + +type MultipartMarshalerMiddle struct { + Child int `form:"child"` +} + var tests = map[string]struct { buf string val any @@ -366,6 +378,19 @@ true }, }, }, + "recursive_struct,brackets": { + `--xxx +Content-Disposition: form-data; name="child[name]" + +Alex +--xxx +Content-Disposition: form-data; name="name" + +Robert +--xxx-- +`, + Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}}, + }, "recursive_struct": { `--xxx @@ -529,6 +554,30 @@ Content-Disposition: form-data; name="union" Union: UnionTime(time.Date(2010, 05, 23, 0, 0, 0, 0, time.UTC)), }, }, + "deeply-nested-struct,brackets": { + `--xxx +Content-Disposition: form-data; name="middle[middleNext][child]" + +10 +--xxx-- +`, + MultipartMarshalerParent{ + Middle: MultipartMarshalerMiddleNext{ + MiddleNext: MultipartMarshalerMiddle{ + Child: 10, + }, + }, + }, + }, + "deeply-nested-map,brackets": { + `--xxx +Content-Disposition: form-data; name="middle[middleNext][child]" + +10 +--xxx-- +`, + map[string]any{"middle": map[string]any{"middleNext": map[string]any{"child": 10}}}, + }, } func TestEncode(t *testing.T) { @@ -553,7 +602,7 @@ func TestEncode(t *testing.T) { } raw := buf.Bytes() if string(raw) != strings.ReplaceAll(test.buf, "\n", "\r\n") { - t.Errorf("expected %+#v to serialize to '%s' but got '%s'", test.val, test.buf, string(raw)) + t.Errorf("expected %+#v to serialize to '%s' but got '%s' (with format %s)", test.val, test.buf, string(raw), arrayFmt) } }) } diff --git a/internal/apijson/enum.go b/internal/apijson/enum.go index 18b218a..5bef11c 100644 --- a/internal/apijson/enum.go +++ b/internal/apijson/enum.go @@ -29,7 +29,7 @@ type validatorFunc func(reflect.Value) exactness var validators sync.Map var validationRegistry = map[reflect.Type][]validationEntry{} -func RegisterFieldValidator[T any, V string | bool | int](fieldName string, values ...V) { +func RegisterFieldValidator[T any, V string | bool | int | float64](fieldName string, values ...V) { var t T parentType := reflect.TypeOf(t) diff --git a/internal/version.go b/internal/version.go index fef6622..436f832 100644 --- a/internal/version.go +++ b/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "2.0.0" // x-release-please-version +const PackageVersion = "2.1.0" // x-release-please-version diff --git a/packages/respjson/respjson.go b/packages/respjson/respjson.go index cc0088c..9e61c5c 100644 --- a/packages/respjson/respjson.go +++ b/packages/respjson/respjson.go @@ -5,7 +5,7 @@ package respjson // Use [Field.Valid] to check if an optional value was null or omitted. // // A Field will always occur in the following structure, where it -// mirrors the original field in it's parent struct: +// mirrors the original field in its parent struct: // // type ExampleObject struct { // Foo bool `json:"foo"` diff --git a/shared/shared.go b/shared/shared.go index 831548c..98859b1 100644 --- a/shared/shared.go +++ b/shared/shared.go @@ -196,6 +196,50 @@ func (r *ExtensionAIAutoDescriptionParam) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } +// Options for generating responsive image attributes including `src`, `srcSet`, +// and `sizes` for HTML `` elements. This schema extends `SrcOptions` to add +// support for responsive image generation with breakpoints. +type GetImageAttributesOptionsParam struct { + // Custom list of **device-width breakpoints** in pixels. These define common + // screen widths for responsive image generation. + // + // Defaults to `[640, 750, 828, 1080, 1200, 1920, 2048, 3840]`. Sorted + // automatically. + DeviceBreakpoints []float64 `json:"deviceBreakpoints,omitzero"` + // Custom list of **image-specific breakpoints** in pixels. Useful for generating + // small variants (e.g., placeholders or thumbnails). + // + // Merged with `deviceBreakpoints` before calculating `srcSet`. Defaults to + // `[16, 32, 48, 64, 96, 128, 256, 384]`. Sorted automatically. + ImageBreakpoints []float64 `json:"imageBreakpoints,omitzero"` + // The value for the HTML `sizes` attribute (e.g., `"100vw"` or + // `"(min-width:768px) 50vw, 100vw"`). + // + // - If it includes one or more `vw` units, breakpoints smaller than the + // corresponding percentage of the smallest device width are excluded. + // - If it contains no `vw` units, the full breakpoint list is used. + // + // Enables a width-based strategy and generates `w` descriptors in `srcSet`. + Sizes param.Opt[string] `json:"sizes,omitzero"` + // The intended display width of the image in pixels, used **only when the `sizes` + // attribute is not provided**. + // + // Triggers a DPR-based strategy (1x and 2x variants) and generates `x` descriptors + // in `srcSet`. + // + // Ignored if `sizes` is present. + Width param.Opt[float64] `json:"width,omitzero"` + SrcOptionsParam +} + +func (r GetImageAttributesOptionsParam) MarshalJSON() (data []byte, err error) { + type shadow struct { + *GetImageAttributesOptionsParam + MarshalJSON bool `json:"-"` // Prevent inheriting [json.Marshaler] from the embedded field + } + return param.MarshalObject(r, shadow{&r, false}) +} + type ImageOverlayParam struct { // Specifies the relative path to the image used as an overlay. Input string `json:"input,required"` @@ -216,8 +260,11 @@ type ImageOverlayParam struct { } func (r ImageOverlayParam) MarshalJSON() (data []byte, err error) { - type shadow ImageOverlayParam - return param.MarshalObject(r, (*shadow)(&r)) + type shadow struct { + *ImageOverlayParam + MarshalJSON bool `json:"-"` // Prevent inheriting [json.Marshaler] from the embedded field + } + return param.MarshalObject(r, shadow{&r, false}) } func OverlayParamOfText(text string) OverlayUnionParam { @@ -623,6 +670,32 @@ func (u *OverlayTimingStartUnionParam) asAny() any { return nil } +// Resulting set of attributes suitable for an HTML `` element. Useful for +// enabling responsive image loading with `srcSet` and `sizes`. +// +// The property Src is required. +type ResponsiveImageAttributesParam struct { + // URL for the _largest_ candidate (assigned to plain `src`). + Src string `json:"src,required" format:"uri"` + // `sizes` returned (or synthesised as `100vw`). The value for the HTML `sizes` + // attribute. + Sizes param.Opt[string] `json:"sizes,omitzero"` + // Candidate set with `w` or `x` descriptors. Multiple image URLs separated by + // commas, each with a descriptor. + SrcSet param.Opt[string] `json:"srcSet,omitzero"` + // Width as a number (if `width` was provided in the input options). + Width param.Opt[float64] `json:"width,omitzero"` + paramObj +} + +func (r ResponsiveImageAttributesParam) MarshalJSON() (data []byte, err error) { + type shadow ResponsiveImageAttributesParam + return param.MarshalObject(r, (*shadow)(&r)) +} +func (r *ResponsiveImageAttributesParam) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + type SolidColorOverlayParam struct { // Specifies the color of the block using an RGB hex code (e.g., `FF0000`), an RGBA // code (e.g., `FFAABB50`), or a color name (e.g., `red`). If an 8-character value @@ -640,8 +713,11 @@ type SolidColorOverlayParam struct { } func (r SolidColorOverlayParam) MarshalJSON() (data []byte, err error) { - type shadow SolidColorOverlayParam - return param.MarshalObject(r, (*shadow)(&r)) + type shadow struct { + *SolidColorOverlayParam + MarshalJSON bool `json:"-"` // Prevent inheriting [json.Marshaler] from the embedded field + } + return param.MarshalObject(r, shadow{&r, false}) } type SolidColorOverlayTransformationParam struct { @@ -874,8 +950,11 @@ type SubtitleOverlayParam struct { } func (r SubtitleOverlayParam) MarshalJSON() (data []byte, err error) { - type shadow SubtitleOverlayParam - return param.MarshalObject(r, (*shadow)(&r)) + type shadow struct { + *SubtitleOverlayParam + MarshalJSON bool `json:"-"` // Prevent inheriting [json.Marshaler] from the embedded field + } + return param.MarshalObject(r, shadow{&r, false}) } // Subtitle styling options. @@ -964,8 +1043,11 @@ type TextOverlayParam struct { } func (r TextOverlayParam) MarshalJSON() (data []byte, err error) { - type shadow TextOverlayParam - return param.MarshalObject(r, (*shadow)(&r)) + type shadow struct { + *TextOverlayParam + MarshalJSON bool `json:"-"` // Prevent inheriting [json.Marshaler] from the embedded field + } + return param.MarshalObject(r, shadow{&r, false}) } type TextOverlayTransformationParam struct { @@ -2134,6 +2216,9 @@ type VideoOverlayParam struct { } func (r VideoOverlayParam) MarshalJSON() (data []byte, err error) { - type shadow VideoOverlayParam - return param.MarshalObject(r, (*shadow)(&r)) + type shadow struct { + *VideoOverlayParam + MarshalJSON bool `json:"-"` // Prevent inheriting [json.Marshaler] from the embedded field + } + return param.MarshalObject(r, shadow{&r, false}) } diff --git a/usage_test.go b/usage_test.go index 9e38cfd..6792db9 100644 --- a/usage_test.go +++ b/usage_test.go @@ -27,6 +27,7 @@ func TestUsage(t *testing.T) { option.WithPrivateKey("My Private Key"), option.WithPassword("My Password"), ) + t.Skip("Prism tests are disabled") response, err := client.Files.Upload(context.TODO(), imagekit.FileUploadParams{ File: io.Reader(bytes.NewBuffer([]byte("https://www.example.com/public-url.jpg"))), FileName: "file-name.jpg", diff --git a/webhook.go b/webhook.go index 6984cf2..192b6ca 100644 --- a/webhook.go +++ b/webhook.go @@ -1552,13 +1552,16 @@ func (r *VideoTransformationReadyEventTimings) UnmarshalJSON(data []byte) error // [UploadPreTransformErrorEvent], [UploadPostTransformSuccessEvent], // [UploadPostTransformErrorEvent]. // +// Use the [UnsafeUnwrapWebhookEventUnion.AsAny] method to switch on the variant. +// // Use the methods beginning with 'As' to cast the union to one of its variants. type UnsafeUnwrapWebhookEventUnion struct { // This field is from variant [VideoTransformationAcceptedEvent], // [VideoTransformationReadyEvent], [VideoTransformationErrorEvent], // [UploadPreTransformSuccessEvent], [UploadPreTransformErrorEvent], // [UploadPostTransformSuccessEvent], [UploadPostTransformErrorEvent]. - ID string `json:"id"` + ID string `json:"id"` + // Any of nil, nil, nil, nil, nil, nil, nil. Type string `json:"type"` CreatedAt time.Time `json:"created_at"` // This field is a union of [VideoTransformationAcceptedEventData], @@ -1890,13 +1893,16 @@ func (r *UnsafeUnwrapWebhookEventUnionRequestTransformation) UnmarshalJSON(data // [UploadPreTransformErrorEvent], [UploadPostTransformSuccessEvent], // [UploadPostTransformErrorEvent]. // +// Use the [UnwrapWebhookEventUnion.AsAny] method to switch on the variant. +// // Use the methods beginning with 'As' to cast the union to one of its variants. type UnwrapWebhookEventUnion struct { // This field is from variant [VideoTransformationAcceptedEvent], // [VideoTransformationReadyEvent], [VideoTransformationErrorEvent], // [UploadPreTransformSuccessEvent], [UploadPreTransformErrorEvent], // [UploadPostTransformSuccessEvent], [UploadPostTransformErrorEvent]. - ID string `json:"id"` + ID string `json:"id"` + // Any of nil, nil, nil, nil, nil, nil, nil. Type string `json:"type"` CreatedAt time.Time `json:"created_at"` // This field is a union of [VideoTransformationAcceptedEventData],