diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2aca35a..d04f223 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.5.0" + ".": "0.5.1" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 22156c8..1e63704 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## 0.5.1 (2026-03-03) + +Full Changelog: [v0.5.0...v0.5.1](https://github.com/CASParser/cas-parser-java/compare/v0.5.0...v0.5.1) + +### Chores + +* drop apache dependency ([2ea7ec5](https://github.com/CASParser/cas-parser-java/commit/2ea7ec51ef82bacadd0df76d7886e7c52e1ab902)) +* **internal:** codegen related update ([e8504a7](https://github.com/CASParser/cas-parser-java/commit/e8504a766d553e1c253e504df23c21f99d89c5da)) +* **internal:** expand imports ([fdca7cd](https://github.com/CASParser/cas-parser-java/commit/fdca7cd3a6a27f31b5b163b456eccf5c956db18f)) +* make `Properties` more resilient to `null` ([72f0c67](https://github.com/CASParser/cas-parser-java/commit/72f0c67cb321fb2ee5832c9c3f775d0b82353a0d)) + ## 0.5.0 (2026-02-23) Full Changelog: [v0.4.0...v0.5.0](https://github.com/CASParser/cas-parser-java/compare/v0.4.0...v0.5.0) diff --git a/README.md b/README.md index bcad7a7..1e5410a 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ -[![Maven Central](https://img.shields.io/maven-central/v/com.cas_parser.api/cas-parser-java)](https://central.sonatype.com/artifact/com.cas_parser.api/cas-parser-java/0.5.0) -[![javadoc](https://javadoc.io/badge2/com.cas_parser.api/cas-parser-java/0.5.0/javadoc.svg)](https://javadoc.io/doc/com.cas_parser.api/cas-parser-java/0.5.0) +[![Maven Central](https://img.shields.io/maven-central/v/com.cas_parser.api/cas-parser-java)](https://central.sonatype.com/artifact/com.cas_parser.api/cas-parser-java/0.5.1) +[![javadoc](https://javadoc.io/badge2/com.cas_parser.api/cas-parser-java/0.5.1/javadoc.svg)](https://javadoc.io/doc/com.cas_parser.api/cas-parser-java/0.5.1) @@ -22,7 +22,7 @@ Use the Cas Parser MCP Server to enable AI assistants to interact with this API, -The REST API documentation can be found on [casparser.in](https://casparser.in/docs). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.cas_parser.api/cas-parser-java/0.5.0). +The REST API documentation can be found on [casparser.in](https://casparser.in/docs). Javadocs are available on [javadoc.io](https://javadoc.io/doc/com.cas_parser.api/cas-parser-java/0.5.1). @@ -33,7 +33,7 @@ The REST API documentation can be found on [casparser.in](https://casparser.in/d ### Gradle ```kotlin -implementation("com.cas_parser.api:cas-parser-java:0.5.0") +implementation("com.cas_parser.api:cas-parser-java:0.5.1") ``` ### Maven @@ -42,7 +42,7 @@ implementation("com.cas_parser.api:cas-parser-java:0.5.0") com.cas_parser.api cas-parser-java - 0.5.0 + 0.5.1 ``` diff --git a/build.gradle.kts b/build.gradle.kts index 4aa1227..cc1e16d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,7 @@ repositories { allprojects { group = "com.cas_parser.api" - version = "0.5.0" // x-release-please-version + version = "0.5.1" // x-release-please-version } subprojects { diff --git a/cas-parser-java-core/build.gradle.kts b/cas-parser-java-core/build.gradle.kts index 5c40ab2..ccacdad 100644 --- a/cas-parser-java-core/build.gradle.kts +++ b/cas-parser-java-core/build.gradle.kts @@ -27,8 +27,6 @@ dependencies { implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.18.2") implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2") implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.18.2") - implementation("org.apache.httpcomponents.core5:httpcore5:5.2.4") - implementation("org.apache.httpcomponents.client5:httpclient5:5.3.1") testImplementation(kotlin("test")) testImplementation(project(":cas-parser-java-client-okhttp")) diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/client/CasParserClient.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/client/CasParserClient.kt index df928cf..6c17b45 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/client/CasParserClient.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/client/CasParserClient.kt @@ -53,28 +53,97 @@ interface CasParserClient { */ fun withOptions(modifier: Consumer): CasParserClient + /** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your API + * usage and remaining quota. + */ fun credits(): CreditService + /** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your API + * usage and remaining quota. + */ fun logs(): LogService + /** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access + * tokens can be used in place of API keys on all v4 endpoints. + */ fun accessToken(): AccessTokenService + /** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access + * tokens can be used in place of API keys on all v4 endpoints. + */ fun verifyToken(): VerifyTokenService + /** Endpoints for parsing CAS PDF files from different sources. */ fun camsKfintech(): CamsKfintechService + /** Endpoints for parsing CAS PDF files from different sources. */ fun cdsl(): CdslService + /** + * Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, + * Upstox, ICICI etc. + */ fun contractNote(): ContractNoteService + /** + * Endpoints for importing CAS files directly from user email inboxes. + * + * **Supported Providers:** Gmail (more coming soon) + * + * **How it works:** + * 1. Call `POST /v4/inbox/connect` to get an OAuth URL + * 2. Redirect user to the OAuth URL for consent + * 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + * 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + * 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + * + * **Security:** + * - Read-only access (we cannot send emails) + * - Tokens are encrypted with server-side secret + * - User can revoke access anytime via `/v4/inbox/disconnect` + */ fun inbox(): InboxService + /** Endpoints for generating new CAS documents via email mailback (KFintech). */ fun kfintech(): KfintechService + /** Endpoints for parsing CAS PDF files from different sources. */ fun nsdl(): NsdlService + /** Endpoints for parsing CAS PDF files from different sources. */ fun smart(): SmartService + /** + * Create dedicated inbound email addresses for investors to forward their CAS statements. + * + * **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or + * file upload. + * + * **How it works:** + * 1. Call `POST /v4/inbound-email` to create a unique inbound email address + * 2. Display this email to your user: "Forward your CAS statement to + * ie_xxx@import.casparser.in" + * 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your + * webhook + * 4. Your webhook receives email metadata + attachment download URLs + * + * **Sender Validation:** + * - Only emails from verified CAS authorities are processed: + * - CDSL: `eCAS@cdslstatement.com` + * - NSDL: `NSDL-CAS@nsdl.co.in` + * - CAMS: `donotreply@camsonline.com` + * - KFintech: `samfS@kfintech.com` + * - Emails failing SPF/DKIM/DMARC are rejected + * - Forwarded emails must contain the original sender in headers + * + * **Billing:** 0.2 credits per successfully processed valid email + */ fun inboundEmail(): InboundEmailService /** @@ -100,28 +169,97 @@ interface CasParserClient { */ fun withOptions(modifier: Consumer): CasParserClient.WithRawResponse + /** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your + * API usage and remaining quota. + */ fun credits(): CreditService.WithRawResponse + /** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your + * API usage and remaining quota. + */ fun logs(): LogService.WithRawResponse + /** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + * Access tokens can be used in place of API keys on all v4 endpoints. + */ fun accessToken(): AccessTokenService.WithRawResponse + /** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + * Access tokens can be used in place of API keys on all v4 endpoints. + */ fun verifyToken(): VerifyTokenService.WithRawResponse + /** Endpoints for parsing CAS PDF files from different sources. */ fun camsKfintech(): CamsKfintechService.WithRawResponse + /** Endpoints for parsing CAS PDF files from different sources. */ fun cdsl(): CdslService.WithRawResponse + /** + * Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, + * Groww, Upstox, ICICI etc. + */ fun contractNote(): ContractNoteService.WithRawResponse + /** + * Endpoints for importing CAS files directly from user email inboxes. + * + * **Supported Providers:** Gmail (more coming soon) + * + * **How it works:** + * 1. Call `POST /v4/inbox/connect` to get an OAuth URL + * 2. Redirect user to the OAuth URL for consent + * 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + * 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + * 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + * + * **Security:** + * - Read-only access (we cannot send emails) + * - Tokens are encrypted with server-side secret + * - User can revoke access anytime via `/v4/inbox/disconnect` + */ fun inbox(): InboxService.WithRawResponse + /** Endpoints for generating new CAS documents via email mailback (KFintech). */ fun kfintech(): KfintechService.WithRawResponse + /** Endpoints for parsing CAS PDF files from different sources. */ fun nsdl(): NsdlService.WithRawResponse + /** Endpoints for parsing CAS PDF files from different sources. */ fun smart(): SmartService.WithRawResponse + /** + * Create dedicated inbound email addresses for investors to forward their CAS statements. + * + * **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth + * or file upload. + * + * **How it works:** + * 1. Call `POST /v4/inbound-email` to create a unique inbound email address + * 2. Display this email to your user: "Forward your CAS statement to + * ie_xxx@import.casparser.in" + * 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your + * webhook + * 4. Your webhook receives email metadata + attachment download URLs + * + * **Sender Validation:** + * - Only emails from verified CAS authorities are processed: + * - CDSL: `eCAS@cdslstatement.com` + * - NSDL: `NSDL-CAS@nsdl.co.in` + * - CAMS: `donotreply@camsonline.com` + * - KFintech: `samfS@kfintech.com` + * - Emails failing SPF/DKIM/DMARC are rejected + * - Forwarded emails must contain the original sender in headers + * + * **Billing:** 0.2 credits per successfully processed valid email + */ fun inboundEmail(): InboundEmailService.WithRawResponse } } diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/client/CasParserClientAsync.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/client/CasParserClientAsync.kt index 4d828d5..575d503 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/client/CasParserClientAsync.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/client/CasParserClientAsync.kt @@ -53,28 +53,97 @@ interface CasParserClientAsync { */ fun withOptions(modifier: Consumer): CasParserClientAsync + /** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your API + * usage and remaining quota. + */ fun credits(): CreditServiceAsync + /** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your API + * usage and remaining quota. + */ fun logs(): LogServiceAsync + /** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access + * tokens can be used in place of API keys on all v4 endpoints. + */ fun accessToken(): AccessTokenServiceAsync + /** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access + * tokens can be used in place of API keys on all v4 endpoints. + */ fun verifyToken(): VerifyTokenServiceAsync + /** Endpoints for parsing CAS PDF files from different sources. */ fun camsKfintech(): CamsKfintechServiceAsync + /** Endpoints for parsing CAS PDF files from different sources. */ fun cdsl(): CdslServiceAsync + /** + * Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, + * Upstox, ICICI etc. + */ fun contractNote(): ContractNoteServiceAsync + /** + * Endpoints for importing CAS files directly from user email inboxes. + * + * **Supported Providers:** Gmail (more coming soon) + * + * **How it works:** + * 1. Call `POST /v4/inbox/connect` to get an OAuth URL + * 2. Redirect user to the OAuth URL for consent + * 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + * 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + * 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + * + * **Security:** + * - Read-only access (we cannot send emails) + * - Tokens are encrypted with server-side secret + * - User can revoke access anytime via `/v4/inbox/disconnect` + */ fun inbox(): InboxServiceAsync + /** Endpoints for generating new CAS documents via email mailback (KFintech). */ fun kfintech(): KfintechServiceAsync + /** Endpoints for parsing CAS PDF files from different sources. */ fun nsdl(): NsdlServiceAsync + /** Endpoints for parsing CAS PDF files from different sources. */ fun smart(): SmartServiceAsync + /** + * Create dedicated inbound email addresses for investors to forward their CAS statements. + * + * **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or + * file upload. + * + * **How it works:** + * 1. Call `POST /v4/inbound-email` to create a unique inbound email address + * 2. Display this email to your user: "Forward your CAS statement to + * ie_xxx@import.casparser.in" + * 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your + * webhook + * 4. Your webhook receives email metadata + attachment download URLs + * + * **Sender Validation:** + * - Only emails from verified CAS authorities are processed: + * - CDSL: `eCAS@cdslstatement.com` + * - NSDL: `NSDL-CAS@nsdl.co.in` + * - CAMS: `donotreply@camsonline.com` + * - KFintech: `samfS@kfintech.com` + * - Emails failing SPF/DKIM/DMARC are rejected + * - Forwarded emails must contain the original sender in headers + * + * **Billing:** 0.2 credits per successfully processed valid email + */ fun inboundEmail(): InboundEmailServiceAsync /** @@ -104,28 +173,97 @@ interface CasParserClientAsync { modifier: Consumer ): CasParserClientAsync.WithRawResponse + /** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your + * API usage and remaining quota. + */ fun credits(): CreditServiceAsync.WithRawResponse + /** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your + * API usage and remaining quota. + */ fun logs(): LogServiceAsync.WithRawResponse + /** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + * Access tokens can be used in place of API keys on all v4 endpoints. + */ fun accessToken(): AccessTokenServiceAsync.WithRawResponse + /** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + * Access tokens can be used in place of API keys on all v4 endpoints. + */ fun verifyToken(): VerifyTokenServiceAsync.WithRawResponse + /** Endpoints for parsing CAS PDF files from different sources. */ fun camsKfintech(): CamsKfintechServiceAsync.WithRawResponse + /** Endpoints for parsing CAS PDF files from different sources. */ fun cdsl(): CdslServiceAsync.WithRawResponse + /** + * Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, + * Groww, Upstox, ICICI etc. + */ fun contractNote(): ContractNoteServiceAsync.WithRawResponse + /** + * Endpoints for importing CAS files directly from user email inboxes. + * + * **Supported Providers:** Gmail (more coming soon) + * + * **How it works:** + * 1. Call `POST /v4/inbox/connect` to get an OAuth URL + * 2. Redirect user to the OAuth URL for consent + * 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + * 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + * 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + * + * **Security:** + * - Read-only access (we cannot send emails) + * - Tokens are encrypted with server-side secret + * - User can revoke access anytime via `/v4/inbox/disconnect` + */ fun inbox(): InboxServiceAsync.WithRawResponse + /** Endpoints for generating new CAS documents via email mailback (KFintech). */ fun kfintech(): KfintechServiceAsync.WithRawResponse + /** Endpoints for parsing CAS PDF files from different sources. */ fun nsdl(): NsdlServiceAsync.WithRawResponse + /** Endpoints for parsing CAS PDF files from different sources. */ fun smart(): SmartServiceAsync.WithRawResponse + /** + * Create dedicated inbound email addresses for investors to forward their CAS statements. + * + * **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth + * or file upload. + * + * **How it works:** + * 1. Call `POST /v4/inbound-email` to create a unique inbound email address + * 2. Display this email to your user: "Forward your CAS statement to + * ie_xxx@import.casparser.in" + * 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your + * webhook + * 4. Your webhook receives email metadata + attachment download URLs + * + * **Sender Validation:** + * - Only emails from verified CAS authorities are processed: + * - CDSL: `eCAS@cdslstatement.com` + * - NSDL: `NSDL-CAS@nsdl.co.in` + * - CAMS: `donotreply@camsonline.com` + * - KFintech: `samfS@kfintech.com` + * - Emails failing SPF/DKIM/DMARC are rejected + * - Forwarded emails must contain the original sender in headers + * + * **Billing:** 0.2 credits per successfully processed valid email + */ fun inboundEmail(): InboundEmailServiceAsync.WithRawResponse } } diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/client/CasParserClientAsyncImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/client/CasParserClientAsyncImpl.kt index b10af0a..eed1327 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/client/CasParserClientAsyncImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/client/CasParserClientAsyncImpl.kt @@ -96,28 +96,97 @@ class CasParserClientAsyncImpl(private val clientOptions: ClientOptions) : CasPa override fun withOptions(modifier: Consumer): CasParserClientAsync = CasParserClientAsyncImpl(clientOptions.toBuilder().apply(modifier::accept).build()) + /** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your API + * usage and remaining quota. + */ override fun credits(): CreditServiceAsync = credits + /** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your API + * usage and remaining quota. + */ override fun logs(): LogServiceAsync = logs + /** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access + * tokens can be used in place of API keys on all v4 endpoints. + */ override fun accessToken(): AccessTokenServiceAsync = accessToken + /** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access + * tokens can be used in place of API keys on all v4 endpoints. + */ override fun verifyToken(): VerifyTokenServiceAsync = verifyToken + /** Endpoints for parsing CAS PDF files from different sources. */ override fun camsKfintech(): CamsKfintechServiceAsync = camsKfintech + /** Endpoints for parsing CAS PDF files from different sources. */ override fun cdsl(): CdslServiceAsync = cdsl + /** + * Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, + * Upstox, ICICI etc. + */ override fun contractNote(): ContractNoteServiceAsync = contractNote + /** + * Endpoints for importing CAS files directly from user email inboxes. + * + * **Supported Providers:** Gmail (more coming soon) + * + * **How it works:** + * 1. Call `POST /v4/inbox/connect` to get an OAuth URL + * 2. Redirect user to the OAuth URL for consent + * 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + * 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + * 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + * + * **Security:** + * - Read-only access (we cannot send emails) + * - Tokens are encrypted with server-side secret + * - User can revoke access anytime via `/v4/inbox/disconnect` + */ override fun inbox(): InboxServiceAsync = inbox + /** Endpoints for generating new CAS documents via email mailback (KFintech). */ override fun kfintech(): KfintechServiceAsync = kfintech + /** Endpoints for parsing CAS PDF files from different sources. */ override fun nsdl(): NsdlServiceAsync = nsdl + /** Endpoints for parsing CAS PDF files from different sources. */ override fun smart(): SmartServiceAsync = smart + /** + * Create dedicated inbound email addresses for investors to forward their CAS statements. + * + * **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or + * file upload. + * + * **How it works:** + * 1. Call `POST /v4/inbound-email` to create a unique inbound email address + * 2. Display this email to your user: "Forward your CAS statement to + * ie_xxx@import.casparser.in" + * 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your + * webhook + * 4. Your webhook receives email metadata + attachment download URLs + * + * **Sender Validation:** + * - Only emails from verified CAS authorities are processed: + * - CDSL: `eCAS@cdslstatement.com` + * - NSDL: `NSDL-CAS@nsdl.co.in` + * - CAMS: `donotreply@camsonline.com` + * - KFintech: `samfS@kfintech.com` + * - Emails failing SPF/DKIM/DMARC are rejected + * - Forwarded emails must contain the original sender in headers + * + * **Billing:** 0.2 credits per successfully processed valid email + */ override fun inboundEmail(): InboundEmailServiceAsync = inboundEmail override fun close() = clientOptions.close() @@ -180,28 +249,97 @@ class CasParserClientAsyncImpl(private val clientOptions: ClientOptions) : CasPa clientOptions.toBuilder().apply(modifier::accept).build() ) + /** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your + * API usage and remaining quota. + */ override fun credits(): CreditServiceAsync.WithRawResponse = credits + /** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your + * API usage and remaining quota. + */ override fun logs(): LogServiceAsync.WithRawResponse = logs + /** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + * Access tokens can be used in place of API keys on all v4 endpoints. + */ override fun accessToken(): AccessTokenServiceAsync.WithRawResponse = accessToken + /** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + * Access tokens can be used in place of API keys on all v4 endpoints. + */ override fun verifyToken(): VerifyTokenServiceAsync.WithRawResponse = verifyToken + /** Endpoints for parsing CAS PDF files from different sources. */ override fun camsKfintech(): CamsKfintechServiceAsync.WithRawResponse = camsKfintech + /** Endpoints for parsing CAS PDF files from different sources. */ override fun cdsl(): CdslServiceAsync.WithRawResponse = cdsl + /** + * Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, + * Groww, Upstox, ICICI etc. + */ override fun contractNote(): ContractNoteServiceAsync.WithRawResponse = contractNote + /** + * Endpoints for importing CAS files directly from user email inboxes. + * + * **Supported Providers:** Gmail (more coming soon) + * + * **How it works:** + * 1. Call `POST /v4/inbox/connect` to get an OAuth URL + * 2. Redirect user to the OAuth URL for consent + * 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + * 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + * 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + * + * **Security:** + * - Read-only access (we cannot send emails) + * - Tokens are encrypted with server-side secret + * - User can revoke access anytime via `/v4/inbox/disconnect` + */ override fun inbox(): InboxServiceAsync.WithRawResponse = inbox + /** Endpoints for generating new CAS documents via email mailback (KFintech). */ override fun kfintech(): KfintechServiceAsync.WithRawResponse = kfintech + /** Endpoints for parsing CAS PDF files from different sources. */ override fun nsdl(): NsdlServiceAsync.WithRawResponse = nsdl + /** Endpoints for parsing CAS PDF files from different sources. */ override fun smart(): SmartServiceAsync.WithRawResponse = smart + /** + * Create dedicated inbound email addresses for investors to forward their CAS statements. + * + * **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth + * or file upload. + * + * **How it works:** + * 1. Call `POST /v4/inbound-email` to create a unique inbound email address + * 2. Display this email to your user: "Forward your CAS statement to + * ie_xxx@import.casparser.in" + * 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your + * webhook + * 4. Your webhook receives email metadata + attachment download URLs + * + * **Sender Validation:** + * - Only emails from verified CAS authorities are processed: + * - CDSL: `eCAS@cdslstatement.com` + * - NSDL: `NSDL-CAS@nsdl.co.in` + * - CAMS: `donotreply@camsonline.com` + * - KFintech: `samfS@kfintech.com` + * - Emails failing SPF/DKIM/DMARC are rejected + * - Forwarded emails must contain the original sender in headers + * + * **Billing:** 0.2 credits per successfully processed valid email + */ override fun inboundEmail(): InboundEmailServiceAsync.WithRawResponse = inboundEmail } } diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/client/CasParserClientImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/client/CasParserClientImpl.kt index db32253..75ee210 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/client/CasParserClientImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/client/CasParserClientImpl.kt @@ -90,28 +90,97 @@ class CasParserClientImpl(private val clientOptions: ClientOptions) : CasParserC override fun withOptions(modifier: Consumer): CasParserClient = CasParserClientImpl(clientOptions.toBuilder().apply(modifier::accept).build()) + /** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your API + * usage and remaining quota. + */ override fun credits(): CreditService = credits + /** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your API + * usage and remaining quota. + */ override fun logs(): LogService = logs + /** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access + * tokens can be used in place of API keys on all v4 endpoints. + */ override fun accessToken(): AccessTokenService = accessToken + /** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access + * tokens can be used in place of API keys on all v4 endpoints. + */ override fun verifyToken(): VerifyTokenService = verifyToken + /** Endpoints for parsing CAS PDF files from different sources. */ override fun camsKfintech(): CamsKfintechService = camsKfintech + /** Endpoints for parsing CAS PDF files from different sources. */ override fun cdsl(): CdslService = cdsl + /** + * Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, + * Upstox, ICICI etc. + */ override fun contractNote(): ContractNoteService = contractNote + /** + * Endpoints for importing CAS files directly from user email inboxes. + * + * **Supported Providers:** Gmail (more coming soon) + * + * **How it works:** + * 1. Call `POST /v4/inbox/connect` to get an OAuth URL + * 2. Redirect user to the OAuth URL for consent + * 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + * 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + * 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + * + * **Security:** + * - Read-only access (we cannot send emails) + * - Tokens are encrypted with server-side secret + * - User can revoke access anytime via `/v4/inbox/disconnect` + */ override fun inbox(): InboxService = inbox + /** Endpoints for generating new CAS documents via email mailback (KFintech). */ override fun kfintech(): KfintechService = kfintech + /** Endpoints for parsing CAS PDF files from different sources. */ override fun nsdl(): NsdlService = nsdl + /** Endpoints for parsing CAS PDF files from different sources. */ override fun smart(): SmartService = smart + /** + * Create dedicated inbound email addresses for investors to forward their CAS statements. + * + * **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or + * file upload. + * + * **How it works:** + * 1. Call `POST /v4/inbound-email` to create a unique inbound email address + * 2. Display this email to your user: "Forward your CAS statement to + * ie_xxx@import.casparser.in" + * 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your + * webhook + * 4. Your webhook receives email metadata + attachment download URLs + * + * **Sender Validation:** + * - Only emails from verified CAS authorities are processed: + * - CDSL: `eCAS@cdslstatement.com` + * - NSDL: `NSDL-CAS@nsdl.co.in` + * - CAMS: `donotreply@camsonline.com` + * - KFintech: `samfS@kfintech.com` + * - Emails failing SPF/DKIM/DMARC are rejected + * - Forwarded emails must contain the original sender in headers + * + * **Billing:** 0.2 credits per successfully processed valid email + */ override fun inboundEmail(): InboundEmailService = inboundEmail override fun close() = clientOptions.close() @@ -174,28 +243,97 @@ class CasParserClientImpl(private val clientOptions: ClientOptions) : CasParserC clientOptions.toBuilder().apply(modifier::accept).build() ) + /** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your + * API usage and remaining quota. + */ override fun credits(): CreditService.WithRawResponse = credits + /** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your + * API usage and remaining quota. + */ override fun logs(): LogService.WithRawResponse = logs + /** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + * Access tokens can be used in place of API keys on all v4 endpoints. + */ override fun accessToken(): AccessTokenService.WithRawResponse = accessToken + /** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. + * Access tokens can be used in place of API keys on all v4 endpoints. + */ override fun verifyToken(): VerifyTokenService.WithRawResponse = verifyToken + /** Endpoints for parsing CAS PDF files from different sources. */ override fun camsKfintech(): CamsKfintechService.WithRawResponse = camsKfintech + /** Endpoints for parsing CAS PDF files from different sources. */ override fun cdsl(): CdslService.WithRawResponse = cdsl + /** + * Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, + * Groww, Upstox, ICICI etc. + */ override fun contractNote(): ContractNoteService.WithRawResponse = contractNote + /** + * Endpoints for importing CAS files directly from user email inboxes. + * + * **Supported Providers:** Gmail (more coming soon) + * + * **How it works:** + * 1. Call `POST /v4/inbox/connect` to get an OAuth URL + * 2. Redirect user to the OAuth URL for consent + * 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + * 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + * 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + * + * **Security:** + * - Read-only access (we cannot send emails) + * - Tokens are encrypted with server-side secret + * - User can revoke access anytime via `/v4/inbox/disconnect` + */ override fun inbox(): InboxService.WithRawResponse = inbox + /** Endpoints for generating new CAS documents via email mailback (KFintech). */ override fun kfintech(): KfintechService.WithRawResponse = kfintech + /** Endpoints for parsing CAS PDF files from different sources. */ override fun nsdl(): NsdlService.WithRawResponse = nsdl + /** Endpoints for parsing CAS PDF files from different sources. */ override fun smart(): SmartService.WithRawResponse = smart + /** + * Create dedicated inbound email addresses for investors to forward their CAS statements. + * + * **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth + * or file upload. + * + * **How it works:** + * 1. Call `POST /v4/inbound-email` to create a unique inbound email address + * 2. Display this email to your user: "Forward your CAS statement to + * ie_xxx@import.casparser.in" + * 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your + * webhook + * 4. Your webhook receives email metadata + attachment download URLs + * + * **Sender Validation:** + * - Only emails from verified CAS authorities are processed: + * - CDSL: `eCAS@cdslstatement.com` + * - NSDL: `NSDL-CAS@nsdl.co.in` + * - CAMS: `donotreply@camsonline.com` + * - KFintech: `samfS@kfintech.com` + * - Emails failing SPF/DKIM/DMARC are rejected + * - Forwarded emails must contain the original sender in headers + * + * **Billing:** 0.2 credits per successfully processed valid email + */ override fun inboundEmail(): InboundEmailService.WithRawResponse = inboundEmail } } diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/core/Properties.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/core/Properties.kt index dc9a375..f946e80 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/core/Properties.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/core/Properties.kt @@ -34,9 +34,9 @@ fun getOsName(): String { } } -fun getOsVersion(): String = System.getProperty("os.version", "unknown") +fun getOsVersion(): String = System.getProperty("os.version", "unknown") ?: "unknown" fun getPackageVersion(): String = - CasParserClient::class.java.`package`.implementationVersion ?: "unknown" + CasParserClient::class.java.`package`?.implementationVersion ?: "unknown" -fun getJavaVersion(): String = System.getProperty("java.version", "unknown") +fun getJavaVersion(): String = System.getProperty("java.version", "unknown") ?: "unknown" diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/core/http/HttpRequestBodies.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/core/http/HttpRequestBodies.kt index 34ce9d6..6f7c370 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/core/http/HttpRequestBodies.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/core/http/HttpRequestBodies.kt @@ -5,16 +5,16 @@ package com.cas_parser.api.core.http import com.cas_parser.api.core.MultipartField +import com.cas_parser.api.core.toImmutable import com.cas_parser.api.errors.CasParserInvalidDataException import com.fasterxml.jackson.databind.JsonNode import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.databind.node.JsonNodeType +import java.io.ByteArrayInputStream import java.io.InputStream import java.io.OutputStream +import java.util.UUID import kotlin.jvm.optionals.getOrNull -import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder -import org.apache.hc.core5.http.ContentType -import org.apache.hc.core5.http.HttpEntity @JvmSynthetic internal inline fun json(jsonMapper: JsonMapper, value: T): HttpRequestBody = @@ -37,94 +37,231 @@ internal fun multipartFormData( jsonMapper: JsonMapper, fields: Map>, ): HttpRequestBody = - object : HttpRequestBody { - private val entity: HttpEntity by lazy { - MultipartEntityBuilder.create() - .apply { - fields.forEach { (name, field) -> - val knownValue = field.value.asKnown().getOrNull() - val parts = - if (knownValue is InputStream) { - // Read directly from the `InputStream` instead of reading it all - // into memory due to the `jsonMapper` serialization below. - sequenceOf(name to knownValue) - } else { - val node = jsonMapper.valueToTree(field.value) - serializePart(name, node) + MultipartBody.Builder() + .apply { + fields.forEach { (name, field) -> + val knownValue = field.value.asKnown().getOrNull() + val parts = + if (knownValue is InputStream) { + // Read directly from the `InputStream` instead of reading it all + // into memory due to the `jsonMapper` serialization below. + sequenceOf(name to knownValue) + } else { + val node = jsonMapper.valueToTree(field.value) + serializePart(name, node) + } + + parts.forEach { (name, bytes) -> + val partBody = + if (bytes is ByteArrayInputStream) { + val byteArray = bytes.readBytes() + + object : HttpRequestBody { + + override fun writeTo(outputStream: OutputStream) { + outputStream.write(byteArray) + } + + override fun contentType(): String = field.contentType + + override fun contentLength(): Long = byteArray.size.toLong() + + override fun repeatable(): Boolean = true + + override fun close() {} } + } else { + object : HttpRequestBody { + + override fun writeTo(outputStream: OutputStream) { + bytes.copyTo(outputStream) + } + + override fun contentType(): String = field.contentType + + override fun contentLength(): Long = -1L - parts.forEach { (name, bytes) -> - addBinaryBody( - name, - bytes, - ContentType.parseLenient(field.contentType), - field.filename().getOrNull(), - ) + override fun repeatable(): Boolean = false + + override fun close() = bytes.close() + } } - } + + addPart( + MultipartBody.Part.create( + name, + field.filename().getOrNull(), + field.contentType, + partBody, + ) + ) } - .build() + } } + .build() - private fun serializePart( - name: String, - node: JsonNode, - ): Sequence> = - when (node.nodeType) { - JsonNodeType.MISSING, - JsonNodeType.NULL -> emptySequence() - JsonNodeType.BINARY -> sequenceOf(name to node.binaryValue().inputStream()) - JsonNodeType.STRING -> sequenceOf(name to node.textValue().inputStream()) - JsonNodeType.BOOLEAN -> - sequenceOf(name to node.booleanValue().toString().inputStream()) - JsonNodeType.NUMBER -> - sequenceOf(name to node.numberValue().toString().inputStream()) - JsonNodeType.ARRAY -> - sequenceOf( - name to - node - .elements() - .asSequence() - .mapNotNull { element -> - when (element.nodeType) { - JsonNodeType.MISSING, - JsonNodeType.NULL -> null - JsonNodeType.STRING -> node.textValue() - JsonNodeType.BOOLEAN -> node.booleanValue().toString() - JsonNodeType.NUMBER -> node.numberValue().toString() - null, - JsonNodeType.BINARY, - JsonNodeType.ARRAY, - JsonNodeType.OBJECT, - JsonNodeType.POJO -> - throw CasParserInvalidDataException( - "Unexpected JsonNode type in array: ${node.nodeType}" - ) - } - } - .joinToString(",") - .inputStream() - ) - JsonNodeType.OBJECT -> - node.fields().asSequence().flatMap { (key, value) -> - serializePart("$name[$key]", value) - } - JsonNodeType.POJO, - null -> - throw CasParserInvalidDataException( - "Unexpected JsonNode type: ${node.nodeType}" - ) +private fun serializePart(name: String, node: JsonNode): Sequence> = + when (node.nodeType) { + JsonNodeType.MISSING, + JsonNodeType.NULL -> emptySequence() + JsonNodeType.BINARY -> sequenceOf(name to node.binaryValue().inputStream()) + JsonNodeType.STRING -> sequenceOf(name to node.textValue().byteInputStream()) + JsonNodeType.BOOLEAN -> sequenceOf(name to node.booleanValue().toString().byteInputStream()) + JsonNodeType.NUMBER -> sequenceOf(name to node.numberValue().toString().byteInputStream()) + JsonNodeType.ARRAY -> + sequenceOf( + name to + node + .elements() + .asSequence() + .mapNotNull { element -> + when (element.nodeType) { + JsonNodeType.MISSING, + JsonNodeType.NULL -> null + JsonNodeType.STRING -> element.textValue() + JsonNodeType.BOOLEAN -> element.booleanValue().toString() + JsonNodeType.NUMBER -> element.numberValue().toString() + null, + JsonNodeType.BINARY, + JsonNodeType.ARRAY, + JsonNodeType.OBJECT, + JsonNodeType.POJO -> + throw CasParserInvalidDataException( + "Unexpected JsonNode type in array: ${element.nodeType}" + ) + } + } + .joinToString(",") + .byteInputStream() + ) + JsonNodeType.OBJECT -> + node.fields().asSequence().flatMap { (key, value) -> + serializePart("$name[$key]", value) } + JsonNodeType.POJO, + null -> throw CasParserInvalidDataException("Unexpected JsonNode type: ${node.nodeType}") + } - private fun String.inputStream(): InputStream = toByteArray().inputStream() +private class MultipartBody +private constructor(private val boundary: String, private val parts: List) : HttpRequestBody { + private val boundaryBytes: ByteArray = boundary.toByteArray() + private val contentType = "multipart/form-data; boundary=$boundary" - override fun writeTo(outputStream: OutputStream) = entity.writeTo(outputStream) + // This must remain in sync with `contentLength`. + override fun writeTo(outputStream: OutputStream) { + parts.forEach { part -> + outputStream.write(DASHDASH) + outputStream.write(boundaryBytes) + outputStream.write(CRLF) - override fun contentType(): String = entity.contentType + outputStream.write(CONTENT_DISPOSITION) + outputStream.write(part.contentDisposition.toByteArray()) + outputStream.write(CRLF) - override fun contentLength(): Long = entity.contentLength + outputStream.write(CONTENT_TYPE) + outputStream.write(part.contentType.toByteArray()) + outputStream.write(CRLF) - override fun repeatable(): Boolean = entity.isRepeatable + outputStream.write(CRLF) + part.body.writeTo(outputStream) + outputStream.write(CRLF) + } - override fun close() = entity.close() + outputStream.write(DASHDASH) + outputStream.write(boundaryBytes) + outputStream.write(DASHDASH) + outputStream.write(CRLF) + } + + override fun contentType(): String = contentType + + // This must remain in sync with `writeTo`. + override fun contentLength(): Long { + var byteCount = 0L + + parts.forEach { part -> + val contentLength = part.body.contentLength() + if (contentLength == -1L) { + return -1L + } + + byteCount += + DASHDASH.size + + boundaryBytes.size + + CRLF.size + + CONTENT_DISPOSITION.size + + part.contentDisposition.toByteArray().size + + CRLF.size + + CONTENT_TYPE.size + + part.contentType.toByteArray().size + + CRLF.size + + CRLF.size + + contentLength + + CRLF.size + } + + byteCount += DASHDASH.size + boundaryBytes.size + DASHDASH.size + CRLF.size + return byteCount + } + + override fun repeatable(): Boolean = parts.all { it.body.repeatable() } + + override fun close() { + parts.forEach { it.body.close() } + } + + class Builder { + private val boundary = UUID.randomUUID().toString() + private val parts: MutableList = mutableListOf() + + fun addPart(part: Part) = apply { parts.add(part) } + + fun build() = MultipartBody(boundary, parts.toImmutable()) + } + + class Part + private constructor( + val contentDisposition: String, + val contentType: String, + val body: HttpRequestBody, + ) { + companion object { + fun create( + name: String, + filename: String?, + contentType: String, + body: HttpRequestBody, + ): Part { + val disposition = buildString { + append("form-data; name=") + appendQuotedString(name) + if (filename != null) { + append("; filename=") + appendQuotedString(filename) + } + } + return Part(disposition, contentType, body) + } + } + } + + companion object { + private val CRLF = byteArrayOf('\r'.code.toByte(), '\n'.code.toByte()) + private val DASHDASH = byteArrayOf('-'.code.toByte(), '-'.code.toByte()) + private val CONTENT_DISPOSITION = "Content-Disposition: ".toByteArray() + private val CONTENT_TYPE = "Content-Type: ".toByteArray() + + private fun StringBuilder.appendQuotedString(key: String) { + append('"') + for (ch in key) { + when (ch) { + '\n' -> append("%0A") + '\r' -> append("%0D") + '"' -> append("%22") + else -> append(ch) + } + } + append('"') + } } +} diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/core/http/RetryingHttpClient.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/core/http/RetryingHttpClient.kt index 3f34f6e..7b39c62 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/core/http/RetryingHttpClient.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/core/http/RetryingHttpClient.kt @@ -1,3 +1,5 @@ +// File generated from our OpenAPI spec by Stainless. + package com.cas_parser.api.core.http import com.cas_parser.api.core.DefaultSleeper diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/AccessTokenServiceAsync.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/AccessTokenServiceAsync.kt index acfdef1..6f0e9db 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/AccessTokenServiceAsync.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/AccessTokenServiceAsync.kt @@ -10,6 +10,11 @@ import com.cas_parser.api.models.accesstoken.AccessTokenCreateResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access + * tokens can be used in place of API keys on all v4 endpoints. + */ interface AccessTokenServiceAsync { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/AccessTokenServiceAsyncImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/AccessTokenServiceAsyncImpl.kt index ec51534..a36cbb7 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/AccessTokenServiceAsyncImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/AccessTokenServiceAsyncImpl.kt @@ -20,6 +20,11 @@ import com.cas_parser.api.models.accesstoken.AccessTokenCreateResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access + * tokens can be used in place of API keys on all v4 endpoints. + */ class AccessTokenServiceAsyncImpl internal constructor(private val clientOptions: ClientOptions) : AccessTokenServiceAsync { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CamsKfintechServiceAsync.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CamsKfintechServiceAsync.kt index c7651f1..e8d7cc2 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CamsKfintechServiceAsync.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CamsKfintechServiceAsync.kt @@ -10,6 +10,7 @@ import com.cas_parser.api.models.camskfintech.UnifiedResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** Endpoints for parsing CAS PDF files from different sources. */ interface CamsKfintechServiceAsync { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CamsKfintechServiceAsyncImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CamsKfintechServiceAsyncImpl.kt index ef05059..df53226 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CamsKfintechServiceAsyncImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CamsKfintechServiceAsyncImpl.kt @@ -20,6 +20,7 @@ import com.cas_parser.api.models.camskfintech.UnifiedResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** Endpoints for parsing CAS PDF files from different sources. */ class CamsKfintechServiceAsyncImpl internal constructor(private val clientOptions: ClientOptions) : CamsKfintechServiceAsync { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CdslServiceAsync.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CdslServiceAsync.kt index b00e28d..dd5e7d3 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CdslServiceAsync.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CdslServiceAsync.kt @@ -11,6 +11,7 @@ import com.cas_parser.api.services.async.cdsl.FetchServiceAsync import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** Endpoints for parsing CAS PDF files from different sources. */ interface CdslServiceAsync { /** @@ -25,6 +26,10 @@ interface CdslServiceAsync { */ fun withOptions(modifier: Consumer): CdslServiceAsync + /** + * Endpoints for fetching CAS documents with instant download. Currently supports CDSL via OTP + * authentication. + */ fun fetch(): FetchServiceAsync /** @@ -58,6 +63,10 @@ interface CdslServiceAsync { */ fun withOptions(modifier: Consumer): CdslServiceAsync.WithRawResponse + /** + * Endpoints for fetching CAS documents with instant download. Currently supports CDSL via + * OTP authentication. + */ fun fetch(): FetchServiceAsync.WithRawResponse /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CdslServiceAsyncImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CdslServiceAsyncImpl.kt index 2531112..0bb2f66 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CdslServiceAsyncImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CdslServiceAsyncImpl.kt @@ -22,6 +22,7 @@ import com.cas_parser.api.services.async.cdsl.FetchServiceAsyncImpl import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** Endpoints for parsing CAS PDF files from different sources. */ class CdslServiceAsyncImpl internal constructor(private val clientOptions: ClientOptions) : CdslServiceAsync { @@ -36,6 +37,10 @@ class CdslServiceAsyncImpl internal constructor(private val clientOptions: Clien override fun withOptions(modifier: Consumer): CdslServiceAsync = CdslServiceAsyncImpl(clientOptions.toBuilder().apply(modifier::accept).build()) + /** + * Endpoints for fetching CAS documents with instant download. Currently supports CDSL via OTP + * authentication. + */ override fun fetch(): FetchServiceAsync = fetch override fun parsePdf( @@ -62,6 +67,10 @@ class CdslServiceAsyncImpl internal constructor(private val clientOptions: Clien clientOptions.toBuilder().apply(modifier::accept).build() ) + /** + * Endpoints for fetching CAS documents with instant download. Currently supports CDSL via + * OTP authentication. + */ override fun fetch(): FetchServiceAsync.WithRawResponse = fetch private val parsePdfHandler: Handler = diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/ContractNoteServiceAsync.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/ContractNoteServiceAsync.kt index 6168665..2fee7ca 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/ContractNoteServiceAsync.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/ContractNoteServiceAsync.kt @@ -10,6 +10,10 @@ import com.cas_parser.api.models.contractnote.ContractNoteParseResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** + * Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, + * Upstox, ICICI etc. + */ interface ContractNoteServiceAsync { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/ContractNoteServiceAsyncImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/ContractNoteServiceAsyncImpl.kt index 7b8d062..b9c1014 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/ContractNoteServiceAsyncImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/ContractNoteServiceAsyncImpl.kt @@ -20,6 +20,10 @@ import com.cas_parser.api.models.contractnote.ContractNoteParseResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** + * Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, + * Upstox, ICICI etc. + */ class ContractNoteServiceAsyncImpl internal constructor(private val clientOptions: ClientOptions) : ContractNoteServiceAsync { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CreditServiceAsync.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CreditServiceAsync.kt index dd5a54f..e737edd 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CreditServiceAsync.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CreditServiceAsync.kt @@ -10,6 +10,10 @@ import com.cas_parser.api.models.credits.CreditCheckResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your API + * usage and remaining quota. + */ interface CreditServiceAsync { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CreditServiceAsyncImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CreditServiceAsyncImpl.kt index 02b806c..e585dd5 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CreditServiceAsyncImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/CreditServiceAsyncImpl.kt @@ -20,6 +20,10 @@ import com.cas_parser.api.models.credits.CreditCheckResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your API + * usage and remaining quota. + */ class CreditServiceAsyncImpl internal constructor(private val clientOptions: ClientOptions) : CreditServiceAsync { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/InboundEmailServiceAsync.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/InboundEmailServiceAsync.kt index 8ff0edb..2c1b443 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/InboundEmailServiceAsync.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/InboundEmailServiceAsync.kt @@ -16,6 +16,29 @@ import com.cas_parser.api.models.inboundemail.InboundEmailRetrieveResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** + * Create dedicated inbound email addresses for investors to forward their CAS statements. + * + * **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or file + * upload. + * + * **How it works:** + * 1. Call `POST /v4/inbound-email` to create a unique inbound email address + * 2. Display this email to your user: "Forward your CAS statement to ie_xxx@import.casparser.in" + * 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your webhook + * 4. Your webhook receives email metadata + attachment download URLs + * + * **Sender Validation:** + * - Only emails from verified CAS authorities are processed: + * - CDSL: `eCAS@cdslstatement.com` + * - NSDL: `NSDL-CAS@nsdl.co.in` + * - CAMS: `donotreply@camsonline.com` + * - KFintech: `samfS@kfintech.com` + * - Emails failing SPF/DKIM/DMARC are rejected + * - Forwarded emails must contain the original sender in headers + * + * **Billing:** 0.2 credits per successfully processed valid email + */ interface InboundEmailServiceAsync { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/InboundEmailServiceAsyncImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/InboundEmailServiceAsyncImpl.kt index a199b5f..9131ae8 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/InboundEmailServiceAsyncImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/InboundEmailServiceAsyncImpl.kt @@ -28,6 +28,29 @@ import java.util.concurrent.CompletableFuture import java.util.function.Consumer import kotlin.jvm.optionals.getOrNull +/** + * Create dedicated inbound email addresses for investors to forward their CAS statements. + * + * **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or file + * upload. + * + * **How it works:** + * 1. Call `POST /v4/inbound-email` to create a unique inbound email address + * 2. Display this email to your user: "Forward your CAS statement to ie_xxx@import.casparser.in" + * 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your webhook + * 4. Your webhook receives email metadata + attachment download URLs + * + * **Sender Validation:** + * - Only emails from verified CAS authorities are processed: + * - CDSL: `eCAS@cdslstatement.com` + * - NSDL: `NSDL-CAS@nsdl.co.in` + * - CAMS: `donotreply@camsonline.com` + * - KFintech: `samfS@kfintech.com` + * - Emails failing SPF/DKIM/DMARC are rejected + * - Forwarded emails must contain the original sender in headers + * + * **Billing:** 0.2 credits per successfully processed valid email + */ class InboundEmailServiceAsyncImpl internal constructor(private val clientOptions: ClientOptions) : InboundEmailServiceAsync { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/InboxServiceAsync.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/InboxServiceAsync.kt index b488405..d2a2a85 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/InboxServiceAsync.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/InboxServiceAsync.kt @@ -16,6 +16,23 @@ import com.cas_parser.api.models.inbox.InboxListCasFilesResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** + * Endpoints for importing CAS files directly from user email inboxes. + * + * **Supported Providers:** Gmail (more coming soon) + * + * **How it works:** + * 1. Call `POST /v4/inbox/connect` to get an OAuth URL + * 2. Redirect user to the OAuth URL for consent + * 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + * 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + * 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + * + * **Security:** + * - Read-only access (we cannot send emails) + * - Tokens are encrypted with server-side secret + * - User can revoke access anytime via `/v4/inbox/disconnect` + */ interface InboxServiceAsync { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/InboxServiceAsyncImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/InboxServiceAsyncImpl.kt index 29c7d03..f3f2599 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/InboxServiceAsyncImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/InboxServiceAsyncImpl.kt @@ -26,6 +26,23 @@ import com.cas_parser.api.models.inbox.InboxListCasFilesResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** + * Endpoints for importing CAS files directly from user email inboxes. + * + * **Supported Providers:** Gmail (more coming soon) + * + * **How it works:** + * 1. Call `POST /v4/inbox/connect` to get an OAuth URL + * 2. Redirect user to the OAuth URL for consent + * 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + * 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + * 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + * + * **Security:** + * - Read-only access (we cannot send emails) + * - Tokens are encrypted with server-side secret + * - User can revoke access anytime via `/v4/inbox/disconnect` + */ class InboxServiceAsyncImpl internal constructor(private val clientOptions: ClientOptions) : InboxServiceAsync { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/KfintechServiceAsync.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/KfintechServiceAsync.kt index fa22355..e2bfdc7 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/KfintechServiceAsync.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/KfintechServiceAsync.kt @@ -10,6 +10,7 @@ import com.cas_parser.api.models.kfintech.KfintechGenerateCasResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** Endpoints for generating new CAS documents via email mailback (KFintech). */ interface KfintechServiceAsync { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/KfintechServiceAsyncImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/KfintechServiceAsyncImpl.kt index 2445261..df04e64 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/KfintechServiceAsyncImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/KfintechServiceAsyncImpl.kt @@ -20,6 +20,7 @@ import com.cas_parser.api.models.kfintech.KfintechGenerateCasResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** Endpoints for generating new CAS documents via email mailback (KFintech). */ class KfintechServiceAsyncImpl internal constructor(private val clientOptions: ClientOptions) : KfintechServiceAsync { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/LogServiceAsync.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/LogServiceAsync.kt index 3f2546a..c74e947 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/LogServiceAsync.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/LogServiceAsync.kt @@ -12,6 +12,10 @@ import com.cas_parser.api.models.logs.LogGetSummaryResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your API + * usage and remaining quota. + */ interface LogServiceAsync { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/LogServiceAsyncImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/LogServiceAsyncImpl.kt index a4bf0f2..f2cc6a7 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/LogServiceAsyncImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/LogServiceAsyncImpl.kt @@ -22,6 +22,10 @@ import com.cas_parser.api.models.logs.LogGetSummaryResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your API + * usage and remaining quota. + */ class LogServiceAsyncImpl internal constructor(private val clientOptions: ClientOptions) : LogServiceAsync { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/NsdlServiceAsync.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/NsdlServiceAsync.kt index d9571cb..cb33723 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/NsdlServiceAsync.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/NsdlServiceAsync.kt @@ -10,6 +10,7 @@ import com.cas_parser.api.models.nsdl.NsdlParseParams import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** Endpoints for parsing CAS PDF files from different sources. */ interface NsdlServiceAsync { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/NsdlServiceAsyncImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/NsdlServiceAsyncImpl.kt index 8fd0b0c..ee79b39 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/NsdlServiceAsyncImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/NsdlServiceAsyncImpl.kt @@ -20,6 +20,7 @@ import com.cas_parser.api.models.nsdl.NsdlParseParams import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** Endpoints for parsing CAS PDF files from different sources. */ class NsdlServiceAsyncImpl internal constructor(private val clientOptions: ClientOptions) : NsdlServiceAsync { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/SmartServiceAsync.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/SmartServiceAsync.kt index 9fb8951..af7ed57 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/SmartServiceAsync.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/SmartServiceAsync.kt @@ -10,6 +10,7 @@ import com.cas_parser.api.models.smart.SmartParseCasPdfParams import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** Endpoints for parsing CAS PDF files from different sources. */ interface SmartServiceAsync { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/SmartServiceAsyncImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/SmartServiceAsyncImpl.kt index 618e4fc..02f8c0a 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/SmartServiceAsyncImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/SmartServiceAsyncImpl.kt @@ -20,6 +20,7 @@ import com.cas_parser.api.models.smart.SmartParseCasPdfParams import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** Endpoints for parsing CAS PDF files from different sources. */ class SmartServiceAsyncImpl internal constructor(private val clientOptions: ClientOptions) : SmartServiceAsync { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/VerifyTokenServiceAsync.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/VerifyTokenServiceAsync.kt index 3d8b41e..fa58dfc 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/VerifyTokenServiceAsync.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/VerifyTokenServiceAsync.kt @@ -10,6 +10,11 @@ import com.cas_parser.api.models.verifytoken.VerifyTokenVerifyResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access + * tokens can be used in place of API keys on all v4 endpoints. + */ interface VerifyTokenServiceAsync { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/VerifyTokenServiceAsyncImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/VerifyTokenServiceAsyncImpl.kt index 9a42c63..cf826af 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/VerifyTokenServiceAsyncImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/VerifyTokenServiceAsyncImpl.kt @@ -20,6 +20,11 @@ import com.cas_parser.api.models.verifytoken.VerifyTokenVerifyResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access + * tokens can be used in place of API keys on all v4 endpoints. + */ class VerifyTokenServiceAsyncImpl internal constructor(private val clientOptions: ClientOptions) : VerifyTokenServiceAsync { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/cdsl/FetchServiceAsync.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/cdsl/FetchServiceAsync.kt index 83b8d52..a60287d 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/cdsl/FetchServiceAsync.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/cdsl/FetchServiceAsync.kt @@ -12,6 +12,10 @@ import com.cas_parser.api.models.cdsl.fetch.FetchVerifyOtpResponse import java.util.concurrent.CompletableFuture import java.util.function.Consumer +/** + * Endpoints for fetching CAS documents with instant download. Currently supports CDSL via OTP + * authentication. + */ interface FetchServiceAsync { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/cdsl/FetchServiceAsyncImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/cdsl/FetchServiceAsyncImpl.kt index fb97ea9..9e9453a 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/cdsl/FetchServiceAsyncImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/async/cdsl/FetchServiceAsyncImpl.kt @@ -24,6 +24,10 @@ import java.util.concurrent.CompletableFuture import java.util.function.Consumer import kotlin.jvm.optionals.getOrNull +/** + * Endpoints for fetching CAS documents with instant download. Currently supports CDSL via OTP + * authentication. + */ class FetchServiceAsyncImpl internal constructor(private val clientOptions: ClientOptions) : FetchServiceAsync { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/AccessTokenService.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/AccessTokenService.kt index 784798e..cef38eb 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/AccessTokenService.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/AccessTokenService.kt @@ -10,6 +10,11 @@ import com.cas_parser.api.models.accesstoken.AccessTokenCreateResponse import com.google.errorprone.annotations.MustBeClosed import java.util.function.Consumer +/** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access + * tokens can be used in place of API keys on all v4 endpoints. + */ interface AccessTokenService { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/AccessTokenServiceImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/AccessTokenServiceImpl.kt index 9fe0782..cc2c4c0 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/AccessTokenServiceImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/AccessTokenServiceImpl.kt @@ -19,6 +19,11 @@ import com.cas_parser.api.models.accesstoken.AccessTokenCreateParams import com.cas_parser.api.models.accesstoken.AccessTokenCreateResponse import java.util.function.Consumer +/** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access + * tokens can be used in place of API keys on all v4 endpoints. + */ class AccessTokenServiceImpl internal constructor(private val clientOptions: ClientOptions) : AccessTokenService { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CamsKfintechService.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CamsKfintechService.kt index 294210c..c8841a5 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CamsKfintechService.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CamsKfintechService.kt @@ -10,6 +10,7 @@ import com.cas_parser.api.models.camskfintech.UnifiedResponse import com.google.errorprone.annotations.MustBeClosed import java.util.function.Consumer +/** Endpoints for parsing CAS PDF files from different sources. */ interface CamsKfintechService { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CamsKfintechServiceImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CamsKfintechServiceImpl.kt index 867c1c6..7796a20 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CamsKfintechServiceImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CamsKfintechServiceImpl.kt @@ -19,6 +19,7 @@ import com.cas_parser.api.models.camskfintech.CamsKfintechParseParams import com.cas_parser.api.models.camskfintech.UnifiedResponse import java.util.function.Consumer +/** Endpoints for parsing CAS PDF files from different sources. */ class CamsKfintechServiceImpl internal constructor(private val clientOptions: ClientOptions) : CamsKfintechService { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CdslService.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CdslService.kt index a8241be..7514c91 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CdslService.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CdslService.kt @@ -11,6 +11,7 @@ import com.cas_parser.api.services.blocking.cdsl.FetchService import com.google.errorprone.annotations.MustBeClosed import java.util.function.Consumer +/** Endpoints for parsing CAS PDF files from different sources. */ interface CdslService { /** @@ -25,6 +26,10 @@ interface CdslService { */ fun withOptions(modifier: Consumer): CdslService + /** + * Endpoints for fetching CAS documents with instant download. Currently supports CDSL via OTP + * authentication. + */ fun fetch(): FetchService /** @@ -57,6 +62,10 @@ interface CdslService { */ fun withOptions(modifier: Consumer): CdslService.WithRawResponse + /** + * Endpoints for fetching CAS documents with instant download. Currently supports CDSL via + * OTP authentication. + */ fun fetch(): FetchService.WithRawResponse /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CdslServiceImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CdslServiceImpl.kt index 0155aa6..7bbc6a3 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CdslServiceImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CdslServiceImpl.kt @@ -21,6 +21,7 @@ import com.cas_parser.api.services.blocking.cdsl.FetchService import com.cas_parser.api.services.blocking.cdsl.FetchServiceImpl import java.util.function.Consumer +/** Endpoints for parsing CAS PDF files from different sources. */ class CdslServiceImpl internal constructor(private val clientOptions: ClientOptions) : CdslService { private val withRawResponse: CdslService.WithRawResponse by lazy { @@ -34,6 +35,10 @@ class CdslServiceImpl internal constructor(private val clientOptions: ClientOpti override fun withOptions(modifier: Consumer): CdslService = CdslServiceImpl(clientOptions.toBuilder().apply(modifier::accept).build()) + /** + * Endpoints for fetching CAS documents with instant download. Currently supports CDSL via OTP + * authentication. + */ override fun fetch(): FetchService = fetch override fun parsePdf( @@ -60,6 +65,10 @@ class CdslServiceImpl internal constructor(private val clientOptions: ClientOpti clientOptions.toBuilder().apply(modifier::accept).build() ) + /** + * Endpoints for fetching CAS documents with instant download. Currently supports CDSL via + * OTP authentication. + */ override fun fetch(): FetchService.WithRawResponse = fetch private val parsePdfHandler: Handler = diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/ContractNoteService.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/ContractNoteService.kt index d7a1b64..bb49da3 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/ContractNoteService.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/ContractNoteService.kt @@ -10,6 +10,10 @@ import com.cas_parser.api.models.contractnote.ContractNoteParseResponse import com.google.errorprone.annotations.MustBeClosed import java.util.function.Consumer +/** + * Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, + * Upstox, ICICI etc. + */ interface ContractNoteService { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/ContractNoteServiceImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/ContractNoteServiceImpl.kt index 978c42b..7d5555f 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/ContractNoteServiceImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/ContractNoteServiceImpl.kt @@ -19,6 +19,10 @@ import com.cas_parser.api.models.contractnote.ContractNoteParseParams import com.cas_parser.api.models.contractnote.ContractNoteParseResponse import java.util.function.Consumer +/** + * Endpoints for parsing Contract Note PDF files from various SEBI brokers like Zerodha, Groww, + * Upstox, ICICI etc. + */ class ContractNoteServiceImpl internal constructor(private val clientOptions: ClientOptions) : ContractNoteService { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CreditService.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CreditService.kt index 0c7a9da..d4459c9 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CreditService.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CreditService.kt @@ -10,6 +10,10 @@ import com.cas_parser.api.models.credits.CreditCheckResponse import com.google.errorprone.annotations.MustBeClosed import java.util.function.Consumer +/** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your API + * usage and remaining quota. + */ interface CreditService { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CreditServiceImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CreditServiceImpl.kt index c611592..6425e16 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CreditServiceImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/CreditServiceImpl.kt @@ -19,6 +19,10 @@ import com.cas_parser.api.models.credits.CreditCheckParams import com.cas_parser.api.models.credits.CreditCheckResponse import java.util.function.Consumer +/** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your API + * usage and remaining quota. + */ class CreditServiceImpl internal constructor(private val clientOptions: ClientOptions) : CreditService { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/InboundEmailService.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/InboundEmailService.kt index c46275e..be0ff40 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/InboundEmailService.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/InboundEmailService.kt @@ -16,6 +16,29 @@ import com.cas_parser.api.models.inboundemail.InboundEmailRetrieveResponse import com.google.errorprone.annotations.MustBeClosed import java.util.function.Consumer +/** + * Create dedicated inbound email addresses for investors to forward their CAS statements. + * + * **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or file + * upload. + * + * **How it works:** + * 1. Call `POST /v4/inbound-email` to create a unique inbound email address + * 2. Display this email to your user: "Forward your CAS statement to ie_xxx@import.casparser.in" + * 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your webhook + * 4. Your webhook receives email metadata + attachment download URLs + * + * **Sender Validation:** + * - Only emails from verified CAS authorities are processed: + * - CDSL: `eCAS@cdslstatement.com` + * - NSDL: `NSDL-CAS@nsdl.co.in` + * - CAMS: `donotreply@camsonline.com` + * - KFintech: `samfS@kfintech.com` + * - Emails failing SPF/DKIM/DMARC are rejected + * - Forwarded emails must contain the original sender in headers + * + * **Billing:** 0.2 credits per successfully processed valid email + */ interface InboundEmailService { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/InboundEmailServiceImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/InboundEmailServiceImpl.kt index b0aefb3..704f560 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/InboundEmailServiceImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/InboundEmailServiceImpl.kt @@ -27,6 +27,29 @@ import com.cas_parser.api.models.inboundemail.InboundEmailRetrieveResponse import java.util.function.Consumer import kotlin.jvm.optionals.getOrNull +/** + * Create dedicated inbound email addresses for investors to forward their CAS statements. + * + * **Use Case:** Your app wants to collect CAS statements from users without requiring OAuth or file + * upload. + * + * **How it works:** + * 1. Call `POST /v4/inbound-email` to create a unique inbound email address + * 2. Display this email to your user: "Forward your CAS statement to ie_xxx@import.casparser.in" + * 3. When user forwards a CAS email, we verify sender authenticity (SPF/DKIM) and call your webhook + * 4. Your webhook receives email metadata + attachment download URLs + * + * **Sender Validation:** + * - Only emails from verified CAS authorities are processed: + * - CDSL: `eCAS@cdslstatement.com` + * - NSDL: `NSDL-CAS@nsdl.co.in` + * - CAMS: `donotreply@camsonline.com` + * - KFintech: `samfS@kfintech.com` + * - Emails failing SPF/DKIM/DMARC are rejected + * - Forwarded emails must contain the original sender in headers + * + * **Billing:** 0.2 credits per successfully processed valid email + */ class InboundEmailServiceImpl internal constructor(private val clientOptions: ClientOptions) : InboundEmailService { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/InboxService.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/InboxService.kt index e2b5ce0..051365a 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/InboxService.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/InboxService.kt @@ -16,6 +16,23 @@ import com.cas_parser.api.models.inbox.InboxListCasFilesResponse import com.google.errorprone.annotations.MustBeClosed import java.util.function.Consumer +/** + * Endpoints for importing CAS files directly from user email inboxes. + * + * **Supported Providers:** Gmail (more coming soon) + * + * **How it works:** + * 1. Call `POST /v4/inbox/connect` to get an OAuth URL + * 2. Redirect user to the OAuth URL for consent + * 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + * 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + * 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + * + * **Security:** + * - Read-only access (we cannot send emails) + * - Tokens are encrypted with server-side secret + * - User can revoke access anytime via `/v4/inbox/disconnect` + */ interface InboxService { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/InboxServiceImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/InboxServiceImpl.kt index 27ebb03..4abb1e6 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/InboxServiceImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/InboxServiceImpl.kt @@ -25,6 +25,23 @@ import com.cas_parser.api.models.inbox.InboxListCasFilesParams import com.cas_parser.api.models.inbox.InboxListCasFilesResponse import java.util.function.Consumer +/** + * Endpoints for importing CAS files directly from user email inboxes. + * + * **Supported Providers:** Gmail (more coming soon) + * + * **How it works:** + * 1. Call `POST /v4/inbox/connect` to get an OAuth URL + * 2. Redirect user to the OAuth URL for consent + * 3. User is redirected back to your `redirect_uri` with an encrypted `inbox_token` + * 4. Use the token to list/fetch CAS files from their inbox (`/v4/inbox/cas`) + * 5. Files are uploaded to temporary cloud storage (URLs expire in 24 hours) + * + * **Security:** + * - Read-only access (we cannot send emails) + * - Tokens are encrypted with server-side secret + * - User can revoke access anytime via `/v4/inbox/disconnect` + */ class InboxServiceImpl internal constructor(private val clientOptions: ClientOptions) : InboxService { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/KfintechService.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/KfintechService.kt index 0bc26c9..b4ac124 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/KfintechService.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/KfintechService.kt @@ -10,6 +10,7 @@ import com.cas_parser.api.models.kfintech.KfintechGenerateCasResponse import com.google.errorprone.annotations.MustBeClosed import java.util.function.Consumer +/** Endpoints for generating new CAS documents via email mailback (KFintech). */ interface KfintechService { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/KfintechServiceImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/KfintechServiceImpl.kt index 679e4d8..aeccbb9 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/KfintechServiceImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/KfintechServiceImpl.kt @@ -19,6 +19,7 @@ import com.cas_parser.api.models.kfintech.KfintechGenerateCasParams import com.cas_parser.api.models.kfintech.KfintechGenerateCasResponse import java.util.function.Consumer +/** Endpoints for generating new CAS documents via email mailback (KFintech). */ class KfintechServiceImpl internal constructor(private val clientOptions: ClientOptions) : KfintechService { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/LogService.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/LogService.kt index 7361743..6c7468b 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/LogService.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/LogService.kt @@ -12,6 +12,10 @@ import com.cas_parser.api.models.logs.LogGetSummaryResponse import com.google.errorprone.annotations.MustBeClosed import java.util.function.Consumer +/** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your API + * usage and remaining quota. + */ interface LogService { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/LogServiceImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/LogServiceImpl.kt index 4dce634..d481476 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/LogServiceImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/LogServiceImpl.kt @@ -21,6 +21,10 @@ import com.cas_parser.api.models.logs.LogGetSummaryParams import com.cas_parser.api.models.logs.LogGetSummaryResponse import java.util.function.Consumer +/** + * Endpoints for checking API quota and credits usage. These endpoints help you monitor your API + * usage and remaining quota. + */ class LogServiceImpl internal constructor(private val clientOptions: ClientOptions) : LogService { private val withRawResponse: LogService.WithRawResponse by lazy { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/NsdlService.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/NsdlService.kt index 8ca3b7c..786d115 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/NsdlService.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/NsdlService.kt @@ -10,6 +10,7 @@ import com.cas_parser.api.models.nsdl.NsdlParseParams import com.google.errorprone.annotations.MustBeClosed import java.util.function.Consumer +/** Endpoints for parsing CAS PDF files from different sources. */ interface NsdlService { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/NsdlServiceImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/NsdlServiceImpl.kt index a887773..582c6df 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/NsdlServiceImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/NsdlServiceImpl.kt @@ -19,6 +19,7 @@ import com.cas_parser.api.models.camskfintech.UnifiedResponse import com.cas_parser.api.models.nsdl.NsdlParseParams import java.util.function.Consumer +/** Endpoints for parsing CAS PDF files from different sources. */ class NsdlServiceImpl internal constructor(private val clientOptions: ClientOptions) : NsdlService { private val withRawResponse: NsdlService.WithRawResponse by lazy { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/SmartService.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/SmartService.kt index fce30ed..79198c1 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/SmartService.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/SmartService.kt @@ -10,6 +10,7 @@ import com.cas_parser.api.models.smart.SmartParseCasPdfParams import com.google.errorprone.annotations.MustBeClosed import java.util.function.Consumer +/** Endpoints for parsing CAS PDF files from different sources. */ interface SmartService { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/SmartServiceImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/SmartServiceImpl.kt index 6808cd7..2c8c2d8 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/SmartServiceImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/SmartServiceImpl.kt @@ -19,6 +19,7 @@ import com.cas_parser.api.models.camskfintech.UnifiedResponse import com.cas_parser.api.models.smart.SmartParseCasPdfParams import java.util.function.Consumer +/** Endpoints for parsing CAS PDF files from different sources. */ class SmartServiceImpl internal constructor(private val clientOptions: ClientOptions) : SmartService { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/VerifyTokenService.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/VerifyTokenService.kt index e8135cd..468e98d 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/VerifyTokenService.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/VerifyTokenService.kt @@ -10,6 +10,11 @@ import com.cas_parser.api.models.verifytoken.VerifyTokenVerifyResponse import com.google.errorprone.annotations.MustBeClosed import java.util.function.Consumer +/** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access + * tokens can be used in place of API keys on all v4 endpoints. + */ interface VerifyTokenService { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/VerifyTokenServiceImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/VerifyTokenServiceImpl.kt index bf3fce7..e76ff7d 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/VerifyTokenServiceImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/VerifyTokenServiceImpl.kt @@ -19,6 +19,11 @@ import com.cas_parser.api.models.verifytoken.VerifyTokenVerifyParams import com.cas_parser.api.models.verifytoken.VerifyTokenVerifyResponse import java.util.function.Consumer +/** + * Endpoints for managing access tokens for the Portfolio Connect SDK. Use these to generate + * short-lived `at_` prefixed tokens that can be safely passed to frontend applications. Access + * tokens can be used in place of API keys on all v4 endpoints. + */ class VerifyTokenServiceImpl internal constructor(private val clientOptions: ClientOptions) : VerifyTokenService { diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/cdsl/FetchService.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/cdsl/FetchService.kt index 81f2bc3..b8f7006 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/cdsl/FetchService.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/cdsl/FetchService.kt @@ -12,6 +12,10 @@ import com.cas_parser.api.models.cdsl.fetch.FetchVerifyOtpResponse import com.google.errorprone.annotations.MustBeClosed import java.util.function.Consumer +/** + * Endpoints for fetching CAS documents with instant download. Currently supports CDSL via OTP + * authentication. + */ interface FetchService { /** diff --git a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/cdsl/FetchServiceImpl.kt b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/cdsl/FetchServiceImpl.kt index e9a3246..9c8db9f 100644 --- a/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/cdsl/FetchServiceImpl.kt +++ b/cas-parser-java-core/src/main/kotlin/com/cas_parser/api/services/blocking/cdsl/FetchServiceImpl.kt @@ -23,6 +23,10 @@ import com.cas_parser.api.models.cdsl.fetch.FetchVerifyOtpResponse import java.util.function.Consumer import kotlin.jvm.optionals.getOrNull +/** + * Endpoints for fetching CAS documents with instant download. Currently supports CDSL via OTP + * authentication. + */ class FetchServiceImpl internal constructor(private val clientOptions: ClientOptions) : FetchService { diff --git a/cas-parser-java-core/src/test/kotlin/com/cas_parser/api/core/http/HttpRequestBodiesTest.kt b/cas-parser-java-core/src/test/kotlin/com/cas_parser/api/core/http/HttpRequestBodiesTest.kt new file mode 100644 index 0000000..8b9e0fd --- /dev/null +++ b/cas-parser-java-core/src/test/kotlin/com/cas_parser/api/core/http/HttpRequestBodiesTest.kt @@ -0,0 +1,729 @@ +// File generated from our OpenAPI spec by Stainless. + +package com.cas_parser.api.core.http + +import com.cas_parser.api.core.MultipartField +import com.cas_parser.api.core.jsonMapper +import java.io.ByteArrayOutputStream +import java.io.InputStream +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.Test + +internal class HttpRequestBodiesTest { + + @Test + fun multipartFormData_serializesFieldWithFilename() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "file" to + MultipartField.builder() + .value("hello") + .filename("hello.txt") + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(output.size().toLong()).isEqualTo(body.contentLength()) + val boundary = body.contentType()!!.substringAfter("multipart/form-data; boundary=") + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="file"; filename="hello.txt" + |Content-Type: text/plain + | + |hello + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesFieldWithoutFilename() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "field" to + MultipartField.builder() + .value("value") + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(output.size().toLong()).isEqualTo(body.contentLength()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="field" + |Content-Type: text/plain + | + |value + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesInputStream() { + // Use `.buffered()` to get a non-ByteArrayInputStream, which hits the non-repeatable code + // path. + val inputStream = "stream content".byteInputStream().buffered() + val body = + multipartFormData( + jsonMapper(), + mapOf( + "data" to + MultipartField.builder() + .value(inputStream) + .contentType("application/octet-stream") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isFalse() + assertThat(body.contentLength()).isEqualTo(-1L) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="data" + |Content-Type: application/octet-stream + | + |stream content + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesByteArray() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "binary" to + MultipartField.builder() + .value("abc".toByteArray()) + .contentType("application/octet-stream") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="binary" + |Content-Type: application/octet-stream + | + |abc + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesBooleanValue() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "flag" to + MultipartField.builder() + .value(true) + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="flag" + |Content-Type: text/plain + | + |true + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesNumberValue() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "count" to + MultipartField.builder().value(42).contentType("text/plain").build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="count" + |Content-Type: text/plain + | + |42 + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesNullValueAsNoParts() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "present" to + MultipartField.builder() + .value("yes") + .contentType("text/plain") + .build(), + "absent" to + MultipartField.builder() + .value(null as String?) + .contentType("text/plain") + .build(), + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="present" + |Content-Type: text/plain + | + |yes + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesArray() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "items" to + MultipartField.builder>() + .value(listOf("alpha", "beta", "gamma")) + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="items" + |Content-Type: text/plain + | + |alpha,beta,gamma + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesObjectAsNestedParts() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "meta" to + MultipartField.builder>() + .value(mapOf("key1" to "val1", "key2" to "val2")) + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="meta[key1]" + |Content-Type: text/plain + | + |val1 + |--$boundary + |Content-Disposition: form-data; name="meta[key2]" + |Content-Type: text/plain + | + |val2 + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesMultipleFields() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "name" to + MultipartField.builder() + .value("Alice") + .contentType("text/plain") + .build(), + "age" to + MultipartField.builder().value(30).contentType("text/plain").build(), + "file" to + MultipartField.builder() + .value("file contents") + .filename("doc.txt") + .contentType("text/plain") + .build(), + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="name" + |Content-Type: text/plain + | + |Alice + |--$boundary + |Content-Disposition: form-data; name="age" + |Content-Type: text/plain + | + |30 + |--$boundary + |Content-Disposition: form-data; name="file"; filename="doc.txt" + |Content-Type: text/plain + | + |file contents + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_quotesSpecialCharactersInNameAndFilename() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "field\nname" to + MultipartField.builder() + .value("value") + .filename("file\r\"name.txt") + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="field%0Aname"; filename="file%0D%22name.txt" + |Content-Type: text/plain + | + |value + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_writeIsRepeatable() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "field" to + MultipartField.builder() + .value("repeatable") + .contentType("text/plain") + .build() + ), + ) + + val output1 = ByteArrayOutputStream() + body.writeTo(output1) + val output2 = ByteArrayOutputStream() + body.writeTo(output2) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output1.size().toLong()) + val boundary = boundary(body) + val expected = + """ + |--$boundary + |Content-Disposition: form-data; name="field" + |Content-Type: text/plain + | + |repeatable + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + assertThat(output1.toString("UTF-8")).isEqualTo(expected) + assertThat(output2.toString("UTF-8")).isEqualTo(expected) + } + + @Test + fun multipartFormData_serializesByteArrayInputStream() { + // ByteArrayInputStream is specifically handled as repeatable with known content length. + val inputStream = "byte array stream".byteInputStream() + val body = + multipartFormData( + jsonMapper(), + mapOf( + "data" to + MultipartField.builder() + .value(inputStream) + .contentType("application/octet-stream") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="data" + |Content-Type: application/octet-stream + | + |byte array stream + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesInputStreamWithFilename() { + // Use `.buffered()` to get a non-ByteArrayInputStream, which hits the non-repeatable code + // path. + val inputStream = "file data".byteInputStream().buffered() + val body = + multipartFormData( + jsonMapper(), + mapOf( + "upload" to + MultipartField.builder() + .value(inputStream) + .filename("upload.bin") + .contentType("application/octet-stream") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isFalse() + assertThat(body.contentLength()).isEqualTo(-1L) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="upload"; filename="upload.bin" + |Content-Type: application/octet-stream + | + |file data + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesNestedArrayInObject() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "data" to + MultipartField.builder>>() + .value(mapOf("tags" to listOf("a", "b"))) + .contentType("text/plain") + .build() + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="data[tags]" + |Content-Type: text/plain + | + |a,b + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_contentLengthIsUnknownWhenInputStreamPresent() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "text" to + MultipartField.builder() + .value("hello") + .contentType("text/plain") + .build(), + "stream" to + MultipartField.builder() + // Use `.buffered()` to get a non-ByteArrayInputStream, which hits the + // non-repeatable code path. + .value("data".byteInputStream().buffered()) + .contentType("application/octet-stream") + .build(), + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isFalse() + assertThat(body.contentLength()).isEqualTo(-1L) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="text" + |Content-Type: text/plain + | + |hello + |--$boundary + |Content-Disposition: form-data; name="stream" + |Content-Type: application/octet-stream + | + |data + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesEmptyArray() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "required" to + MultipartField.builder() + .value("present") + .contentType("text/plain") + .build(), + "items" to + MultipartField.builder>() + .value(emptyList()) + .contentType("text/plain") + .build(), + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="required" + |Content-Type: text/plain + | + |present + |--$boundary + |Content-Disposition: form-data; name="items" + |Content-Type: text/plain + | + | + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + @Test + fun multipartFormData_serializesEmptyObject() { + val body = + multipartFormData( + jsonMapper(), + mapOf( + "required" to + MultipartField.builder() + .value("present") + .contentType("text/plain") + .build(), + "meta" to + MultipartField.builder>() + .value(emptyMap()) + .contentType("text/plain") + .build(), + ), + ) + + val output = ByteArrayOutputStream() + body.writeTo(output) + + assertThat(body.repeatable()).isTrue() + assertThat(body.contentLength()).isEqualTo(output.size().toLong()) + val boundary = boundary(body) + assertThat(output.toString("UTF-8")) + .isEqualTo( + """ + |--$boundary + |Content-Disposition: form-data; name="required" + |Content-Type: text/plain + | + |present + |--$boundary-- + | + """ + .trimMargin() + .replace("\n", "\r\n") + ) + } + + private fun boundary(body: HttpRequestBody): String = + body.contentType()!!.substringAfter("multipart/form-data; boundary=") +} diff --git a/cas-parser-java-core/src/test/kotlin/com/cas_parser/api/core/http/RetryingHttpClientTest.kt b/cas-parser-java-core/src/test/kotlin/com/cas_parser/api/core/http/RetryingHttpClientTest.kt index 90db8ea..c3f47cd 100644 --- a/cas-parser-java-core/src/test/kotlin/com/cas_parser/api/core/http/RetryingHttpClientTest.kt +++ b/cas-parser-java-core/src/test/kotlin/com/cas_parser/api/core/http/RetryingHttpClientTest.kt @@ -1,10 +1,21 @@ +// File generated from our OpenAPI spec by Stainless. + package com.cas_parser.api.core.http import com.cas_parser.api.client.okhttp.OkHttpClient import com.cas_parser.api.core.RequestOptions import com.cas_parser.api.core.Sleeper import com.cas_parser.api.errors.CasParserRetryableException -import com.github.tomakehurst.wiremock.client.WireMock.* +import com.github.tomakehurst.wiremock.client.WireMock.equalTo +import com.github.tomakehurst.wiremock.client.WireMock.matching +import com.github.tomakehurst.wiremock.client.WireMock.ok +import com.github.tomakehurst.wiremock.client.WireMock.post +import com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor +import com.github.tomakehurst.wiremock.client.WireMock.resetAllScenarios +import com.github.tomakehurst.wiremock.client.WireMock.serviceUnavailable +import com.github.tomakehurst.wiremock.client.WireMock.stubFor +import com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo +import com.github.tomakehurst.wiremock.client.WireMock.verify import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo import com.github.tomakehurst.wiremock.junit5.WireMockTest import com.github.tomakehurst.wiremock.stubbing.Scenario