diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4ad9441..98273bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,7 +20,7 @@ jobs: if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup go uses: actions/setup-go@v5 @@ -35,7 +35,7 @@ jobs: runs-on: ${{ github.repository == 'stainless-sdks/cas-parser-go' && 'depot-ubuntu-24.04' || 'ubuntu-latest' }} if: github.event_name == 'push' || github.event.pull_request.head.repo.fork steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup go uses: actions/setup-go@v5 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 28e831b..3d2ac0b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.3" + ".": "0.1.0" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 92721c7..2c90fa6 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 5 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser%2Fcas-parser-b7fdba3d3f97c7debc22c7ca30b828bce81bcd64648df8c94029b27a3321ebb9.yml -openapi_spec_hash: 03f1315f1d32ada42445ca920f047dff +configured_endpoints: 4 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/cas-parser%2Fcas-parser-56b0f699c5437d9e5326626d35dfc972c17d01f12cb416c7f4854c8ea6d0e95e.yml +openapi_spec_hash: 158f405c1880706266d83e6ff16b9d2f config_hash: cb5d75abef6264b5d86448caf7295afa diff --git a/CHANGELOG.md b/CHANGELOG.md index 077729d..c7c7cd5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,33 @@ # Changelog +## 0.1.0 (2026-02-03) + +Full Changelog: [v0.0.3...v0.1.0](https://github.com/CASParser/cas-parser-go/compare/v0.0.3...v0.1.0) + +### Features + +* **api:** api update ([321c851](https://github.com/CASParser/cas-parser-go/commit/321c851d5ce4729962559904ec4e13db1d8febdd)) +* **api:** api update ([58bd341](https://github.com/CASParser/cas-parser-go/commit/58bd3413a106d7e2e99011664649d9ea80f47286)) +* **api:** api update ([58f353e](https://github.com/CASParser/cas-parser-go/commit/58f353ea9385874ac4f84ae9107bcee481fd79f9)) +* **api:** api update ([4de0c4b](https://github.com/CASParser/cas-parser-go/commit/4de0c4b7936cbe00370980ab20aad02586090fcd)) +* **api:** api update ([c4b3ed4](https://github.com/CASParser/cas-parser-go/commit/c4b3ed40ff9e5d6ad1ac4ca57e0219d184038d20)) +* **api:** api update ([4cafc4e](https://github.com/CASParser/cas-parser-go/commit/4cafc4e634e376310e635eeeacd9c6bc1656d7ca)) + + +### Bug Fixes + +* bugfix for setting JSON keys with special characters ([48f9ffd](https://github.com/CASParser/cas-parser-go/commit/48f9ffda3a0053adcf1a68c5378bbeac9de9027c)) +* use slices.Concat instead of sometimes modifying r.Options ([c30d79a](https://github.com/CASParser/cas-parser-go/commit/c30d79aa4eb1f8b4d793fe5e641db7d0f385a9dc)) + + +### Chores + +* bump minimum go version to 1.22 ([54f1ae1](https://github.com/CASParser/cas-parser-go/commit/54f1ae1bedb207621a92b70a64220456781a8e37)) +* do not install brew dependencies in ./scripts/bootstrap by default ([a665c6a](https://github.com/CASParser/cas-parser-go/commit/a665c6aa0d14754a7f4ea644b2b53ac49dec0f0c)) +* **internal:** codegen related update ([b0d066b](https://github.com/CASParser/cas-parser-go/commit/b0d066be8e48533c4e0bf3f004c3c6ad96af0008)) +* **internal:** grammar fix (it's -> its) ([0af28e9](https://github.com/CASParser/cas-parser-go/commit/0af28e95581cfd4c2d9f729ee59f8213253da54a)) +* update more docs for 1.22 ([fc3b56e](https://github.com/CASParser/cas-parser-go/commit/fc3b56e861603f65fd1a61d6c843b220e94154cc)) + ## 0.0.3 (2025-09-06) Full Changelog: [v0.0.2...v0.0.3](https://github.com/CASParser/cas-parser-go/compare/v0.0.2...v0.0.3) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a02f386..5be4390 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,7 @@ $ ./scripts/lint This will install all the required dependencies and build the SDK. -You can also [install go 1.18+ manually](https://go.dev/doc/install). +You can also [install go 1.22+ manually](https://go.dev/doc/install). ## Modifying/Adding code diff --git a/LICENSE b/LICENSE index f1756ce..6bbb512 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2025 Cas Parser + Copyright 2026 Cas Parser Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 14db163..5d464fc 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,15 @@ from applications written in Go. It is generated with [Stainless](https://www.stainless.com/). +## MCP Server + +Use the Cas Parser MCP Server to enable AI assistants to interact with this API, allowing them to explore endpoints, make test requests, and use documentation to help integrate this SDK into your application. + +[![Add to Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en-US/install-mcp?name=cas-parser-node-mcp&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsImNhcy1wYXJzZXItbm9kZS1tY3AiXSwiZW52Ijp7IkNBU19QQVJTRVJfQVBJX0tFWSI6Ik15IEFQSSBLZXkifX0) +[![Install in VS Code](https://img.shields.io/badge/_-Add_to_VS_Code-blue?style=for-the-badge&logo=data:image/svg%2bxml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA0MCA0MCI+PHBhdGggZmlsbD0iI0VFRSIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMzAuMjM1IDM5Ljg4NGEyLjQ5MSAyLjQ5MSAwIDAgMS0xLjc4MS0uNzNMMTIuNyAyNC43OGwtMy40NiAyLjYyNC0zLjQwNiAyLjU4MmExLjY2NSAxLjY2NSAwIDAgMS0xLjA4Mi4zMzggMS42NjQgMS42NjQgMCAwIDEtMS4wNDYtLjQzMWwtMi4yLTJhMS42NjYgMS42NjYgMCAwIDEgMC0yLjQ2M0w3LjQ1OCAyMCA0LjY3IDE3LjQ1MyAxLjUwNyAxNC41N2ExLjY2NSAxLjY2NSAwIDAgMSAwLTIuNDYzbDIuMi0yYTEuNjY1IDEuNjY1IDAgMCAxIDIuMTMtLjA5N2w2Ljg2MyA1LjIwOUwyOC40NTIuODQ0YTIuNDg4IDIuNDg4IDAgMCAxIDEuODQxLS43MjljLjM1MS4wMDkuNjk5LjA5MSAxLjAxOS4yNDVsOC4yMzYgMy45NjFhMi41IDIuNSAwIDAgMSAxLjQxNSAyLjI1M3YuMDk5LS4wNDVWMzMuMzd2LS4wNDUuMDk1YTIuNTAxIDIuNTAxIDAgMCAxLTEuNDE2IDIuMjU3bC04LjIzNSAzLjk2MWEyLjQ5MiAyLjQ5MiAwIDAgMS0xLjA3Ny4yNDZabS43MTYtMjguOTQ3LTExLjk0OCA5LjA2MiAxMS45NTIgOS4wNjUtLjAwNC0xOC4xMjdaIi8+PC9zdmc+)](https://vscode.stainless.com/mcp/%7B%22name%22%3A%22cas-parser-node-mcp%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22cas-parser-node-mcp%22%5D%2C%22env%22%3A%7B%22CAS_PARSER_API_KEY%22%3A%22My%20API%20Key%22%7D%7D) + +> Note: You may need to set environment variables in your MCP client. + ## Installation @@ -28,14 +37,14 @@ Or to pin the version: ```sh -go get -u 'github.com/CASParser/cas-parser-go@v0.0.3' +go get -u 'github.com/CASParser/cas-parser-go@v0.1.0' ``` ## Requirements -This library requires Go 1.18+. +This library requires Go 1.22+. ## Usage @@ -128,7 +137,7 @@ custom := param.Override[casparser.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/api.md b/api.md index ddecff9..30672c5 100644 --- a/api.md +++ b/api.md @@ -6,17 +6,9 @@ Response Types: Methods: -- client.CasParser.CamsKfintech(ctx context.Context, body casparser.CasParserCamsKfintechParams) (casparser.UnifiedResponse, error) -- client.CasParser.Cdsl(ctx context.Context, body casparser.CasParserCdslParams) (casparser.UnifiedResponse, error) -- client.CasParser.Nsdl(ctx context.Context, body casparser.CasParserNsdlParams) (casparser.UnifiedResponse, error) -- client.CasParser.SmartParse(ctx context.Context, body casparser.CasParserSmartParseParams) (casparser.UnifiedResponse, error) +- client.CasParser.CamsKfintech(ctx context.Context, body casparser.CasParserCamsKfintechParams) (\*casparser.UnifiedResponse, error) +- client.CasParser.Cdsl(ctx context.Context, body casparser.CasParserCdslParams) (\*casparser.UnifiedResponse, error) +- client.CasParser.Nsdl(ctx context.Context, body casparser.CasParserNsdlParams) (\*casparser.UnifiedResponse, error) +- client.CasParser.SmartParse(ctx context.Context, body casparser.CasParserSmartParseParams) (\*casparser.UnifiedResponse, error) # CasGenerator - -Response Types: - -- casparser.CasGeneratorGenerateCasResponse - -Methods: - -- client.CasGenerator.GenerateCas(ctx context.Context, body casparser.CasGeneratorGenerateCasParams) (casparser.CasGeneratorGenerateCasResponse, error) diff --git a/casgenerator.go b/casgenerator.go index e1dea8b..4b3b086 100644 --- a/casgenerator.go +++ b/casgenerator.go @@ -3,14 +3,7 @@ package casparser import ( - "context" - "net/http" - - "github.com/CASParser/cas-parser-go/internal/apijson" - "github.com/CASParser/cas-parser-go/internal/requestconfig" "github.com/CASParser/cas-parser-go/option" - "github.com/CASParser/cas-parser-go/packages/param" - "github.com/CASParser/cas-parser-go/packages/respjson" ) // CasGeneratorService contains methods and other services that help with @@ -31,69 +24,3 @@ func NewCasGeneratorService(opts ...option.RequestOption) (r CasGeneratorService r.Options = opts return } - -// This endpoint generates CAS (Consolidated Account Statement) documents by -// submitting a mailback request to the specified CAS authority. Currently only -// supports KFintech, with plans to support CAMS, CDSL, and NSDL in the future. -func (r *CasGeneratorService) GenerateCas(ctx context.Context, body CasGeneratorGenerateCasParams, opts ...option.RequestOption) (res *CasGeneratorGenerateCasResponse, err error) { - opts = append(r.Options[:], opts...) - path := "v4/generate" - err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) - return -} - -type CasGeneratorGenerateCasResponse struct { - Msg string `json:"msg"` - Status string `json:"status"` - // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. - JSON struct { - Msg respjson.Field - Status respjson.Field - ExtraFields map[string]respjson.Field - raw string - } `json:"-"` -} - -// Returns the unmodified JSON received from the API -func (r CasGeneratorGenerateCasResponse) RawJSON() string { return r.JSON.raw } -func (r *CasGeneratorGenerateCasResponse) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -type CasGeneratorGenerateCasParams struct { - // Email address to receive the CAS document - Email string `json:"email,required"` - // Start date for the CAS period (format YYYY-MM-DD) - FromDate string `json:"from_date,required"` - // Password to protect the generated CAS PDF - Password string `json:"password,required"` - // End date for the CAS period (format YYYY-MM-DD) - ToDate string `json:"to_date,required"` - // PAN number (optional for some CAS authorities) - PanNo param.Opt[string] `json:"pan_no,omitzero"` - // CAS authority to generate the document from (currently only kfintech is - // supported) - // - // Any of "kfintech", "cams", "cdsl", "nsdl". - CasAuthority CasGeneratorGenerateCasParamsCasAuthority `json:"cas_authority,omitzero"` - paramObj -} - -func (r CasGeneratorGenerateCasParams) MarshalJSON() (data []byte, err error) { - type shadow CasGeneratorGenerateCasParams - return param.MarshalObject(r, (*shadow)(&r)) -} -func (r *CasGeneratorGenerateCasParams) UnmarshalJSON(data []byte) error { - return apijson.UnmarshalRoot(data, r) -} - -// CAS authority to generate the document from (currently only kfintech is -// supported) -type CasGeneratorGenerateCasParamsCasAuthority string - -const ( - CasGeneratorGenerateCasParamsCasAuthorityKfintech CasGeneratorGenerateCasParamsCasAuthority = "kfintech" - CasGeneratorGenerateCasParamsCasAuthorityCams CasGeneratorGenerateCasParamsCasAuthority = "cams" - CasGeneratorGenerateCasParamsCasAuthorityCdsl CasGeneratorGenerateCasParamsCasAuthority = "cdsl" - CasGeneratorGenerateCasParamsCasAuthorityNsdl CasGeneratorGenerateCasParamsCasAuthority = "nsdl" -) diff --git a/casgenerator_test.go b/casgenerator_test.go deleted file mode 100644 index e093e18..0000000 --- a/casgenerator_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -package casparser_test - -import ( - "context" - "errors" - "os" - "testing" - - "github.com/CASParser/cas-parser-go" - "github.com/CASParser/cas-parser-go/internal/testutil" - "github.com/CASParser/cas-parser-go/option" -) - -func TestCasGeneratorGenerateCasWithOptionalParams(t *testing.T) { - t.Skip("Prism tests are disabled") - baseURL := "http://localhost:4010" - if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok { - baseURL = envURL - } - if !testutil.CheckTestServer(t, baseURL) { - return - } - client := casparser.NewClient( - option.WithBaseURL(baseURL), - option.WithAPIKey("My API Key"), - ) - _, err := client.CasGenerator.GenerateCas(context.TODO(), casparser.CasGeneratorGenerateCasParams{ - Email: "user@example.com", - FromDate: "2023-01-01", - Password: "Abcdefghi12$", - ToDate: "2023-12-31", - CasAuthority: casparser.CasGeneratorGenerateCasParamsCasAuthorityKfintech, - PanNo: casparser.String("ABCDE1234F"), - }) - if err != nil { - var apierr *casparser.Error - if errors.As(err, &apierr) { - t.Log(string(apierr.DumpRequest(true))) - } - t.Fatalf("err should be nil: %s", err.Error()) - } -} diff --git a/casparser.go b/casparser.go index 66e6cca..577c26f 100644 --- a/casparser.go +++ b/casparser.go @@ -5,6 +5,7 @@ package casparser import ( "context" "net/http" + "slices" "time" "github.com/CASParser/cas-parser-go/internal/apijson" @@ -37,7 +38,7 @@ func NewCasParserService(opts ...option.RequestOption) (r CasParserService) { // Statement) PDF files and returns data in a unified format. Use this endpoint // when you know the PDF is from CAMS or KFintech. func (r *CasParserService) CamsKfintech(ctx context.Context, body CasParserCamsKfintechParams, opts ...option.RequestOption) (res *UnifiedResponse, err error) { - opts = append(r.Options[:], opts...) + opts = slices.Concat(r.Options, opts) path := "v4/cams_kfintech/parse" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) return @@ -47,7 +48,7 @@ func (r *CasParserService) CamsKfintech(ctx context.Context, body CasParserCamsK // files and returns data in a unified format. Use this endpoint when you know the // PDF is from CDSL. func (r *CasParserService) Cdsl(ctx context.Context, body CasParserCdslParams, opts ...option.RequestOption) (res *UnifiedResponse, err error) { - opts = append(r.Options[:], opts...) + opts = slices.Concat(r.Options, opts) path := "v4/cdsl/parse" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) return @@ -57,7 +58,7 @@ func (r *CasParserService) Cdsl(ctx context.Context, body CasParserCdslParams, o // files and returns data in a unified format. Use this endpoint when you know the // PDF is from NSDL. func (r *CasParserService) Nsdl(ctx context.Context, body CasParserNsdlParams, opts ...option.RequestOption) (res *UnifiedResponse, err error) { - opts = append(r.Options[:], opts...) + opts = slices.Concat(r.Options, opts) path := "v4/nsdl/parse" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) return @@ -68,7 +69,7 @@ func (r *CasParserService) Nsdl(ctx context.Context, body CasParserNsdlParams, o // CAS type and transforms the data into a consistent structure regardless of the // source. func (r *CasParserService) SmartParse(ctx context.Context, body CasParserSmartParseParams, opts ...option.RequestOption) (res *UnifiedResponse, err error) { - opts = append(r.Options[:], opts...) + opts = slices.Concat(r.Options, opts) path := "v4/smart/parse" err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) return @@ -80,7 +81,9 @@ type UnifiedResponse struct { Investor UnifiedResponseInvestor `json:"investor"` Meta UnifiedResponseMeta `json:"meta"` MutualFunds []UnifiedResponseMutualFund `json:"mutual_funds"` - Summary UnifiedResponseSummary `json:"summary"` + // List of NPS accounts + Nps []UnifiedResponseNp `json:"nps"` + Summary UnifiedResponseSummary `json:"summary"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { DematAccounts respjson.Field @@ -88,6 +91,7 @@ type UnifiedResponse struct { Investor respjson.Field Meta respjson.Field MutualFunds respjson.Field + Nps respjson.Field Summary respjson.Field ExtraFields map[string]respjson.Field raw string @@ -116,6 +120,8 @@ type UnifiedResponseDematAccount struct { // Depository Participant name DpName string `json:"dp_name"` Holdings UnifiedResponseDematAccountHoldings `json:"holdings"` + // List of account holders linked to this demat account + LinkedHolders []UnifiedResponseDematAccountLinkedHolder `json:"linked_holders"` // Total value of the demat account Value float64 `json:"value"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. @@ -127,6 +133,7 @@ type UnifiedResponseDematAccount struct { DpID respjson.Field DpName respjson.Field Holdings respjson.Field + LinkedHolders respjson.Field Value respjson.Field ExtraFields map[string]respjson.Field raw string @@ -204,11 +211,13 @@ func (r *UnifiedResponseDematAccountHoldings) UnmarshalJSON(data []byte) error { type UnifiedResponseDematAccountHoldingsAif struct { // Additional information specific to the AIF - AdditionalInfo any `json:"additional_info"` + AdditionalInfo UnifiedResponseDematAccountHoldingsAifAdditionalInfo `json:"additional_info"` // ISIN code of the AIF Isin string `json:"isin"` // Name of the AIF Name string `json:"name"` + // List of transactions for this holding (beta) + Transactions []UnifiedResponseDematAccountHoldingsAifTransaction `json:"transactions"` // Number of units held Units float64 `json:"units"` // Current market value of the holding @@ -218,6 +227,7 @@ type UnifiedResponseDematAccountHoldingsAif struct { AdditionalInfo respjson.Field Isin respjson.Field Name respjson.Field + Transactions respjson.Field Units respjson.Field Value respjson.Field ExtraFields map[string]respjson.Field @@ -231,13 +241,125 @@ func (r *UnifiedResponseDematAccountHoldingsAif) UnmarshalJSON(data []byte) erro return apijson.UnmarshalRoot(data, r) } +// Additional information specific to the AIF +type UnifiedResponseDematAccountHoldingsAifAdditionalInfo struct { + // Closing balance units for the statement period (beta) + CloseUnits float64 `json:"close_units,nullable"` + // Opening balance units for the statement period (beta) + OpenUnits float64 `json:"open_units,nullable"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + CloseUnits respjson.Field + OpenUnits respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseDematAccountHoldingsAifAdditionalInfo) RawJSON() string { return r.JSON.raw } +func (r *UnifiedResponseDematAccountHoldingsAifAdditionalInfo) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Unified transaction schema for all holding types (MF folios, equities, bonds, +// etc.) +type UnifiedResponseDematAccountHoldingsAifTransaction struct { + // Additional transaction-specific fields that vary by source + AdditionalInfo UnifiedResponseDematAccountHoldingsAifTransactionAdditionalInfo `json:"additional_info"` + // Transaction amount in currency (computed from units × price/NAV) + Amount float64 `json:"amount,nullable"` + // Balance units after transaction + Balance float64 `json:"balance"` + // Transaction date (YYYY-MM-DD) + Date time.Time `json:"date" format:"date"` + // Transaction description/particulars + Description string `json:"description"` + // Dividend rate (for DIVIDEND_PAYOUT transactions) + DividendRate float64 `json:"dividend_rate,nullable"` + // NAV/price per unit on transaction date + Nav float64 `json:"nav,nullable"` + // Transaction type. Possible values are PURCHASE, PURCHASE_SIP, REDEMPTION, + // SWITCH_IN, SWITCH_IN_MERGER, SWITCH_OUT, SWITCH_OUT_MERGER, DIVIDEND_PAYOUT, + // DIVIDEND_REINVEST, SEGREGATION, STAMP_DUTY_TAX, TDS_TAX, STT_TAX, MISC, + // REVERSAL, UNKNOWN. + // + // Any of "PURCHASE", "PURCHASE_SIP", "REDEMPTION", "SWITCH_IN", + // "SWITCH_IN_MERGER", "SWITCH_OUT", "SWITCH_OUT_MERGER", "DIVIDEND_PAYOUT", + // "DIVIDEND_REINVEST", "SEGREGATION", "STAMP_DUTY_TAX", "TDS_TAX", "STT_TAX", + // "MISC", "REVERSAL", "UNKNOWN". + Type string `json:"type"` + // Number of units involved in transaction + Units float64 `json:"units"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + AdditionalInfo respjson.Field + Amount respjson.Field + Balance respjson.Field + Date respjson.Field + Description respjson.Field + DividendRate respjson.Field + Nav respjson.Field + Type respjson.Field + Units respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseDematAccountHoldingsAifTransaction) RawJSON() string { return r.JSON.raw } +func (r *UnifiedResponseDematAccountHoldingsAifTransaction) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Additional transaction-specific fields that vary by source +type UnifiedResponseDematAccountHoldingsAifTransactionAdditionalInfo struct { + // Capital withdrawal amount (CDSL MF transactions) + CapitalWithdrawal float64 `json:"capital_withdrawal"` + // Units credited (demat transactions) + Credit float64 `json:"credit"` + // Units debited (demat transactions) + Debit float64 `json:"debit"` + // Income distribution amount (CDSL MF transactions) + IncomeDistribution float64 `json:"income_distribution"` + // Order/transaction reference number (demat transactions) + OrderNo string `json:"order_no"` + // Price per unit (NSDL/CDSL MF transactions) + Price float64 `json:"price"` + // Stamp duty charged + StampDuty float64 `json:"stamp_duty"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + CapitalWithdrawal respjson.Field + Credit respjson.Field + Debit respjson.Field + IncomeDistribution respjson.Field + OrderNo respjson.Field + Price respjson.Field + StampDuty respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseDematAccountHoldingsAifTransactionAdditionalInfo) RawJSON() string { + return r.JSON.raw +} +func (r *UnifiedResponseDematAccountHoldingsAifTransactionAdditionalInfo) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + type UnifiedResponseDematAccountHoldingsCorporateBond struct { // Additional information specific to the corporate bond - AdditionalInfo any `json:"additional_info"` + AdditionalInfo UnifiedResponseDematAccountHoldingsCorporateBondAdditionalInfo `json:"additional_info"` // ISIN code of the corporate bond Isin string `json:"isin"` // Name of the corporate bond Name string `json:"name"` + // List of transactions for this holding (beta) + Transactions []UnifiedResponseDematAccountHoldingsCorporateBondTransaction `json:"transactions"` // Number of units held Units float64 `json:"units"` // Current market value of the holding @@ -247,6 +369,7 @@ type UnifiedResponseDematAccountHoldingsCorporateBond struct { AdditionalInfo respjson.Field Isin respjson.Field Name respjson.Field + Transactions respjson.Field Units respjson.Field Value respjson.Field ExtraFields map[string]respjson.Field @@ -260,13 +383,129 @@ func (r *UnifiedResponseDematAccountHoldingsCorporateBond) UnmarshalJSON(data [] return apijson.UnmarshalRoot(data, r) } +// Additional information specific to the corporate bond +type UnifiedResponseDematAccountHoldingsCorporateBondAdditionalInfo struct { + // Closing balance units for the statement period (beta) + CloseUnits float64 `json:"close_units,nullable"` + // Opening balance units for the statement period (beta) + OpenUnits float64 `json:"open_units,nullable"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + CloseUnits respjson.Field + OpenUnits respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseDematAccountHoldingsCorporateBondAdditionalInfo) RawJSON() string { + return r.JSON.raw +} +func (r *UnifiedResponseDematAccountHoldingsCorporateBondAdditionalInfo) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Unified transaction schema for all holding types (MF folios, equities, bonds, +// etc.) +type UnifiedResponseDematAccountHoldingsCorporateBondTransaction struct { + // Additional transaction-specific fields that vary by source + AdditionalInfo UnifiedResponseDematAccountHoldingsCorporateBondTransactionAdditionalInfo `json:"additional_info"` + // Transaction amount in currency (computed from units × price/NAV) + Amount float64 `json:"amount,nullable"` + // Balance units after transaction + Balance float64 `json:"balance"` + // Transaction date (YYYY-MM-DD) + Date time.Time `json:"date" format:"date"` + // Transaction description/particulars + Description string `json:"description"` + // Dividend rate (for DIVIDEND_PAYOUT transactions) + DividendRate float64 `json:"dividend_rate,nullable"` + // NAV/price per unit on transaction date + Nav float64 `json:"nav,nullable"` + // Transaction type. Possible values are PURCHASE, PURCHASE_SIP, REDEMPTION, + // SWITCH_IN, SWITCH_IN_MERGER, SWITCH_OUT, SWITCH_OUT_MERGER, DIVIDEND_PAYOUT, + // DIVIDEND_REINVEST, SEGREGATION, STAMP_DUTY_TAX, TDS_TAX, STT_TAX, MISC, + // REVERSAL, UNKNOWN. + // + // Any of "PURCHASE", "PURCHASE_SIP", "REDEMPTION", "SWITCH_IN", + // "SWITCH_IN_MERGER", "SWITCH_OUT", "SWITCH_OUT_MERGER", "DIVIDEND_PAYOUT", + // "DIVIDEND_REINVEST", "SEGREGATION", "STAMP_DUTY_TAX", "TDS_TAX", "STT_TAX", + // "MISC", "REVERSAL", "UNKNOWN". + Type string `json:"type"` + // Number of units involved in transaction + Units float64 `json:"units"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + AdditionalInfo respjson.Field + Amount respjson.Field + Balance respjson.Field + Date respjson.Field + Description respjson.Field + DividendRate respjson.Field + Nav respjson.Field + Type respjson.Field + Units respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseDematAccountHoldingsCorporateBondTransaction) RawJSON() string { + return r.JSON.raw +} +func (r *UnifiedResponseDematAccountHoldingsCorporateBondTransaction) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Additional transaction-specific fields that vary by source +type UnifiedResponseDematAccountHoldingsCorporateBondTransactionAdditionalInfo struct { + // Capital withdrawal amount (CDSL MF transactions) + CapitalWithdrawal float64 `json:"capital_withdrawal"` + // Units credited (demat transactions) + Credit float64 `json:"credit"` + // Units debited (demat transactions) + Debit float64 `json:"debit"` + // Income distribution amount (CDSL MF transactions) + IncomeDistribution float64 `json:"income_distribution"` + // Order/transaction reference number (demat transactions) + OrderNo string `json:"order_no"` + // Price per unit (NSDL/CDSL MF transactions) + Price float64 `json:"price"` + // Stamp duty charged + StampDuty float64 `json:"stamp_duty"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + CapitalWithdrawal respjson.Field + Credit respjson.Field + Debit respjson.Field + IncomeDistribution respjson.Field + OrderNo respjson.Field + Price respjson.Field + StampDuty respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseDematAccountHoldingsCorporateBondTransactionAdditionalInfo) RawJSON() string { + return r.JSON.raw +} +func (r *UnifiedResponseDematAccountHoldingsCorporateBondTransactionAdditionalInfo) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + type UnifiedResponseDematAccountHoldingsDematMutualFund struct { // Additional information specific to the mutual fund - AdditionalInfo any `json:"additional_info"` + AdditionalInfo UnifiedResponseDematAccountHoldingsDematMutualFundAdditionalInfo `json:"additional_info"` // ISIN code of the mutual fund Isin string `json:"isin"` // Name of the mutual fund Name string `json:"name"` + // List of transactions for this holding (beta) + Transactions []UnifiedResponseDematAccountHoldingsDematMutualFundTransaction `json:"transactions"` // Number of units held Units float64 `json:"units"` // Current market value of the holding @@ -276,6 +515,7 @@ type UnifiedResponseDematAccountHoldingsDematMutualFund struct { AdditionalInfo respjson.Field Isin respjson.Field Name respjson.Field + Transactions respjson.Field Units respjson.Field Value respjson.Field ExtraFields map[string]respjson.Field @@ -289,13 +529,129 @@ func (r *UnifiedResponseDematAccountHoldingsDematMutualFund) UnmarshalJSON(data return apijson.UnmarshalRoot(data, r) } +// Additional information specific to the mutual fund +type UnifiedResponseDematAccountHoldingsDematMutualFundAdditionalInfo struct { + // Closing balance units for the statement period (beta) + CloseUnits float64 `json:"close_units,nullable"` + // Opening balance units for the statement period (beta) + OpenUnits float64 `json:"open_units,nullable"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + CloseUnits respjson.Field + OpenUnits respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseDematAccountHoldingsDematMutualFundAdditionalInfo) RawJSON() string { + return r.JSON.raw +} +func (r *UnifiedResponseDematAccountHoldingsDematMutualFundAdditionalInfo) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Unified transaction schema for all holding types (MF folios, equities, bonds, +// etc.) +type UnifiedResponseDematAccountHoldingsDematMutualFundTransaction struct { + // Additional transaction-specific fields that vary by source + AdditionalInfo UnifiedResponseDematAccountHoldingsDematMutualFundTransactionAdditionalInfo `json:"additional_info"` + // Transaction amount in currency (computed from units × price/NAV) + Amount float64 `json:"amount,nullable"` + // Balance units after transaction + Balance float64 `json:"balance"` + // Transaction date (YYYY-MM-DD) + Date time.Time `json:"date" format:"date"` + // Transaction description/particulars + Description string `json:"description"` + // Dividend rate (for DIVIDEND_PAYOUT transactions) + DividendRate float64 `json:"dividend_rate,nullable"` + // NAV/price per unit on transaction date + Nav float64 `json:"nav,nullable"` + // Transaction type. Possible values are PURCHASE, PURCHASE_SIP, REDEMPTION, + // SWITCH_IN, SWITCH_IN_MERGER, SWITCH_OUT, SWITCH_OUT_MERGER, DIVIDEND_PAYOUT, + // DIVIDEND_REINVEST, SEGREGATION, STAMP_DUTY_TAX, TDS_TAX, STT_TAX, MISC, + // REVERSAL, UNKNOWN. + // + // Any of "PURCHASE", "PURCHASE_SIP", "REDEMPTION", "SWITCH_IN", + // "SWITCH_IN_MERGER", "SWITCH_OUT", "SWITCH_OUT_MERGER", "DIVIDEND_PAYOUT", + // "DIVIDEND_REINVEST", "SEGREGATION", "STAMP_DUTY_TAX", "TDS_TAX", "STT_TAX", + // "MISC", "REVERSAL", "UNKNOWN". + Type string `json:"type"` + // Number of units involved in transaction + Units float64 `json:"units"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + AdditionalInfo respjson.Field + Amount respjson.Field + Balance respjson.Field + Date respjson.Field + Description respjson.Field + DividendRate respjson.Field + Nav respjson.Field + Type respjson.Field + Units respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseDematAccountHoldingsDematMutualFundTransaction) RawJSON() string { + return r.JSON.raw +} +func (r *UnifiedResponseDematAccountHoldingsDematMutualFundTransaction) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Additional transaction-specific fields that vary by source +type UnifiedResponseDematAccountHoldingsDematMutualFundTransactionAdditionalInfo struct { + // Capital withdrawal amount (CDSL MF transactions) + CapitalWithdrawal float64 `json:"capital_withdrawal"` + // Units credited (demat transactions) + Credit float64 `json:"credit"` + // Units debited (demat transactions) + Debit float64 `json:"debit"` + // Income distribution amount (CDSL MF transactions) + IncomeDistribution float64 `json:"income_distribution"` + // Order/transaction reference number (demat transactions) + OrderNo string `json:"order_no"` + // Price per unit (NSDL/CDSL MF transactions) + Price float64 `json:"price"` + // Stamp duty charged + StampDuty float64 `json:"stamp_duty"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + CapitalWithdrawal respjson.Field + Credit respjson.Field + Debit respjson.Field + IncomeDistribution respjson.Field + OrderNo respjson.Field + Price respjson.Field + StampDuty respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseDematAccountHoldingsDematMutualFundTransactionAdditionalInfo) RawJSON() string { + return r.JSON.raw +} +func (r *UnifiedResponseDematAccountHoldingsDematMutualFundTransactionAdditionalInfo) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + type UnifiedResponseDematAccountHoldingsEquity struct { // Additional information specific to the equity - AdditionalInfo any `json:"additional_info"` + AdditionalInfo UnifiedResponseDematAccountHoldingsEquityAdditionalInfo `json:"additional_info"` // ISIN code of the equity Isin string `json:"isin"` // Name of the equity Name string `json:"name"` + // List of transactions for this holding (beta) + Transactions []UnifiedResponseDematAccountHoldingsEquityTransaction `json:"transactions"` // Number of units held Units float64 `json:"units"` // Current market value of the holding @@ -305,6 +661,7 @@ type UnifiedResponseDematAccountHoldingsEquity struct { AdditionalInfo respjson.Field Isin respjson.Field Name respjson.Field + Transactions respjson.Field Units respjson.Field Value respjson.Field ExtraFields map[string]respjson.Field @@ -318,13 +675,125 @@ func (r *UnifiedResponseDematAccountHoldingsEquity) UnmarshalJSON(data []byte) e return apijson.UnmarshalRoot(data, r) } +// Additional information specific to the equity +type UnifiedResponseDematAccountHoldingsEquityAdditionalInfo struct { + // Closing balance units for the statement period (beta) + CloseUnits float64 `json:"close_units,nullable"` + // Opening balance units for the statement period (beta) + OpenUnits float64 `json:"open_units,nullable"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + CloseUnits respjson.Field + OpenUnits respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseDematAccountHoldingsEquityAdditionalInfo) RawJSON() string { return r.JSON.raw } +func (r *UnifiedResponseDematAccountHoldingsEquityAdditionalInfo) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Unified transaction schema for all holding types (MF folios, equities, bonds, +// etc.) +type UnifiedResponseDematAccountHoldingsEquityTransaction struct { + // Additional transaction-specific fields that vary by source + AdditionalInfo UnifiedResponseDematAccountHoldingsEquityTransactionAdditionalInfo `json:"additional_info"` + // Transaction amount in currency (computed from units × price/NAV) + Amount float64 `json:"amount,nullable"` + // Balance units after transaction + Balance float64 `json:"balance"` + // Transaction date (YYYY-MM-DD) + Date time.Time `json:"date" format:"date"` + // Transaction description/particulars + Description string `json:"description"` + // Dividend rate (for DIVIDEND_PAYOUT transactions) + DividendRate float64 `json:"dividend_rate,nullable"` + // NAV/price per unit on transaction date + Nav float64 `json:"nav,nullable"` + // Transaction type. Possible values are PURCHASE, PURCHASE_SIP, REDEMPTION, + // SWITCH_IN, SWITCH_IN_MERGER, SWITCH_OUT, SWITCH_OUT_MERGER, DIVIDEND_PAYOUT, + // DIVIDEND_REINVEST, SEGREGATION, STAMP_DUTY_TAX, TDS_TAX, STT_TAX, MISC, + // REVERSAL, UNKNOWN. + // + // Any of "PURCHASE", "PURCHASE_SIP", "REDEMPTION", "SWITCH_IN", + // "SWITCH_IN_MERGER", "SWITCH_OUT", "SWITCH_OUT_MERGER", "DIVIDEND_PAYOUT", + // "DIVIDEND_REINVEST", "SEGREGATION", "STAMP_DUTY_TAX", "TDS_TAX", "STT_TAX", + // "MISC", "REVERSAL", "UNKNOWN". + Type string `json:"type"` + // Number of units involved in transaction + Units float64 `json:"units"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + AdditionalInfo respjson.Field + Amount respjson.Field + Balance respjson.Field + Date respjson.Field + Description respjson.Field + DividendRate respjson.Field + Nav respjson.Field + Type respjson.Field + Units respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseDematAccountHoldingsEquityTransaction) RawJSON() string { return r.JSON.raw } +func (r *UnifiedResponseDematAccountHoldingsEquityTransaction) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Additional transaction-specific fields that vary by source +type UnifiedResponseDematAccountHoldingsEquityTransactionAdditionalInfo struct { + // Capital withdrawal amount (CDSL MF transactions) + CapitalWithdrawal float64 `json:"capital_withdrawal"` + // Units credited (demat transactions) + Credit float64 `json:"credit"` + // Units debited (demat transactions) + Debit float64 `json:"debit"` + // Income distribution amount (CDSL MF transactions) + IncomeDistribution float64 `json:"income_distribution"` + // Order/transaction reference number (demat transactions) + OrderNo string `json:"order_no"` + // Price per unit (NSDL/CDSL MF transactions) + Price float64 `json:"price"` + // Stamp duty charged + StampDuty float64 `json:"stamp_duty"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + CapitalWithdrawal respjson.Field + Credit respjson.Field + Debit respjson.Field + IncomeDistribution respjson.Field + OrderNo respjson.Field + Price respjson.Field + StampDuty respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseDematAccountHoldingsEquityTransactionAdditionalInfo) RawJSON() string { + return r.JSON.raw +} +func (r *UnifiedResponseDematAccountHoldingsEquityTransactionAdditionalInfo) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + type UnifiedResponseDematAccountHoldingsGovernmentSecurity struct { // Additional information specific to the government security - AdditionalInfo any `json:"additional_info"` + AdditionalInfo UnifiedResponseDematAccountHoldingsGovernmentSecurityAdditionalInfo `json:"additional_info"` // ISIN code of the government security Isin string `json:"isin"` // Name of the government security Name string `json:"name"` + // List of transactions for this holding (beta) + Transactions []UnifiedResponseDematAccountHoldingsGovernmentSecurityTransaction `json:"transactions"` // Number of units held Units float64 `json:"units"` // Current market value of the holding @@ -334,6 +803,7 @@ type UnifiedResponseDematAccountHoldingsGovernmentSecurity struct { AdditionalInfo respjson.Field Isin respjson.Field Name respjson.Field + Transactions respjson.Field Units respjson.Field Value respjson.Field ExtraFields map[string]respjson.Field @@ -347,6 +817,140 @@ func (r *UnifiedResponseDematAccountHoldingsGovernmentSecurity) UnmarshalJSON(da return apijson.UnmarshalRoot(data, r) } +// Additional information specific to the government security +type UnifiedResponseDematAccountHoldingsGovernmentSecurityAdditionalInfo struct { + // Closing balance units for the statement period (beta) + CloseUnits float64 `json:"close_units,nullable"` + // Opening balance units for the statement period (beta) + OpenUnits float64 `json:"open_units,nullable"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + CloseUnits respjson.Field + OpenUnits respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseDematAccountHoldingsGovernmentSecurityAdditionalInfo) RawJSON() string { + return r.JSON.raw +} +func (r *UnifiedResponseDematAccountHoldingsGovernmentSecurityAdditionalInfo) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Unified transaction schema for all holding types (MF folios, equities, bonds, +// etc.) +type UnifiedResponseDematAccountHoldingsGovernmentSecurityTransaction struct { + // Additional transaction-specific fields that vary by source + AdditionalInfo UnifiedResponseDematAccountHoldingsGovernmentSecurityTransactionAdditionalInfo `json:"additional_info"` + // Transaction amount in currency (computed from units × price/NAV) + Amount float64 `json:"amount,nullable"` + // Balance units after transaction + Balance float64 `json:"balance"` + // Transaction date (YYYY-MM-DD) + Date time.Time `json:"date" format:"date"` + // Transaction description/particulars + Description string `json:"description"` + // Dividend rate (for DIVIDEND_PAYOUT transactions) + DividendRate float64 `json:"dividend_rate,nullable"` + // NAV/price per unit on transaction date + Nav float64 `json:"nav,nullable"` + // Transaction type. Possible values are PURCHASE, PURCHASE_SIP, REDEMPTION, + // SWITCH_IN, SWITCH_IN_MERGER, SWITCH_OUT, SWITCH_OUT_MERGER, DIVIDEND_PAYOUT, + // DIVIDEND_REINVEST, SEGREGATION, STAMP_DUTY_TAX, TDS_TAX, STT_TAX, MISC, + // REVERSAL, UNKNOWN. + // + // Any of "PURCHASE", "PURCHASE_SIP", "REDEMPTION", "SWITCH_IN", + // "SWITCH_IN_MERGER", "SWITCH_OUT", "SWITCH_OUT_MERGER", "DIVIDEND_PAYOUT", + // "DIVIDEND_REINVEST", "SEGREGATION", "STAMP_DUTY_TAX", "TDS_TAX", "STT_TAX", + // "MISC", "REVERSAL", "UNKNOWN". + Type string `json:"type"` + // Number of units involved in transaction + Units float64 `json:"units"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + AdditionalInfo respjson.Field + Amount respjson.Field + Balance respjson.Field + Date respjson.Field + Description respjson.Field + DividendRate respjson.Field + Nav respjson.Field + Type respjson.Field + Units respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseDematAccountHoldingsGovernmentSecurityTransaction) RawJSON() string { + return r.JSON.raw +} +func (r *UnifiedResponseDematAccountHoldingsGovernmentSecurityTransaction) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Additional transaction-specific fields that vary by source +type UnifiedResponseDematAccountHoldingsGovernmentSecurityTransactionAdditionalInfo struct { + // Capital withdrawal amount (CDSL MF transactions) + CapitalWithdrawal float64 `json:"capital_withdrawal"` + // Units credited (demat transactions) + Credit float64 `json:"credit"` + // Units debited (demat transactions) + Debit float64 `json:"debit"` + // Income distribution amount (CDSL MF transactions) + IncomeDistribution float64 `json:"income_distribution"` + // Order/transaction reference number (demat transactions) + OrderNo string `json:"order_no"` + // Price per unit (NSDL/CDSL MF transactions) + Price float64 `json:"price"` + // Stamp duty charged + StampDuty float64 `json:"stamp_duty"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + CapitalWithdrawal respjson.Field + Credit respjson.Field + Debit respjson.Field + IncomeDistribution respjson.Field + OrderNo respjson.Field + Price respjson.Field + StampDuty respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseDematAccountHoldingsGovernmentSecurityTransactionAdditionalInfo) RawJSON() string { + return r.JSON.raw +} +func (r *UnifiedResponseDematAccountHoldingsGovernmentSecurityTransactionAdditionalInfo) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type UnifiedResponseDematAccountLinkedHolder struct { + // Name of the account holder + Name string `json:"name"` + // PAN of the account holder + Pan string `json:"pan"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Name respjson.Field + Pan respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseDematAccountLinkedHolder) RawJSON() string { return r.JSON.raw } +func (r *UnifiedResponseDematAccountLinkedHolder) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + type UnifiedResponseInsurance struct { LifeInsurancePolicies []UnifiedResponseInsuranceLifeInsurancePolicy `json:"life_insurance_policies"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. @@ -490,6 +1094,8 @@ type UnifiedResponseMutualFund struct { Amc string `json:"amc"` // Folio number FolioNumber string `json:"folio_number"` + // List of account holders linked to this mutual fund folio + LinkedHolders []UnifiedResponseMutualFundLinkedHolder `json:"linked_holders"` // Registrar and Transfer Agent name Registrar string `json:"registrar"` Schemes []UnifiedResponseMutualFundScheme `json:"schemes"` @@ -500,6 +1106,7 @@ type UnifiedResponseMutualFund struct { AdditionalInfo respjson.Field Amc respjson.Field FolioNumber respjson.Field + LinkedHolders respjson.Field Registrar respjson.Field Schemes respjson.Field Value respjson.Field @@ -538,6 +1145,26 @@ func (r *UnifiedResponseMutualFundAdditionalInfo) UnmarshalJSON(data []byte) err return apijson.UnmarshalRoot(data, r) } +type UnifiedResponseMutualFundLinkedHolder struct { + // Name of the account holder + Name string `json:"name"` + // PAN of the account holder + Pan string `json:"pan"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Name respjson.Field + Pan respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseMutualFundLinkedHolder) RawJSON() string { return r.JSON.raw } +func (r *UnifiedResponseMutualFundLinkedHolder) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + type UnifiedResponseMutualFundScheme struct { // Additional information specific to the scheme AdditionalInfo UnifiedResponseMutualFundSchemeAdditionalInfo `json:"additional_info"` @@ -591,10 +1218,10 @@ type UnifiedResponseMutualFundSchemeAdditionalInfo struct { Advisor string `json:"advisor"` // AMFI code for the scheme (CAMS/KFintech) Amfi string `json:"amfi"` - // Closing balance units (CAMS/KFintech) - CloseUnits float64 `json:"close_units"` - // Opening balance units (CAMS/KFintech) - OpenUnits float64 `json:"open_units"` + // Closing balance units for the statement period + CloseUnits float64 `json:"close_units,nullable"` + // Opening balance units for the statement period + OpenUnits float64 `json:"open_units,nullable"` // RTA code for the scheme (CAMS/KFintech) RtaCode string `json:"rta_code"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. @@ -635,38 +1262,48 @@ func (r *UnifiedResponseMutualFundSchemeGain) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, r) } +// Unified transaction schema for all holding types (MF folios, equities, bonds, +// etc.) type UnifiedResponseMutualFundSchemeTransaction struct { - // Transaction amount - Amount float64 `json:"amount"` + // Additional transaction-specific fields that vary by source + AdditionalInfo UnifiedResponseMutualFundSchemeTransactionAdditionalInfo `json:"additional_info"` + // Transaction amount in currency (computed from units × price/NAV) + Amount float64 `json:"amount,nullable"` // Balance units after transaction Balance float64 `json:"balance"` - // Transaction date + // Transaction date (YYYY-MM-DD) Date time.Time `json:"date" format:"date"` - // Transaction description + // Transaction description/particulars Description string `json:"description"` - // Dividend rate (for dividend transactions) - DividendRate float64 `json:"dividend_rate"` - // NAV on transaction date - Nav float64 `json:"nav"` - // Transaction type detected based on description. Possible values are - // PURCHASE,PURCHASE_SIP,REDEMPTION,SWITCH_IN,SWITCH_IN_MERGER,SWITCH_OUT,SWITCH_OUT_MERGER,DIVIDEND_PAYOUT,DIVIDEND_REINVESTMENT,SEGREGATION,STAMP_DUTY_TAX,TDS_TAX,STT_TAX,MISC. - // If dividend_rate is present, then possible values are dividend_rate is - // applicable only for DIVIDEND_PAYOUT and DIVIDEND_REINVESTMENT. + // Dividend rate (for DIVIDEND_PAYOUT transactions) + DividendRate float64 `json:"dividend_rate,nullable"` + // NAV/price per unit on transaction date + Nav float64 `json:"nav,nullable"` + // Transaction type. Possible values are PURCHASE, PURCHASE_SIP, REDEMPTION, + // SWITCH_IN, SWITCH_IN_MERGER, SWITCH_OUT, SWITCH_OUT_MERGER, DIVIDEND_PAYOUT, + // DIVIDEND_REINVEST, SEGREGATION, STAMP_DUTY_TAX, TDS_TAX, STT_TAX, MISC, + // REVERSAL, UNKNOWN. + // + // Any of "PURCHASE", "PURCHASE_SIP", "REDEMPTION", "SWITCH_IN", + // "SWITCH_IN_MERGER", "SWITCH_OUT", "SWITCH_OUT_MERGER", "DIVIDEND_PAYOUT", + // "DIVIDEND_REINVEST", "SEGREGATION", "STAMP_DUTY_TAX", "TDS_TAX", "STT_TAX", + // "MISC", "REVERSAL", "UNKNOWN". Type string `json:"type"` - // Number of units involved + // Number of units involved in transaction Units float64 `json:"units"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { - Amount respjson.Field - Balance respjson.Field - Date respjson.Field - Description respjson.Field - DividendRate respjson.Field - Nav respjson.Field - Type respjson.Field - Units respjson.Field - ExtraFields map[string]respjson.Field - raw string + AdditionalInfo respjson.Field + Amount respjson.Field + Balance respjson.Field + Date respjson.Field + Description respjson.Field + DividendRate respjson.Field + Nav respjson.Field + Type respjson.Field + Units respjson.Field + ExtraFields map[string]respjson.Field + raw string } `json:"-"` } @@ -676,6 +1313,148 @@ func (r *UnifiedResponseMutualFundSchemeTransaction) UnmarshalJSON(data []byte) return apijson.UnmarshalRoot(data, r) } +// Additional transaction-specific fields that vary by source +type UnifiedResponseMutualFundSchemeTransactionAdditionalInfo struct { + // Capital withdrawal amount (CDSL MF transactions) + CapitalWithdrawal float64 `json:"capital_withdrawal"` + // Units credited (demat transactions) + Credit float64 `json:"credit"` + // Units debited (demat transactions) + Debit float64 `json:"debit"` + // Income distribution amount (CDSL MF transactions) + IncomeDistribution float64 `json:"income_distribution"` + // Order/transaction reference number (demat transactions) + OrderNo string `json:"order_no"` + // Price per unit (NSDL/CDSL MF transactions) + Price float64 `json:"price"` + // Stamp duty charged + StampDuty float64 `json:"stamp_duty"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + CapitalWithdrawal respjson.Field + Credit respjson.Field + Debit respjson.Field + IncomeDistribution respjson.Field + OrderNo respjson.Field + Price respjson.Field + StampDuty respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseMutualFundSchemeTransactionAdditionalInfo) RawJSON() string { return r.JSON.raw } +func (r *UnifiedResponseMutualFundSchemeTransactionAdditionalInfo) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type UnifiedResponseNp struct { + // Additional information specific to the NPS account + AdditionalInfo any `json:"additional_info"` + // Central Record Keeping Agency name + Cra string `json:"cra"` + Funds []UnifiedResponseNpFund `json:"funds"` + // List of account holders linked to this NPS account + LinkedHolders []UnifiedResponseNpLinkedHolder `json:"linked_holders"` + // Permanent Retirement Account Number (PRAN) + Pran string `json:"pran"` + // Total value of the NPS account + Value float64 `json:"value"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + AdditionalInfo respjson.Field + Cra respjson.Field + Funds respjson.Field + LinkedHolders respjson.Field + Pran respjson.Field + Value respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseNp) RawJSON() string { return r.JSON.raw } +func (r *UnifiedResponseNp) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type UnifiedResponseNpFund struct { + // Additional information specific to the NPS fund + AdditionalInfo UnifiedResponseNpFundAdditionalInfo `json:"additional_info"` + // Cost of investment + Cost float64 `json:"cost"` + // Name of the NPS fund + Name string `json:"name"` + // Net Asset Value per unit + Nav float64 `json:"nav"` + // Number of units held + Units float64 `json:"units"` + // Current market value of the holding + Value float64 `json:"value"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + AdditionalInfo respjson.Field + Cost respjson.Field + Name respjson.Field + Nav respjson.Field + Units respjson.Field + Value respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseNpFund) RawJSON() string { return r.JSON.raw } +func (r *UnifiedResponseNpFund) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +// Additional information specific to the NPS fund +type UnifiedResponseNpFundAdditionalInfo struct { + // Fund manager name + Manager string `json:"manager"` + // NPS tier (Tier I or Tier II) + // + // Any of 1, 2. + Tier float64 `json:"tier,nullable"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Manager respjson.Field + Tier respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseNpFundAdditionalInfo) RawJSON() string { return r.JSON.raw } +func (r *UnifiedResponseNpFundAdditionalInfo) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + +type UnifiedResponseNpLinkedHolder struct { + // Name of the account holder + Name string `json:"name"` + // PAN of the account holder + Pan string `json:"pan"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Name respjson.Field + Pan respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseNpLinkedHolder) RawJSON() string { return r.JSON.raw } +func (r *UnifiedResponseNpLinkedHolder) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + type UnifiedResponseSummary struct { Accounts UnifiedResponseSummaryAccounts `json:"accounts"` // Total portfolio value across all accounts @@ -699,11 +1478,13 @@ type UnifiedResponseSummaryAccounts struct { Demat UnifiedResponseSummaryAccountsDemat `json:"demat"` Insurance UnifiedResponseSummaryAccountsInsurance `json:"insurance"` MutualFunds UnifiedResponseSummaryAccountsMutualFunds `json:"mutual_funds"` + Nps UnifiedResponseSummaryAccountsNps `json:"nps"` // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. JSON struct { Demat respjson.Field Insurance respjson.Field MutualFunds respjson.Field + Nps respjson.Field ExtraFields map[string]respjson.Field raw string } `json:"-"` @@ -775,12 +1556,32 @@ func (r *UnifiedResponseSummaryAccountsMutualFunds) UnmarshalJSON(data []byte) e return apijson.UnmarshalRoot(data, r) } +type UnifiedResponseSummaryAccountsNps struct { + // Number of NPS accounts + Count int64 `json:"count"` + // Total value of NPS accounts + TotalValue float64 `json:"total_value"` + // JSON contains metadata for fields, check presence with [respjson.Field.Valid]. + JSON struct { + Count respjson.Field + TotalValue respjson.Field + ExtraFields map[string]respjson.Field + raw string + } `json:"-"` +} + +// Returns the unmodified JSON received from the API +func (r UnifiedResponseSummaryAccountsNps) RawJSON() string { return r.JSON.raw } +func (r *UnifiedResponseSummaryAccountsNps) UnmarshalJSON(data []byte) error { + return apijson.UnmarshalRoot(data, r) +} + type CasParserCamsKfintechParams struct { // Password for the PDF file (if required) Password param.Opt[string] `json:"password,omitzero"` - // Base64 encoded CAS PDF file + // Base64 encoded CAS PDF file (required if pdf_url not provided) PdfFile param.Opt[string] `json:"pdf_file,omitzero" format:"base64"` - // URL to the CAS PDF file + // URL to the CAS PDF file (required if pdf_file not provided) PdfURL param.Opt[string] `json:"pdf_url,omitzero" format:"uri"` paramObj } @@ -796,9 +1597,9 @@ func (r *CasParserCamsKfintechParams) UnmarshalJSON(data []byte) error { type CasParserCdslParams struct { // Password for the PDF file (if required) Password param.Opt[string] `json:"password,omitzero"` - // Base64 encoded CAS PDF file + // Base64 encoded CAS PDF file (required if pdf_url not provided) PdfFile param.Opt[string] `json:"pdf_file,omitzero" format:"base64"` - // URL to the CAS PDF file + // URL to the CAS PDF file (required if pdf_file not provided) PdfURL param.Opt[string] `json:"pdf_url,omitzero" format:"uri"` paramObj } @@ -814,9 +1615,9 @@ func (r *CasParserCdslParams) UnmarshalJSON(data []byte) error { type CasParserNsdlParams struct { // Password for the PDF file (if required) Password param.Opt[string] `json:"password,omitzero"` - // Base64 encoded CAS PDF file + // Base64 encoded CAS PDF file (required if pdf_url not provided) PdfFile param.Opt[string] `json:"pdf_file,omitzero" format:"base64"` - // URL to the CAS PDF file + // URL to the CAS PDF file (required if pdf_file not provided) PdfURL param.Opt[string] `json:"pdf_url,omitzero" format:"uri"` paramObj } @@ -832,9 +1633,9 @@ func (r *CasParserNsdlParams) UnmarshalJSON(data []byte) error { type CasParserSmartParseParams struct { // Password for the PDF file (if required) Password param.Opt[string] `json:"password,omitzero"` - // Base64 encoded CAS PDF file + // Base64 encoded CAS PDF file (required if pdf_url not provided) PdfFile param.Opt[string] `json:"pdf_file,omitzero" format:"base64"` - // URL to the CAS PDF file + // URL to the CAS PDF file (required if pdf_file not provided) PdfURL param.Opt[string] `json:"pdf_url,omitzero" format:"uri"` paramObj } diff --git a/client.go b/client.go index a757680..ab47a50 100644 --- a/client.go +++ b/client.go @@ -6,6 +6,7 @@ import ( "context" "net/http" "os" + "slices" "github.com/CASParser/cas-parser-go/internal/requestconfig" "github.com/CASParser/cas-parser-go/option" @@ -80,7 +81,7 @@ func NewClient(opts ...option.RequestOption) (r Client) { // For even greater flexibility, see [option.WithResponseInto] and // [option.WithResponseBodyInto]. func (r *Client) Execute(ctx context.Context, method string, path string, params any, res any, opts ...option.RequestOption) error { - opts = append(r.Options, opts...) + opts = slices.Concat(r.Options, opts) return requestconfig.ExecuteNewRequest(ctx, method, path, params, res, opts...) } diff --git a/go.mod b/go.mod index d01a0e4..967552e 100644 --- a/go.mod +++ b/go.mod @@ -1,9 +1,9 @@ module github.com/CASParser/cas-parser-go -go 1.21 +go 1.22 require ( - github.com/tidwall/gjson v1.14.4 + github.com/tidwall/gjson v1.18.0 github.com/tidwall/sjson v1.2.5 ) diff --git a/go.sum b/go.sum index a70a5e0..32ba293 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,6 @@ 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 82f21a7..c2bfd45 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 227e50a..a660e74 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/encoder.go b/internal/apijson/encoder.go index 8358a2f..ab7a3c1 100644 --- a/internal/apijson/encoder.go +++ b/internal/apijson/encoder.go @@ -16,6 +16,10 @@ import ( var encoders sync.Map // map[encoderEntry]encoderFunc +// If we want to set a literal key value into JSON using sjson, we need to make sure it doesn't have +// special characters that sjson interprets as a path. +var EscapeSJSONKey = strings.NewReplacer("\\", "\\\\", "|", "\\|", "#", "\\#", "@", "\\@", "*", "\\*", ".", "\\.", ":", "\\:", "?", "\\?").Replace + func Marshal(value any) ([]byte, error) { e := &encoder{dateFormat: time.RFC3339} return e.marshal(value) @@ -270,7 +274,7 @@ func (e *encoder) newStructTypeEncoder(t reflect.Type) encoderFunc { if encoded == nil { continue } - json, err = sjson.SetRawBytes(json, ef.tag.name, encoded) + json, err = sjson.SetRawBytes(json, EscapeSJSONKey(ef.tag.name), encoded) if err != nil { return nil, err } @@ -348,7 +352,7 @@ func (e *encoder) encodeMapEntries(json []byte, v reflect.Value) ([]byte, error) } encodedKeyString = string(encodedKeyBytes) } - encodedKey := []byte(sjsonReplacer.Replace(encodedKeyString)) + encodedKey := []byte(encodedKeyString) pairs = append(pairs, mapPair{key: encodedKey, value: iter.Value()}) } @@ -366,7 +370,7 @@ func (e *encoder) encodeMapEntries(json []byte, v reflect.Value) ([]byte, error) if len(encodedValue) == 0 { continue } - json, err = sjson.SetRawBytes(json, string(p.key), encodedValue) + json, err = sjson.SetRawBytes(json, EscapeSJSONKey(string(p.key)), encodedValue) if err != nil { return nil, err } @@ -386,7 +390,3 @@ func (e *encoder) newMapEncoder(_ reflect.Type) encoderFunc { return json, nil } } - -// If we want to set a literal key value into JSON using sjson, we need to make sure it doesn't have -// special characters that sjson interprets as a path. -var sjsonReplacer *strings.Replacer = strings.NewReplacer(".", "\\.", ":", "\\:", "*", "\\*") 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/apijson/union.go b/internal/apijson/union.go index 87eeb20..7b37be1 100644 --- a/internal/apijson/union.go +++ b/internal/apijson/union.go @@ -78,7 +78,7 @@ func (d *decoderBuilder) newStructUnionDecoder(t reflect.Type) decoderFunc { return func(n gjson.Result, v reflect.Value, state *decoderState) error { if discriminated && n.Type == gjson.JSON && len(unionEntry.discriminatorKey) != 0 { - discriminator := n.Get(unionEntry.discriminatorKey).Value() + discriminator := n.Get(EscapeSJSONKey(unionEntry.discriminatorKey)).Value() for _, decoder := range discriminatedDecoders { if discriminator == decoder.discriminator { inner := v.FieldByIndex(decoder.field.Index) @@ -162,7 +162,7 @@ func (d *decoderBuilder) newUnionDecoder(t reflect.Type) decoderFunc { } if len(unionEntry.discriminatorKey) != 0 { - discriminatorValue := n.Get(unionEntry.discriminatorKey).Value() + discriminatorValue := n.Get(EscapeSJSONKey(unionEntry.discriminatorKey)).Value() if discriminatorValue == variant.DiscriminatorValue { inner := reflect.New(variant.Type).Elem() err := decoder(n, inner, state) diff --git a/internal/encoding/json/shims/shims.go b/internal/encoding/json/shims/shims.go index b65a016..fe9a71a 100644 --- a/internal/encoding/json/shims/shims.go +++ b/internal/encoding/json/shims/shims.go @@ -1,5 +1,5 @@ // This package provides shims over Go 1.2{2,3} APIs -// which are missing from Go 1.21, and used by the Go 1.24 encoding/json package. +// which are missing from Go 1.22, and used by the Go 1.24 encoding/json package. // // Inside the vendored package, all shim code has comments that begin look like // // SHIM(...): ... diff --git a/internal/version.go b/internal/version.go index b5b3e63..02eac73 100644 --- a/internal/version.go +++ b/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.0.3" // x-release-please-version +const PackageVersion = "0.1.0" // x-release-please-version diff --git a/packages/param/encoder.go b/packages/param/encoder.go index 3ec5f41..42f2ae7 100644 --- a/packages/param/encoder.go +++ b/packages/param/encoder.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "reflect" + "strings" "time" shimjson "github.com/CASParser/cas-parser-go/internal/encoding/json" @@ -14,6 +15,10 @@ import ( // EncodedAsDate is not be stable and shouldn't be relied upon type EncodedAsDate Opt[time.Time] +// If we want to set a literal key value into JSON using sjson, we need to make sure it doesn't have +// special characters that sjson interprets as a path. +var EscapeSJSONKey = strings.NewReplacer("\\", "\\\\", "|", "\\|", "#", "\\#", "@", "\\@", "*", "\\*", ".", "\\.", ":", "\\:", "?", "\\?").Replace + type forceOmit int func (m EncodedAsDate) MarshalJSON() ([]byte, error) { @@ -52,7 +57,7 @@ func MarshalWithExtras[T ParamStruct, R any](f T, underlying any, extras map[str } continue } - bytes, err = sjson.SetBytes(bytes, k, v) + bytes, err = sjson.SetBytes(bytes, EscapeSJSONKey(k), v) if err != nil { return nil, err } diff --git a/packages/param/param.go b/packages/param/param.go index 3335a9a..dcc3083 100644 --- a/packages/param/param.go +++ b/packages/param/param.go @@ -41,6 +41,19 @@ func Override[T ParamStruct, PtrT InferPtr[T]](v any) T { return *pt } +// SetJSON configures a param struct to serialize with the provided raw JSON data. +// Use this when you have existing JSON that you want to send as request parameters. +// +// var req example.NewUserParams +// var rawJSON = []byte(`{"name": "...", "age": 40}`) +// param.SetJSON(rawJSON, &req) +// res, err := client.Users.New(ctx, req) +// +// Note: The struct's existing fields will be ignored; only the provided JSON is serialized. +func SetJSON(rawJSON []byte, ptr anyParamStruct) { + ptr.setMetadata(json.RawMessage(rawJSON)) +} + // IsOmitted returns true if v is the zero value of its type. // // If IsOmitted is true, and the field uses a `json:"...,omitzero"` tag, @@ -91,6 +104,11 @@ type ParamStruct interface { extraFields() map[string]any } +// A pointer to ParamStruct +type anyParamStruct interface { + setMetadata(any) +} + // This is an implementation detail and should never be explicitly set. type InferPtr[T ParamStruct] interface { setMetadata(any) 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/scripts/bootstrap b/scripts/bootstrap index d6ac165..5ab3066 100755 --- a/scripts/bootstrap +++ b/scripts/bootstrap @@ -4,10 +4,18 @@ set -e cd "$(dirname "$0")/.." -if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ]; then +if [ -f "Brewfile" ] && [ "$(uname -s)" = "Darwin" ] && [ "$SKIP_BREW" != "1" ] && [ -t 0 ]; then brew bundle check >/dev/null 2>&1 || { - echo "==> Installing Homebrew dependencies…" - brew bundle + echo -n "==> Install Homebrew dependencies? (y/N): " + read -r response + case "$response" in + [yY][eE][sS]|[yY]) + brew bundle + ;; + *) + ;; + esac + echo } fi diff --git a/usage_test.go b/usage_test.go index 071d9f8..5b017e4 100644 --- a/usage_test.go +++ b/usage_test.go @@ -24,6 +24,7 @@ func TestUsage(t *testing.T) { option.WithBaseURL(baseURL), option.WithAPIKey("My API Key"), ) + t.Skip("Prism tests are disabled") unifiedResponse, err := client.CasParser.SmartParse(context.TODO(), casparser.CasParserSmartParseParams{ Password: casparser.String("ABCDF"), PdfURL: casparser.String("https://your-cas-pdf-url-here.com"),